From 767265325421c81c9cf05d07953beac116d599e4 Mon Sep 17 00:00:00 2001 From: architeur Date: Sun, 14 Dec 2025 20:53:14 +0100 Subject: [PATCH] Integrate symbols/ into main repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added symbols/ folder (previously separate repo) - Removed symbols/ from .gitignore - Updated CLAUDE.md documentation - Deleted separate Symbols repo on Gitea 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .gitignore | 1 - CLAUDE.md | 6 +- symbols/.gitignore | 11 + symbols/README.md | 67 + symbols/css/styles.css | 1248 +++++++++++++++ symbols/index.html | 385 +++++ symbols/index2.html | 385 +++++ symbols/index3.html | 385 +++++ symbols/index4.html | 390 +++++ symbols/js/app.js | 1684 ++++++++++++++++++++ symbols/js/symbols.js | 870 ++++++++++ symbols/js/symbols.js.backup3 | 859 ++++++++++ symbols/js/text-generator.js | 1154 ++++++++++++++ symbols/js/text-generator/export.js | 321 ++++ symbols/js/text-generator/state.js | 95 ++ symbols/js/text-generator/svg-generator.js | 371 +++++ symbols/js/text-generator/ui-bindings.js | 344 ++++ symbols/lib/svg.min.js | 13 + symbols/symbols.js | 912 +++++++++++ 19 files changed, 9497 insertions(+), 4 deletions(-) create mode 100644 symbols/.gitignore create mode 100644 symbols/README.md create mode 100644 symbols/css/styles.css create mode 100644 symbols/index.html create mode 100644 symbols/index2.html create mode 100644 symbols/index3.html create mode 100644 symbols/index4.html create mode 100644 symbols/js/app.js create mode 100644 symbols/js/symbols.js create mode 100644 symbols/js/symbols.js.backup3 create mode 100644 symbols/js/text-generator.js create mode 100644 symbols/js/text-generator/export.js create mode 100644 symbols/js/text-generator/state.js create mode 100644 symbols/js/text-generator/svg-generator.js create mode 100644 symbols/js/text-generator/ui-bindings.js create mode 100644 symbols/lib/svg.min.js create mode 100644 symbols/symbols.js diff --git a/.gitignore b/.gitignore index 4804afd..f9205ee 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ node_modules/ # Temp files *.tmp *.bak -symbols/ kostenschaetzung/ schadendokumentation/ zeitwert/ diff --git a/CLAUDE.md b/CLAUDE.md index 3305b67..825e2da 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,10 +30,10 @@ Dieses Repository enthält die Landing Page und kleine SPAs für **docs.artetui. ├── fotoupload/ # Foto-Upload SPA ├── geo-calc/ # Geo-Rechner SPA ├── legal/ # Impressum & Datenschutz +├── symbols/ # Gutachter Symbolbibliothek ├── zeitutility/ # Zeit-Utilities SPA │ # Separate Git-Repositories (nicht in diesem Repo): -├── symbols/ # → git.artetui.de/admin/Symbols ├── kostenschaetzung/ # → eigenes Repo ├── schadendokumentation/ # → eigenes Repo └── zeitwert/ # → eigenes Repo @@ -78,14 +78,14 @@ git push | Repository | URL | Beschreibung | |------------|-----|--------------| | **server-config** | git.artetui.de/admin/server-config | Server-Administration | -| **Symbols** | git.artetui.de/admin/Symbols | Gutachter Symbolbibliothek | | **SPA-landing** | git.artetui.de/admin/SPA-landing | Dieses Repo | ## Hinweise -- Die Subprojekte (symbols, kostenschaetzung, etc.) haben eigene Git-Repos +- Die Subprojekte (kostenschaetzung, schadendokumentation, zeitwert) haben eigene Git-Repos - Sie sind in `.gitignore` ausgeschlossen - Änderungen an diesen müssen in deren jeweiligen Repos gemacht werden +- `symbols/` ist jetzt Teil dieses Repos --- **Letzte Aktualisierung:** 2025-12-14 diff --git a/symbols/.gitignore b/symbols/.gitignore new file mode 100644 index 0000000..f644bec --- /dev/null +++ b/symbols/.gitignore @@ -0,0 +1,11 @@ +# OS files +.DS_Store +Thumbs.db + +# IDE +.vscode/ +.idea/ + +# Temporary files +*.tmp +*.bak diff --git a/symbols/README.md b/symbols/README.md new file mode 100644 index 0000000..a0b82b0 --- /dev/null +++ b/symbols/README.md @@ -0,0 +1,67 @@ +# Gutachter Symbolbibliothek + +Webanwendung für die Erstellung von Symbolen für Gutachten. + +## Live-URL + +https://docs.artetui.de/symbols/ + +## Seiten + +- **index.html** - Hauptseite mit Symbol-Bibliothek +- **index4.html** - Text-Generator (neue SVG.js Version) + +## Features + +### Symbol-Bibliothek (index.html) +- Vordefinierte CAD-Symbole für Gutachten +- Suche und Filterung +- Export als SVG, PNG, JPG, DXF + +### Text-Generator (index4.html) +- Erstellt Text-Symbole mit Rahmen und Pfeilen +- Verschiedene Rahmenformen (Rechteck, Abgerundet, Oval, Raute) +- Konfigurierbare Pfeile mit Winkel und Knick +- Asymmetrisches Padding für Text-Positionierung +- Standalone-Pfeil Export +- Export in verschiedenen Formaten + +## Technologie + +- **SVG.js** - Vector Graphics Library +- **Vanilla JavaScript** - Kein Framework +- **Modular Architecture** (Text-Generator): + - state.js - State Management + - svg-generator.js - SVG-Generierung + - ui-bindings.js - UI Event Bindings + - export.js - Export-Funktionen + +## Verzeichnisstruktur + + symbols/ + ├── index.html # Symbol-Bibliothek + ├── index4.html # Text-Generator (SVG.js) + ├── symbols.js # Symbol-Definitionen + ├── css/ # Stylesheets + ├── lib/ + │ └── svg.min.js # SVG.js Library + └── js/ + └── text-generator/ + ├── state.js + ├── svg-generator.js + ├── ui-bindings.js + └── export.js + +## Entwicklung + + # Auf dem Server + cd /opt/stacks/spa-hosting/html/symbols + + # Änderungen committen + git add . + git commit -m Beschreibung + git push origin master + +## Lizenz + +Intern - ArTeTui diff --git a/symbols/css/styles.css b/symbols/css/styles.css new file mode 100644 index 0000000..e72b71f --- /dev/null +++ b/symbols/css/styles.css @@ -0,0 +1,1248 @@ +/* ============================================ + GUTACHTER SYMBOLBIBLIOTHEK - Styles v2.0 + ============================================ */ + +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); + +:root { + --primary: #2563eb; + --primary-dark: #1d4ed8; + --danger: #dc2626; + --success: #16a34a; + --warning: #f59e0b; + --gray-50: #f9fafb; + --gray-100: #f3f4f6; + --gray-200: #e5e7eb; + --gray-300: #d1d5db; + --gray-400: #9ca3af; + --gray-500: #6b7280; + --gray-600: #4b5563; + --gray-700: #374151; + --gray-800: #1f2937; + --gray-900: #111827; + --radius: 12px; + --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); +} + +* { box-sizing: border-box; margin: 0; padding: 0; } + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + background: var(--gray-100); + color: var(--gray-800); + line-height: 1.5; +} + +/* ========== HEADER ========== */ +.header { + background: linear-gradient(135deg, var(--gray-800) 0%, var(--gray-900) 100%); + color: white; + padding: 1.5rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; + position: sticky; + top: 0; + z-index: 100; + box-shadow: var(--shadow-lg); +} + +.header-content h1 { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 0.25rem; +} + +.header-content .subtitle { + color: var(--gray-400); + font-size: 0.85rem; +} + +.header-actions { + display: flex; + gap: 0.75rem; +} + +.btn-header { + background: rgba(255,255,255,0.1); + color: white; + border: 1px solid rgba(255,255,255,0.2); + padding: 0.6rem 1rem; + border-radius: var(--radius); + cursor: pointer; + font-size: 0.85rem; + font-weight: 500; + transition: all 0.2s; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.btn-header:hover { + background: rgba(255,255,255,0.2); + border-color: rgba(255,255,255,0.3); +} + +.btn-header .badge { + background: var(--primary); + padding: 0.1rem 0.5rem; + border-radius: 10px; + font-size: 0.75rem; +} + +/* ========== SEARCH & FILTER ========== */ +.search-container { + background: white; + padding: 1rem 2rem; + border-bottom: 1px solid var(--gray-200); + position: sticky; + top: 72px; + z-index: 90; +} + +.search-box { + position: relative; + max-width: 400px; + margin-bottom: 1rem; +} + +.search-box input { + width: 100%; + padding: 0.75rem 1rem 0.75rem 2.5rem; + border: 2px solid var(--gray-200); + border-radius: var(--radius); + font-size: 0.9rem; + transition: border-color 0.2s; +} + +.search-box input:focus { + outline: none; + border-color: var(--primary); +} + +.search-box .search-icon { + position: absolute; + left: 0.75rem; + top: 50%; + transform: translateY(-50%); +} + +.filter-pills { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + align-items: center; +} + +.filter-pill { + padding: 0.5rem 1rem; + border: 2px solid var(--gray-200); + border-radius: 20px; + background: white; + cursor: pointer; + font-size: 0.8rem; + font-weight: 500; + transition: all 0.2s; +} + +.filter-pill:hover { + border-color: var(--primary); + color: var(--primary); +} + +.filter-pill.active { + background: var(--primary); + border-color: var(--primary); + color: white; +} + +.filter-divider { + color: var(--gray-300); + margin: 0 0.25rem; +} + +.filter-label { + font-size: 0.75rem; + color: var(--gray-500); + font-weight: 600; + text-transform: uppercase; +} + +/* ========== MAIN CONTENT ========== */ +.main-content { + max-width: 1600px; + margin: 0 auto; + padding: 2rem; +} + +/* ========== SYMBOL GRID (Container) ========== */ +.symbol-grid { + display: flex; + flex-direction: column; + gap: 2.5rem; +} + +/* ========== CATEGORY HEADER ========== */ +.category-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.75rem 1rem; + background: linear-gradient(90deg, var(--gray-200), transparent); + border-radius: var(--radius); + margin-bottom: 1rem; +} + +.category-header span:first-child { + font-size: 1.1rem; + font-weight: 600; + color: var(--gray-800); +} + +.category-count { + background: var(--gray-300); + color: var(--gray-700); + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; +} + +/* ========== CATEGORY GRID ========== */ +.category-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 1rem; +} + +/* Gleichmäßige Kartenhöhe */ +.category-grid .symbol-card { + height: 100%; + min-height: 200px; +} + +/* ========== SYMBOL CARD ========== */ +.symbol-card { + background: white; + border: 2px solid var(--gray-200); + border-radius: var(--radius); + padding: 1.25rem 1rem; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + position: relative; +} + +.symbol-card:hover { + border-color: var(--primary); + box-shadow: var(--shadow-lg); + transform: translateY(-4px); +} + +.symbol-card svg { + width: 64px; + height: 64px; + margin-bottom: 0.75rem; +} + +.symbol-name { + font-size: 0.8rem; + font-weight: 600; + color: var(--gray-700); + line-height: 1.3; + margin-bottom: 0.5rem; +} + +/* ========== SYMBOL ACTIONS ========== */ +.symbol-actions { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.3rem; + margin-top: auto; + padding-top: 0.75rem; + width: 100%; + opacity: 0; + transition: opacity 0.2s; +} + +.symbol-card:hover .symbol-actions { + opacity: 1; +} + +.btn-action { + padding: 0.35rem 0.4rem; + border: 1px solid var(--gray-300); + border-radius: 6px; + background: white; + cursor: pointer; + font-size: 0.65rem; + transition: all 0.15s; + white-space: nowrap; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; +} + +.btn-action:hover { + background: var(--gray-100); +} + +.btn-copy:hover { background: #dcfce7; border-color: #22c55e; } +.btn-svg:hover { background: #dbeafe; border-color: #3b82f6; } +.btn-dxf:hover { background: #fef3c7; border-color: #f59e0b; } +.btn-legend:hover { background: #fce7f3; border-color: #ec4899; } +.btn-legend.active { background: #fce7f3; border-color: #ec4899; } + +/* ========== CHECKBOX ========== */ +.symbol-checkbox { + position: absolute; + top: 0.5rem; + right: 0.5rem; + width: 22px; + height: 22px; + border: 2px solid var(--gray-300); + border-radius: 6px; + background: white; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.75rem; + transition: all 0.2s; + opacity: 0; +} + +.symbol-card:hover .symbol-checkbox { + opacity: 1; +} + +.symbol-checkbox.checked { + opacity: 1; + background: var(--primary); + border-color: var(--primary); + color: white; +} + +/* ========== MODAL ========== */ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.5); + z-index: 1000; + align-items: center; + justify-content: center; +} + +.modal.open { + display: flex; +} + +.modal-content { + background: white; + border-radius: var(--radius); + width: 90%; + max-width: 600px; + max-height: 80vh; + display: flex; + flex-direction: column; + box-shadow: var(--shadow-lg); +} + +.modal-header { + padding: 1.25rem 1.5rem; + border-bottom: 1px solid var(--gray-200); + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal-header h2 { + font-size: 1.1rem; + font-weight: 600; +} + +.modal-close { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: var(--gray-500); + padding: 0.25rem; + line-height: 1; +} + +.modal-close:hover { + color: var(--gray-800); +} + +.modal-body { + flex: 1; + overflow-y: auto; + padding: 1.5rem; +} + +.modal-footer { + padding: 1rem 1.5rem; + border-top: 1px solid var(--gray-200); + display: flex; + gap: 0.75rem; + justify-content: flex-end; +} + +/* ========== BUTTONS ========== */ +.btn-primary { + background: var(--primary); + color: white; + border: none; + padding: 0.6rem 1.25rem; + border-radius: var(--radius); + cursor: pointer; + font-weight: 600; + font-size: 0.85rem; + transition: background 0.2s; +} + +.btn-primary:hover { + background: var(--primary-dark); +} + +.btn-secondary { + background: var(--gray-100); + color: var(--gray-700); + border: 1px solid var(--gray-300); + padding: 0.6rem 1.25rem; + border-radius: var(--radius); + cursor: pointer; + font-weight: 500; + font-size: 0.85rem; + transition: all 0.2s; +} + +.btn-secondary:hover { + background: var(--gray-200); +} + +/* ========== LEGEND ITEMS ========== */ +.legend-items { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.legend-item { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem; + background: var(--gray-50); + border-radius: 8px; + border: 1px solid var(--gray-200); +} + +.legend-item svg { + width: 40px; + height: 40px; + flex-shrink: 0; +} + +.legend-item-content { + flex: 1; +} + +.legend-item-name { + font-size: 0.75rem; + color: var(--gray-500); + margin-bottom: 0.25rem; +} + +.legend-item-input { + width: 100%; + padding: 0.4rem 0.6rem; + border: 1px solid var(--gray-300); + border-radius: 6px; + font-size: 0.85rem; +} + +.legend-item-input:focus { + outline: none; + border-color: var(--primary); +} + +.legend-item-remove { + background: var(--danger); + color: white; + border: none; + border-radius: 6px; + width: 28px; + height: 28px; + cursor: pointer; + font-size: 1rem; +} + +.legend-empty { + text-align: center; + color: var(--gray-400); + padding: 2rem; +} + +/* ========== TOAST ========== */ +.toast { + position: fixed; + bottom: 2rem; + left: 50%; + transform: translateX(-50%) translateY(100px); + background: var(--gray-800); + color: white; + padding: 1rem 2rem; + border-radius: var(--radius); + font-weight: 500; + box-shadow: var(--shadow-lg); + opacity: 0; + transition: all 0.3s; + z-index: 1100; +} + +.toast.show { + transform: translateX(-50%) translateY(0); + opacity: 1; +} + +/* ========== FOOTER ========== */ +.footer { + text-align: center; + padding: 2rem; + color: var(--gray-500); + font-size: 0.85rem; + border-top: 1px solid var(--gray-200); + background: white; + margin-top: 2rem; +} + +/* ========== RESPONSIVE ========== */ +@media (max-width: 768px) { + .header { + flex-direction: column; + text-align: center; + padding: 1rem; + } + + .search-container { + padding: 1rem; + top: auto; + position: relative; + } + + .search-box { + max-width: 100%; + } + + .main-content { + padding: 1rem; + } + + .symbol-grid { + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 0.75rem; + } + + .symbol-card { + padding: 1rem 0.75rem; + } + + .symbol-actions { + opacity: 1; + flex-wrap: wrap; + justify-content: center; + } +} + +/* ========== HIDDEN ========== */ +.hidden { + display: none !important; +} + +/* ========== TEXT-GENERATOR ========== */ +.text-generator { + background: white; + margin: 1rem 2rem; + border-radius: var(--radius); + box-shadow: var(--shadow); + overflow: hidden; +} + +.text-generator-header { + background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); + color: white; + padding: 0.75rem 1.25rem; + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + user-select: none; +} + +.text-generator-header h3 { + font-size: 1rem; + font-weight: 600; + margin: 0; +} + +.collapse-btn { + background: none; + border: none; + color: white; + font-size: 1rem; + cursor: pointer; + transition: transform 0.3s; + padding: 0.25rem; +} + +.text-generator.collapsed .collapse-btn { + transform: rotate(-90deg); +} + +.text-generator-body { + padding: 1.25rem; + display: flex; + flex-direction: column; + gap: 1rem; + transition: all 0.3s; +} + +.text-generator.collapsed .text-generator-body { + display: none; +} + +.text-generator-row { + display: flex; + gap: 2rem; + flex-wrap: wrap; +} + +.text-input-group { + display: flex; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; +} + +.text-input-group label { + font-weight: 500; + font-size: 0.85rem; + color: var(--gray-700); + min-width: 90px; +} + +.text-input-group input[type="text"] { + padding: 0.5rem 0.75rem; + border: 1px solid var(--gray-300); + border-radius: 6px; + font-size: 0.9rem; + width: 180px; + transition: border-color 0.2s, box-shadow 0.2s; +} + +.text-input-group input[type="text"]:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + +.text-input-group input[type="range"] { + width: 120px; + cursor: pointer; +} + +#fontSizeValue { + font-size: 0.8rem; + color: var(--gray-500); + min-width: 35px; +} + +/* Shape, Line Style, Weight Buttons */ +.shape-options, +.line-style-options, +.line-weight-options { + display: flex; + gap: 0.35rem; +} + +.shape-btn, +.line-btn, +.weight-btn { + padding: 0.4rem 0.65rem; + border: 1px solid var(--gray-300); + background: white; + border-radius: 6px; + cursor: pointer; + font-size: 0.8rem; + transition: all 0.2s; + color: var(--gray-600); +} + +.shape-btn:hover, +.line-btn:hover, +.weight-btn:hover { + border-color: var(--primary); + color: var(--primary); +} + +.shape-btn.active, +.line-btn.active, +.weight-btn.active { + background: var(--primary); + color: white; + border-color: var(--primary); +} + +/* Preview */ +.text-generator-preview { + display: flex; + align-items: center; + gap: 1.5rem; + padding-top: 1rem; + border-top: 1px solid var(--gray-200); + flex-wrap: wrap; +} + +.preview-label { + font-weight: 500; + font-size: 0.85rem; + color: var(--gray-700); +} + +.preview-box { + width: 100px; + height: 100px; + border: 2px dashed var(--gray-300); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + background: var(--gray-50); +} + +.preview-box svg { + max-width: 80px; + max-height: 80px; +} + +.preview-actions { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + +/* Textarea fuer Text-Generator */ +.text-input-wide { + flex: 1; + min-width: 250px; +} + +.text-input-group textarea { + padding: 0.5rem 0.75rem; + border: 1px solid var(--gray-300); + border-radius: 6px; + font-size: 0.9rem; + font-family: inherit; + width: 100%; + min-width: 200px; + resize: vertical; + transition: border-color 0.2s, box-shadow 0.2s; +} + +.text-input-group textarea:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + +/* Preview-Box dynamische Groesse */ +.preview-box { + min-width: 100px; + min-height: 80px; + width: auto; + height: auto; + max-width: 300px; + max-height: 200px; + padding: 10px; +} + +.preview-box svg { + max-width: 100%; + max-height: 100%; + width: auto; + height: auto; +} +/* ========== TEXT-GENERATOR v3 ADDITIONS ========== */ + +/* Color Picker */ +.color-picker-wrap { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.color-picker-wrap input[type="color"] { + -webkit-appearance: none; + appearance: none; + width: 40px; + height: 32px; + border: 1px solid var(--gray-300); + border-radius: 4px; + cursor: pointer; + padding: 2px; +} + +.color-picker-wrap input[type="color"]::-webkit-color-swatch-wrapper { + padding: 0; +} + +.color-picker-wrap input[type="color"]::-webkit-color-swatch { + border: none; + border-radius: 2px; +} + +.color-value { + font-family: monospace; + font-size: 0.85rem; + color: var(--gray-600); +} + +/* Arrow Options */ +.arrow-options { + display: flex; + gap: 0.25rem; + flex-wrap: wrap; +} + +.arrow-btn { + padding: 0.4rem 0.6rem; + border: 1px solid var(--gray-300); + background: white; + border-radius: 4px; + cursor: pointer; + font-size: 0.8rem; + transition: all 0.2s; +} + +.arrow-btn:hover { + border-color: var(--primary); + background: var(--primary-light); +} + +.arrow-btn.active { + background: var(--primary); + color: white; + border-color: var(--primary); +} + +/* Arrow Details Row */ +#arrowDetailsRow { + background: var(--gray-50); + padding: 0.75rem; + border-radius: 6px; + margin-top: 0.5rem; +} + +#arrowDetailsRow .text-input-group { + flex: 1; + min-width: 120px; +} + +/* Save Button */ +.btn-save { + background: linear-gradient(135deg, #10b981, #059669) !important; +} + +.btn-save:hover { + background: linear-gradient(135deg, #059669, #047857) !important; +} + +/* Save Modal */ +.modal-small { + max-width: 400px; +} + +.save-form { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.form-group label { + font-weight: 500; + color: var(--gray-700); +} + +.form-group input[type="text"] { + padding: 0.75rem; + border: 1px solid var(--gray-300); + border-radius: 6px; + font-size: 1rem; +} + +.form-group input[type="text"]:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + +/* Custom Symbols Badge */ +.filter-pill[data-filter="custom"] { + background: linear-gradient(135deg, #10b981, #059669); + color: white; + border-color: #059669; +} + +.filter-pill[data-filter="custom"]:hover { + background: linear-gradient(135deg, #059669, #047857); +} + +/* Symbol Card Delete Button for custom symbols */ +.symbol-card .btn-delete { + position: absolute; + top: 4px; + right: 4px; + width: 20px; + height: 20px; + border-radius: 50%; + background: #ef4444; + color: white; + border: none; + cursor: pointer; + font-size: 12px; + line-height: 1; + display: none; + z-index: 10; +} + +.symbol-card:hover .btn-delete { + display: flex; + align-items: center; + justify-content: center; +} + +.symbol-card .btn-delete:hover { + background: #dc2626; +} + +/* Improved range inputs */ +input[type="range"] { + -webkit-appearance: none; + appearance: none; + height: 6px; + background: var(--gray-200); + border-radius: 3px; + outline: none; +} + +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + background: var(--primary); + border-radius: 50%; + cursor: pointer; + transition: transform 0.2s; +} + +input[type="range"]::-webkit-slider-thumb:hover { + transform: scale(1.2); +} + +input[type="range"]::-moz-range-thumb { + width: 16px; + height: 16px; + background: var(--primary); + border-radius: 50%; + cursor: pointer; + border: none; +} + +/* Preview Box Dynamic Size */ +.preview-box { + min-width: 100px; + min-height: 80px; + width: auto; + height: auto; + max-width: 400px; + max-height: 300px; + display: flex; + align-items: center; + justify-content: center; + overflow: auto; +} + +.preview-box svg { + max-width: 100%; + max-height: 100%; +} + +/* ========== TEXT-GENERATOR v4 CSS ADDITIONS ========== */ + +/* PNG Button */ +.btn-png { + background: linear-gradient(135deg, #8b5cf6, #7c3aed) !important; +} + +.btn-png:hover { + background: linear-gradient(135deg, #7c3aed, #6d28d9) !important; +} + +/* JPG Button */ +.btn-jpg { + background: linear-gradient(135deg, #f59e0b, #d97706) !important; +} + +.btn-jpg:hover { + background: linear-gradient(135deg, #d97706, #b45309) !important; +} + +/* Modal Medium Size */ +.modal-medium { + max-width: 600px; +} + +/* Impressum Content */ +.impressum-content { + text-align: left; + line-height: 1.6; +} + +.impressum-content h3 { + color: var(--primary); + margin-bottom: 1.5rem; + font-size: 1.4rem; +} + +.impressum-content h4 { + color: var(--gray-700); + margin-top: 1.5rem; + margin-bottom: 0.75rem; + font-size: 1.1rem; +} + +.impressum-content p { + margin-bottom: 1rem; + color: var(--gray-600); +} + +.impressum-content strong { + color: var(--gray-800); +} + +.impressum-content a { + color: var(--primary); + text-decoration: none; +} + +.impressum-content a:hover { + text-decoration: underline; +} + +.impressum-content hr { + border: none; + border-top: 1px solid var(--gray-200); + margin: 1.5rem 0; +} + +.impressum-content .copyright { + margin-top: 1.5rem; + padding-top: 1rem; + border-top: 1px solid var(--gray-200); + text-align: center; + font-size: 0.9rem; + color: var(--gray-500); +} + +/* Footer Links */ +.footer-links { + margin-top: 0.5rem; +} + +.footer-links a { + color: var(--gray-400); + text-decoration: none; + font-size: 0.85rem; + transition: color 0.2s; +} + +.footer-links a:hover { + color: white; + text-decoration: underline; +} + +/* No Results Message */ +.no-results { + grid-column: 1 / -1; + text-align: center; + padding: 3rem; + color: var(--gray-500); + font-size: 1rem; + line-height: 1.6; +} +/* ========== TEXT-GENERATOR v5 Additions ========== */ + +/* Reset Button im Header */ +.text-generator-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.text-generator-header .header-buttons { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.btn-reset { + background: #ef4444; + color: white; + border: none; + padding: 0.35rem 0.75rem; + border-radius: 6px; + font-size: 0.8rem; + cursor: pointer; + transition: background 0.2s; +} + +.btn-reset:hover { + background: #dc2626; +} + +/* Padding Row mit 4 Feldern */ +.padding-row { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; +} + +.padding-group { + flex: 1; + min-width: 120px; +} + +.padding-group label { + font-size: 0.75rem; + color: #6b7280; +} + +.padding-group input[type="range"] { + width: 100%; +} + +/* Standalone Arrow Section */ +.standalone-arrow-section { + margin-top: 1.5rem; + padding-top: 1.5rem; + border-top: 2px dashed #e5e7eb; +} + +.standalone-arrow-section .section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.standalone-arrow-section .section-header h4 { + margin: 0; + font-size: 1rem; + color: #374151; +} + +.standalone-arrow-section .section-hint { + font-size: 0.75rem; + color: #9ca3af; + font-style: italic; +} + +.standalone-arrow-content { + display: flex; + align-items: center; + gap: 1.5rem; + flex-wrap: wrap; +} + +.standalone-arrow-preview { + min-width: 150px; + min-height: 60px; + max-width: 200px; + display: flex; + align-items: center; + justify-content: center; + background: #f9fafb; + border: 1px solid #e5e7eb; + border-radius: 8px; + padding: 1rem; +} + +.standalone-arrow-preview svg { + max-width: 100%; + max-height: 50px; +} + +.standalone-arrow-content .preview-actions { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +/* Verbessertes Layout fuer Pfeil-Details */ +#arrowDetailsRow, +#arrowDetailsRow2 { + background: #f9fafb; + border-radius: 8px; + padding: 0.75rem; + margin: 0.5rem 0; +} + +/* Dark Mode Anpassungen */ +[data-theme="dark"] .btn-reset { + background: #dc2626; +} + +[data-theme="dark"] .btn-reset:hover { + background: #b91c1c; +} + +[data-theme="dark"] .standalone-arrow-section { + border-top-color: #3e3126; +} + +[data-theme="dark"] .standalone-arrow-preview { + background: #2a2319; + border-color: #3e3126; +} + +[data-theme="dark"] #arrowDetailsRow, +[data-theme="dark"] #arrowDetailsRow2 { + background: #2a2319; +} + +[data-theme="dark"] .padding-group label { + color: #9ca3af; +} + +[data-theme="dark"] .standalone-arrow-section .section-header h4 { + color: #f5f1e8; +} diff --git a/symbols/index.html b/symbols/index.html new file mode 100644 index 0000000..25f4322 --- /dev/null +++ b/symbols/index.html @@ -0,0 +1,385 @@ + + + + + + Gutachter Symbolbibliothek v2.2 + + + + +
+
+

Gutachter Symbolbibliothek

+

SVG & DXF Symbole fuer Schadensgutachten und Vermessung

+
+
+ + +
+
+ + +
+
+

Text-Symbol erstellen

+
+ + +
+
+
+ +
+
+ + +
+
+ + + 16px +
+
+ + +
+
+ +
+ + #000000 +
+
+
+ +
+ + #000000 +
+
+
+ + +
+
+ +
+ + + + + + +
+
+
+ + + + + +
+
+ + + 10px +
+
+ + +
+
+ + + 10px +
+
+ + + 10px +
+
+ + + 10px +
+
+ + + 10px +
+
+ + +
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+ + +
+
+ +
+ + + + + +
+
+
+ + + + + +
+
+ + + 10px +
+
+ + + 15px +
+
+ + +
+
Vorschau:
+
+ +
+
+ + + + + + + +
+
+ + +
+
+

Einzelner Pfeil (ohne Text)

+ Verwendet die gleichen Pfeil-Einstellungen +
+
+
+ +
+
+ + + + + + +
+
+
+
+
+ + +
+ + +
+ + | + Gutachten: + + + + + + + + + | + Vermessung: + + + + + + + + + | + Eigene: + +
+
+ + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + diff --git a/symbols/index2.html b/symbols/index2.html new file mode 100644 index 0000000..62588fe --- /dev/null +++ b/symbols/index2.html @@ -0,0 +1,385 @@ + + + + + + Gutachter Symbolbibliothek v2.2 + + + + +
+
+

Gutachter Symbolbibliothek

+

SVG & DXF Symbole fuer Schadensgutachten und Vermessung

+
+
+ + +
+
+ + +
+
+

Text-Symbol erstellen

+
+ + +
+
+
+ +
+
+ + +
+
+ + + 16px +
+
+ + +
+
+ +
+ + #000000 +
+
+
+ +
+ + #000000 +
+
+
+ + +
+
+ +
+ + + + + + +
+
+
+ + + + + + + + + + + +
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+ + +
+
+ +
+ + + + + +
+
+
+ + + + + + + + +
+
Vorschau:
+
+ +
+
+ + + + + + + +
+
+ + +
+
+

Einzelner Pfeil (ohne Text)

+ Verwendet die gleichen Pfeil-Einstellungen +
+
+
+ +
+
+ + + + + + +
+
+
+
+
+ + +
+ + +
+ + | + Gutachten: + + + + + + + + + | + Vermessung: + + + + + + + + + | + Eigene: + +
+
+ + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + diff --git a/symbols/index3.html b/symbols/index3.html new file mode 100644 index 0000000..425a949 --- /dev/null +++ b/symbols/index3.html @@ -0,0 +1,385 @@ + + + + + + Gutachter Symbolbibliothek v2.2 + + + + +
+
+

Gutachter Symbolbibliothek

+

SVG & DXF Symbole fuer Schadensgutachten und Vermessung

+
+
+ + +
+
+ + +
+
+

Text-Symbol erstellen

+
+ + +
+
+
+ +
+
+ + +
+
+ + + 16px +
+
+ + +
+
+ +
+ + #000000 +
+
+
+ +
+ + #000000 +
+
+
+ + +
+
+ +
+ + + + + + +
+
+
+ + + + + +
+
+ + + 10px +
+
+ + +
+
+ + + 10px +
+
+ + + 10px +
+
+ + + 10px +
+
+ + + 10px +
+
+ + +
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+ + +
+
+ +
+ + + + + +
+
+
+ + + + + +
+
+ + + 10px +
+
+ + + 15px +
+
+ + +
+
Vorschau:
+
+ +
+
+ + + + + + + +
+
+ + +
+
+

Einzelner Pfeil (ohne Text)

+ Verwendet die gleichen Pfeil-Einstellungen +
+
+
+ +
+
+ + + + + + +
+
+
+
+
+ + +
+ + +
+ + | + Gutachten: + + + + + + + + + | + Vermessung: + + + + + + + + + | + Eigene: + +
+
+ + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + diff --git a/symbols/index4.html b/symbols/index4.html new file mode 100644 index 0000000..97f6f15 --- /dev/null +++ b/symbols/index4.html @@ -0,0 +1,390 @@ + + + + + + Gutachter Symbolbibliothek v2.2 + + + + +
+
+

Gutachter Symbolbibliothek

+

SVG & DXF Symbole fuer Schadensgutachten und Vermessung

+
+
+ + +
+
+ + +
+
+

Text-Symbol erstellen

+
+ + +
+
+
+ +
+
+ + +
+
+ + + 16px +
+
+ + +
+
+ +
+ + #000000 +
+
+
+ +
+ + #000000 +
+
+
+ + +
+
+ +
+ + + + + + +
+
+
+ + + + + +
+
+ + + 10px +
+
+ + +
+
+ + + 10px +
+
+ + + 10px +
+
+ + + 10px +
+
+ + + 10px +
+
+ + +
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+ + +
+
+ +
+ + + + + +
+
+
+ + + + + +
+
+ + + 10px +
+
+ + + 15px +
+
+ + +
+
Vorschau:
+
+ +
+
+ + + + + + + +
+
+ + +
+
+

Einzelner Pfeil (ohne Text)

+ Verwendet die gleichen Pfeil-Einstellungen +
+
+
+ +
+
+ + + + + + +
+
+
+
+
+ + +
+ + +
+ + | + Gutachten: + + + + + + + + + | + Vermessung: + + + + + + + + + | + Eigene: + +
+
+ + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/symbols/js/app.js b/symbols/js/app.js new file mode 100644 index 0000000..ebc70dd --- /dev/null +++ b/symbols/js/app.js @@ -0,0 +1,1684 @@ +// ============================================ +// ANWENDUNGSLOGIK +// Gutachter Symbolbibliothek v2.0 +// ============================================ + +// ========== GLOBALE VARIABLEN ========== +let currentFilter = 'all'; +let currentSearch = ''; +let selectedSymbols = new Set(); +let legendItems = []; + +// ========== INITIALISIERUNG ========== +document.addEventListener('DOMContentLoaded', function() { + renderSymbols(); + setupEventListeners(); + loadLegendFromStorage(); +}); + +// ========== EVENT LISTENERS ========== +function setupEventListeners() { + // Suche + document.getElementById('searchInput').addEventListener('input', function(e) { + currentSearch = e.target.value.toLowerCase(); + renderSymbols(); + }); + + // Filter Pills + document.querySelectorAll('.filter-pill').forEach(pill => { + pill.addEventListener('click', function() { + document.querySelectorAll('.filter-pill').forEach(p => p.classList.remove('active')); + this.classList.add('active'); + currentFilter = this.dataset.filter; + renderSymbols(); + }); + }); + + // Modal schließen + document.getElementById('legendModal').addEventListener('click', function(e) { + if (e.target === this) { + closeLegendModal(); + } + }); + + // Escape-Taste zum Schließen + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape') { + closeLegendModal(); + } + }); +} + +// ========== SYMBOLE RENDERN ========== +function renderSymbols() { + const container = document.getElementById('symbolGrid'); + container.innerHTML = ''; + + let hasResults = false; + + Object.keys(SYMBOLS).forEach(categoryKey => { + const category = SYMBOLS[categoryKey]; + + // Filter nach Kategorie + if (currentFilter !== 'all' && currentFilter !== categoryKey) { + return; + } + + const filteredItems = category.items.filter(item => { + if (!currentSearch) return true; + return item.name.toLowerCase().includes(currentSearch) || + item.tags.some(tag => tag.toLowerCase().includes(currentSearch)); + }); + + if (filteredItems.length === 0) return; + + hasResults = true; + + // Kategorie-Header + const categoryHeader = document.createElement('div'); + categoryHeader.className = 'category-header'; + categoryHeader.innerHTML = `${category.icon} ${category.name}${filteredItems.length} Symbole`; + container.appendChild(categoryHeader); + + // Symbol-Grid für diese Kategorie + const categoryGrid = document.createElement('div'); + categoryGrid.className = 'category-grid'; + + filteredItems.forEach(item => { + const card = createSymbolCard(item, categoryKey); + categoryGrid.appendChild(card); + }); + + container.appendChild(categoryGrid); + }); + + if (!hasResults) { + container.innerHTML = '
Keine Symbole gefunden. Versuchen Sie einen anderen Suchbegriff.
'; + } +} + +// ========== SYMBOL-KARTE ERSTELLEN ========== +function createSymbolCard(item, categoryKey) { + const card = document.createElement('div'); + card.className = 'symbol-card'; + + // Spezielle Klasse für Vermessungssymbole + if (categoryKey.startsWith('vermessung_')) { + card.classList.add('vermessung'); + } + + const isSelected = selectedSymbols.has(item.id); + if (isSelected) { + card.classList.add('selected'); + } + + card.innerHTML = ` +
${item.svg}
+
${item.name}
+
+ + + + + + +
+ `; + + return card; +} + +// ========== SYMBOL FINDEN ========== +function findSymbol(id) { + for (const categoryKey of Object.keys(SYMBOLS)) { + const item = SYMBOLS[categoryKey].items.find(i => i.id === id); + if (item) return item; + } + return null; +} + +// ========== BILD KOPIEREN (transparent) ========== +async function copyAsImage(id) { + const item = findSymbol(id); + if (!item) return; + + try { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + const size = 256; + canvas.width = size; + canvas.height = size; + + // Transparenter Hintergrund (kein fillRect) + // ctx.fillStyle = 'white'; // Entfernt für Transparenz + // ctx.fillRect(0, 0, size, size); // Entfernt für Transparenz + + const img = new Image(); + const svgBlob = new Blob([item.svg], { type: 'image/svg+xml;charset=utf-8' }); + const url = URL.createObjectURL(svgBlob); + + await new Promise((resolve, reject) => { + img.onload = resolve; + img.onerror = reject; + img.src = url; + }); + + ctx.drawImage(img, 0, 0, size, size); + URL.revokeObjectURL(url); + + canvas.toBlob(async (blob) => { + try { + await navigator.clipboard.write([ + new ClipboardItem({ 'image/png': blob }) + ]); + showNotification('Bild in Zwischenablage kopiert!'); + } catch (err) { + // Fallback: Download + const link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = item.filename.replace('.svg', '.png'); + link.click(); + showNotification('PNG heruntergeladen (Kopieren nicht unterstützt)'); + } + }, 'image/png'); + } catch (err) { + console.error('Fehler beim Bild-Export:', err); + showNotification('Fehler beim Kopieren', 'error'); + } +} + +// ========== SVG DOWNLOAD ========== +function downloadSVG(id) { + const item = findSymbol(id); + if (!item) return; + + const blob = new Blob([item.svg], { type: 'image/svg+xml' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = item.filename; + link.click(); + URL.revokeObjectURL(url); + showNotification('SVG heruntergeladen!'); +} + +// ========== DXF EXPORT (AutoCAD R12 kompatibel) ========== +function svgToDxf(svgString, scaleFactor = 1) { + const parser = new DOMParser(); + const svg = parser.parseFromString(svgString, 'image/svg+xml').documentElement; + + const viewBox = svg.getAttribute('viewBox')?.split(' ').map(Number) || [0, 0, 64, 64]; + const height = viewBox[3]; + + let entities = ''; + + function flipY(y) { + return (height - y) * scaleFactor; + } + + function scaleX(x) { + return x * scaleFactor; + } + + function processElement(el) { + const tag = el.tagName?.toLowerCase(); + if (!tag) return; + + switch(tag) { + case 'line': + const x1 = parseFloat(el.getAttribute('x1') || 0); + const y1 = parseFloat(el.getAttribute('y1') || 0); + const x2 = parseFloat(el.getAttribute('x2') || 0); + const y2 = parseFloat(el.getAttribute('y2') || 0); + entities += createDxfLine(scaleX(x1), flipY(y1), scaleX(x2), flipY(y2)); + break; + + case 'rect': + const rx = parseFloat(el.getAttribute('x') || 0); + const ry = parseFloat(el.getAttribute('y') || 0); + const rw = parseFloat(el.getAttribute('width') || 0); + const rh = parseFloat(el.getAttribute('height') || 0); + // Rechteck als 4 Linien + entities += createDxfLine(scaleX(rx), flipY(ry), scaleX(rx + rw), flipY(ry)); + entities += createDxfLine(scaleX(rx + rw), flipY(ry), scaleX(rx + rw), flipY(ry + rh)); + entities += createDxfLine(scaleX(rx + rw), flipY(ry + rh), scaleX(rx), flipY(ry + rh)); + entities += createDxfLine(scaleX(rx), flipY(ry + rh), scaleX(rx), flipY(ry)); + break; + + case 'circle': + const cx = parseFloat(el.getAttribute('cx') || 0); + const cy = parseFloat(el.getAttribute('cy') || 0); + const r = parseFloat(el.getAttribute('r') || 0); + entities += createDxfCircle(scaleX(cx), flipY(cy), r * scaleFactor); + break; + + case 'ellipse': + const ecx = parseFloat(el.getAttribute('cx') || 0); + const ecy = parseFloat(el.getAttribute('cy') || 0); + const erx = parseFloat(el.getAttribute('rx') || 0); + const ery = parseFloat(el.getAttribute('ry') || 0); + // Ellipse als Kreis approximieren (Durchschnitt) + entities += createDxfCircle(scaleX(ecx), flipY(ecy), ((erx + ery) / 2) * scaleFactor); + break; + + case 'polygon': + case 'polyline': + const points = el.getAttribute('points'); + if (points) { + const pts = points.trim().split(/[\s,]+/).map(Number); + for (let i = 0; i < pts.length - 2; i += 2) { + entities += createDxfLine( + scaleX(pts[i]), flipY(pts[i+1]), + scaleX(pts[i+2]), flipY(pts[i+3]) + ); + } + // Polygon schließen + if (tag === 'polygon' && pts.length >= 4) { + entities += createDxfLine( + scaleX(pts[pts.length-2]), flipY(pts[pts.length-1]), + scaleX(pts[0]), flipY(pts[1]) + ); + } + } + break; + + case 'path': + const d = el.getAttribute('d'); + if (d) { + const pathEntities = parseSvgPath(d, scaleX, flipY); + entities += pathEntities; + } + break; + + case 'text': + const tx = parseFloat(el.getAttribute('x') || 0); + const ty = parseFloat(el.getAttribute('y') || 0); + const textContent = el.textContent || ''; + const fontSize = parseFloat(el.getAttribute('font-size') || 10); + entities += createDxfText(scaleX(tx), flipY(ty), textContent, fontSize * scaleFactor * 0.7); + break; + + case 'g': + case 'svg': + Array.from(el.children).forEach(child => processElement(child)); + break; + } + } + + processElement(svg); + + // DXF mit AutoCAD R12 Format (AC1009) - CRLF Zeilenenden + const dxf = [ + '0', 'SECTION', + '2', 'HEADER', + '9', '$ACADVER', + '1', 'AC1009', + '9', '$INSBASE', + '10', '0.0', + '20', '0.0', + '30', '0.0', + '9', '$EXTMIN', + '10', '0.0', + '20', '0.0', + '30', '0.0', + '9', '$EXTMAX', + '10', String(height * scaleFactor), + '20', String(height * scaleFactor), + '30', '0.0', + '0', 'ENDSEC', + '0', 'SECTION', + '2', 'TABLES', + '0', 'TABLE', + '2', 'LAYER', + '70', '1', + '0', 'LAYER', + '2', '0', + '70', '0', + '62', '7', + '6', 'CONTINUOUS', + '0', 'ENDTAB', + '0', 'ENDSEC', + '0', 'SECTION', + '2', 'ENTITIES', + entities, + '0', 'ENDSEC', + '0', 'EOF' + ].join('\r\n'); + + return dxf; +} + +function createDxfLine(x1, y1, x2, y2) { + return [ + '0', 'LINE', + '8', '0', + '10', x1.toFixed(4), + '20', y1.toFixed(4), + '30', '0.0', + '11', x2.toFixed(4), + '21', y2.toFixed(4), + '31', '0.0', + '' + ].join('\r\n'); +} + +function createDxfCircle(cx, cy, r) { + return [ + '0', 'CIRCLE', + '8', '0', + '10', cx.toFixed(4), + '20', cy.toFixed(4), + '30', '0.0', + '40', r.toFixed(4), + '' + ].join('\r\n'); +} + +function createDxfText(x, y, text, height) { + return [ + '0', 'TEXT', + '8', '0', + '10', x.toFixed(4), + '20', y.toFixed(4), + '30', '0.0', + '40', height.toFixed(4), + '1', text, + '' + ].join('\r\n'); +} + +function downloadDXF(id) { + const item = findSymbol(id); + if (!item) return; + + const dxf = svgToDxf(item.dxfSvg || item.svg, 1); + const blob = new Blob([dxf], { type: 'application/dxf' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = item.filename.replace('.svg', '.dxf'); + link.click(); + URL.revokeObjectURL(url); + showNotification('DXF heruntergeladen!'); +} + +// ========== LEGENDE FUNKTIONEN ========== +function toggleLegendSelection(id) { + if (selectedSymbols.has(id)) { + selectedSymbols.delete(id); + } else { + selectedSymbols.add(id); + + // Zur Legende hinzufügen + const item = findSymbol(id); + if (item && !legendItems.find(l => l.id === id)) { + legendItems.push({ + id: item.id, + name: item.name, + svg: item.svg, + description: '' + }); + } + } + + renderSymbols(); + updateLegendCount(); + saveLegendToStorage(); +} + +function updateLegendCount() { + const countEl = document.getElementById('legendCount'); + if (countEl) { + countEl.textContent = legendItems.length; + } +} + +function openLegendModal() { + const modal = document.getElementById('legendModal'); + modal.classList.add('active'); + renderLegendEditor(); +} + +function closeLegendModal() { + const modal = document.getElementById('legendModal'); + modal.classList.remove('active'); +} + +function renderLegendEditor() { + const container = document.getElementById('legendItems'); + + if (legendItems.length === 0) { + container.innerHTML = '
Keine Symbole in der Legende. Klicken Sie auf 📑 bei einem Symbol, um es hinzuzufügen.
'; + return; + } + + container.innerHTML = legendItems.map((item, index) => ` +
+
${item.svg}
+
+ + +
+
+ + + +
+
+ `).join(''); +} + +function updateLegendItem(index, field, value) { + if (legendItems[index]) { + legendItems[index][field] = value; + saveLegendToStorage(); + } +} + +function moveLegendItem(index, direction) { + const newIndex = index + direction; + if (newIndex >= 0 && newIndex < legendItems.length) { + const temp = legendItems[index]; + legendItems[index] = legendItems[newIndex]; + legendItems[newIndex] = temp; + renderLegendEditor(); + saveLegendToStorage(); + } +} + +function removeLegendItem(index) { + const item = legendItems[index]; + if (item) { + selectedSymbols.delete(item.id); + } + legendItems.splice(index, 1); + renderLegendEditor(); + renderSymbols(); + updateLegendCount(); + saveLegendToStorage(); +} + +function clearLegend() { + if (confirm('Möchten Sie die Legende wirklich leeren?')) { + legendItems = []; + selectedSymbols.clear(); + renderLegendEditor(); + renderSymbols(); + updateLegendCount(); + saveLegendToStorage(); + } +} + +// ========== LEGENDE SPEICHERN/LADEN ========== +function saveLegendToStorage() { + try { + localStorage.setItem('gutachter_legende', JSON.stringify(legendItems)); + localStorage.setItem('gutachter_selected', JSON.stringify([...selectedSymbols])); + } catch (e) { + console.warn('LocalStorage nicht verfügbar'); + } +} + +function loadLegendFromStorage() { + try { + const saved = localStorage.getItem('gutachter_legende'); + const savedSelected = localStorage.getItem('gutachter_selected'); + + if (saved) { + legendItems = JSON.parse(saved); + } + if (savedSelected) { + selectedSymbols = new Set(JSON.parse(savedSelected)); + } + + updateLegendCount(); + } catch (e) { + console.warn('Fehler beim Laden der Legende'); + } +} + +// ========== LEGENDE EXPORTIEREN ========== +function exportLegendSVG() { + if (legendItems.length === 0) { + showNotification('Legende ist leer', 'error'); + return; + } + + const itemHeight = 50; + const width = 400; + const height = legendItems.length * itemHeight + 60; + + let svg = ` + + Legende + `; + + legendItems.forEach((item, index) => { + const y = 60 + index * itemHeight; + svg += ` + ${item.svg.replace(/]*>/, '').replace('', '')} + ${item.name} + ${item.description ? `${item.description}` : ''} + `; + }); + + svg += ''; + + const blob = new Blob([svg], { type: 'image/svg+xml' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = 'legende.svg'; + link.click(); + URL.revokeObjectURL(url); + showNotification('Legende als SVG exportiert!'); +} + +function exportLegendPNG() { + if (legendItems.length === 0) { + showNotification('Legende ist leer', 'error'); + return; + } + + const itemHeight = 50; + const width = 400; + const height = legendItems.length * itemHeight + 60; + + const canvas = document.createElement('canvas'); + canvas.width = width * 2; + canvas.height = height * 2; + const ctx = canvas.getContext('2d'); + ctx.scale(2, 2); + + // Hintergrund + // ctx.fillStyle = 'white'; // Entfernt für Transparenz + ctx.fillRect(0, 0, width, height); + + // Titel + ctx.fillStyle = '#000'; + ctx.font = 'bold 18px Arial'; + ctx.fillText('Legende', 20, 30); + + // Linie + ctx.strokeStyle = '#ccc'; + ctx.beginPath(); + ctx.moveTo(20, 40); + ctx.lineTo(width - 20, 40); + ctx.stroke(); + + // Symbole laden und zeichnen + let loadedCount = 0; + + legendItems.forEach((item, index) => { + const y = 60 + index * itemHeight; + const img = new Image(); + const svgBlob = new Blob([item.svg], { type: 'image/svg+xml' }); + img.src = URL.createObjectURL(svgBlob); + + img.onload = () => { + ctx.drawImage(img, 20, y - 5, 32, 32); + + ctx.fillStyle = '#000'; + ctx.font = 'bold 14px Arial'; + ctx.fillText(item.name, 60, y + 15); + + if (item.description) { + ctx.fillStyle = '#666'; + ctx.font = '11px Arial'; + ctx.fillText(item.description, 60, y + 30); + } + + loadedCount++; + if (loadedCount === legendItems.length) { + canvas.toBlob(blob => { + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = 'legende.png'; + link.click(); + URL.revokeObjectURL(url); + showNotification('Legende als PNG exportiert!'); + }, 'image/png'); + } + }; + }); +} + +// ========== ZIP EXPORT ========== +async function downloadAllAsZip() { + showNotification('ZIP wird erstellt...', 'info'); + + // Simple ZIP ohne externe Bibliothek + const files = []; + + Object.keys(SYMBOLS).forEach(categoryKey => { + const category = SYMBOLS[categoryKey]; + category.items.forEach(item => { + files.push({ + name: `${categoryKey}/${item.filename}`, + content: item.svg + }); + files.push({ + name: `${categoryKey}/${item.filename.replace('.svg', '.dxf')}`, + content: svgToDxf(item.svg, 1) + }); + }); + }); + + // Da wir keine ZIP-Bibliothek haben, erstellen wir einen Download-Dialog + const info = `Symbolbibliothek enthält ${files.length / 2} Symbole in ${Object.keys(SYMBOLS).length} Kategorien.\n\n` + + `Für den ZIP-Download empfehlen wir, die Symbole einzeln herunterzuladen oder ` + + `das Projekt lokal mit einer ZIP-Bibliothek zu erweitern.`; + + alert(info); + showNotification('ZIP-Export: Siehe Hinweis', 'info'); +} + +// ========== BENACHRICHTIGUNGEN ========== +function showNotification(message, type = 'success') { + // Bestehende Notification entfernen + const existing = document.querySelector('.notification'); + if (existing) existing.remove(); + + const notification = document.createElement('div'); + notification.className = `notification ${type}`; + notification.textContent = message; + document.body.appendChild(notification); + + setTimeout(() => { + notification.classList.add('show'); + }, 10); + + setTimeout(() => { + notification.classList.remove('show'); + setTimeout(() => notification.remove(), 300); + }, 2500); +} + +// ========== SVG PATH PARSER FÜR DXF ========== +function parseSvgPath(d, scaleX, flipY) { + let entities = ""; + let currentX = 0, currentY = 0; + let startX = 0, startY = 0; + + // Tokenize path data + const commands = d.match(/[MmLlHhVvCcSsQqTtAaZz][^MmLlHhVvCcSsQqTtAaZz]*/g) || []; + + commands.forEach(cmd => { + const type = cmd[0]; + const args = cmd.slice(1).trim().split(/[\s,]+/).filter(s => s).map(Number); + + switch(type) { + case "M": // Absolute moveto + currentX = args[0]; currentY = args[1]; + startX = currentX; startY = currentY; + // Weitere Punkt-Paare sind implizite LineTo + for(let i = 2; i < args.length; i += 2) { + entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(args[i]), flipY(args[i+1])); + currentX = args[i]; currentY = args[i+1]; + } + break; + case "m": // Relative moveto + currentX += args[0]; currentY += args[1]; + startX = currentX; startY = currentY; + for(let i = 2; i < args.length; i += 2) { + const nx = currentX + args[i], ny = currentY + args[i+1]; + entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(nx), flipY(ny)); + currentX = nx; currentY = ny; + } + break; + case "L": // Absolute lineto + for(let i = 0; i < args.length; i += 2) { + entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(args[i]), flipY(args[i+1])); + currentX = args[i]; currentY = args[i+1]; + } + break; + case "l": // Relative lineto + for(let i = 0; i < args.length; i += 2) { + const nx = currentX + args[i], ny = currentY + args[i+1]; + entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(nx), flipY(ny)); + currentX = nx; currentY = ny; + } + break; + case "H": // Absolute horizontal + for(let i = 0; i < args.length; i++) { + entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(args[i]), flipY(currentY)); + currentX = args[i]; + } + break; + case "h": // Relative horizontal + for(let i = 0; i < args.length; i++) { + const nx = currentX + args[i]; + entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(nx), flipY(currentY)); + currentX = nx; + } + break; + case "V": // Absolute vertical + for(let i = 0; i < args.length; i++) { + entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(currentX), flipY(args[i])); + currentY = args[i]; + } + break; + case "v": // Relative vertical + for(let i = 0; i < args.length; i++) { + const ny = currentY + args[i]; + entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(currentX), flipY(ny)); + currentY = ny; + } + break; + case "Q": // Quadratic Bezier (approximiert als Linie zum Endpunkt) + case "q": + for(let i = 0; i < args.length; i += 4) { + let ex, ey; + if(type === "Q") { + ex = args[i+2]; ey = args[i+3]; + } else { + ex = currentX + args[i+2]; ey = currentY + args[i+3]; + } + // Quadratische Bezier als Polyline approximieren + const cx = type === "Q" ? args[i] : currentX + args[i]; + const cy = type === "Q" ? args[i+1] : currentY + args[i+1]; + for(let t = 0; t <= 1; t += 0.25) { + const t2 = Math.min(t + 0.25, 1); + const x1 = (1-t)*(1-t)*currentX + 2*(1-t)*t*cx + t*t*ex; + const y1 = (1-t)*(1-t)*currentY + 2*(1-t)*t*cy + t*t*ey; + const x2 = (1-t2)*(1-t2)*currentX + 2*(1-t2)*t2*cx + t2*t2*ex; + const y2 = (1-t2)*(1-t2)*currentY + 2*(1-t2)*t2*cy + t2*t2*ey; + entities += createDxfLine(scaleX(x1), flipY(y1), scaleX(x2), flipY(y2)); + } + currentX = ex; currentY = ey; + } + break; + case "C": // Cubic Bezier + case "c": + for(let i = 0; i < args.length; i += 6) { + let c1x, c1y, c2x, c2y, ex, ey; + if(type === "C") { + c1x = args[i]; c1y = args[i+1]; + c2x = args[i+2]; c2y = args[i+3]; + ex = args[i+4]; ey = args[i+5]; + } else { + c1x = currentX + args[i]; c1y = currentY + args[i+1]; + c2x = currentX + args[i+2]; c2y = currentY + args[i+3]; + ex = currentX + args[i+4]; ey = currentY + args[i+5]; + } + // Kubische Bezier als Polyline approximieren + for(let t = 0; t <= 1; t += 0.2) { + const t2 = Math.min(t + 0.2, 1); + const x1 = Math.pow(1-t,3)*currentX + 3*Math.pow(1-t,2)*t*c1x + 3*(1-t)*t*t*c2x + t*t*t*ex; + const y1 = Math.pow(1-t,3)*currentY + 3*Math.pow(1-t,2)*t*c1y + 3*(1-t)*t*t*c2y + t*t*t*ey; + const x2 = Math.pow(1-t2,3)*currentX + 3*Math.pow(1-t2,2)*t2*c1x + 3*(1-t2)*t2*t2*c2x + t2*t2*t2*ex; + const y2 = Math.pow(1-t2,3)*currentY + 3*Math.pow(1-t2,2)*t2*c1y + 3*(1-t2)*t2*t2*c2y + t2*t2*t2*ey; + entities += createDxfLine(scaleX(x1), flipY(y1), scaleX(x2), flipY(y2)); + } + currentX = ex; currentY = ey; + } + break; + case "Z": + case "z": // Close path + if(currentX !== startX || currentY !== startY) { + entities += createDxfLine(scaleX(currentX), flipY(currentY), scaleX(startX), flipY(startY)); + } + currentX = startX; currentY = startY; + break; + } + }); + + return entities; +} + + +// ========== TEXT-GENERATOR v4 ========== +// Features: Farben, Skalierung, abknickbare Pfeile (korrigiert), Pfeilspitzengroesse, PNG/JPG, Speichern + +var textGenState = { + text: '', + fontSize: 16, + textColor: '#000000', + frameColor: '#000000', + shape: 'none', + frameScale: 100, + framePadding: 10, + lineStyle: 'solid', + lineWeight: 2, + arrow: 'none', + arrowLength: 40, + arrowAngle: 0, + arrowBend: 50, + arrowSize: 10 +}; + +// Custom Symbols aus localStorage +var customSymbols = []; + +function initTextGenerator() { + // Load custom symbols + loadCustomSymbols(); + + var textInput = document.getElementById('customText'); + var fontSizeInput = document.getElementById('fontSize'); + var fontSizeValue = document.getElementById('fontSizeValue'); + var textColorInput = document.getElementById('textColor'); + var textColorValue = document.getElementById('textColorValue'); + var frameColorInput = document.getElementById('frameColor'); + var frameColorValue = document.getElementById('frameColorValue'); + var frameScaleInput = document.getElementById('frameScale'); + var frameScaleValue = document.getElementById('frameScaleValue'); + var framePaddingInput = document.getElementById('framePadding'); + var framePaddingValue = document.getElementById('framePaddingValue'); + var arrowLengthInput = document.getElementById('arrowLength'); + var arrowLengthValue = document.getElementById('arrowLengthValue'); + var arrowAngleInput = document.getElementById('arrowAngle'); + var arrowAngleValue = document.getElementById('arrowAngleValue'); + var arrowBendInput = document.getElementById('arrowBend'); + var arrowBendValue = document.getElementById('arrowBendValue'); + var arrowSizeInput = document.getElementById('arrowSize'); + var arrowSizeValue = document.getElementById('arrowSizeValue'); + + // Text input + if (textInput) { + textInput.addEventListener('input', function(e) { + textGenState.text = e.target.value; + updateTextPreview(); + }); + } + + // Font size + if (fontSizeInput) { + fontSizeInput.addEventListener('input', function(e) { + textGenState.fontSize = parseInt(e.target.value); + fontSizeValue.textContent = e.target.value + 'px'; + updateTextPreview(); + }); + } + + // Text color + if (textColorInput) { + textColorInput.addEventListener('input', function(e) { + textGenState.textColor = e.target.value; + textColorValue.textContent = e.target.value; + updateTextPreview(); + }); + } + + // Frame color + if (frameColorInput) { + frameColorInput.addEventListener('input', function(e) { + textGenState.frameColor = e.target.value; + frameColorValue.textContent = e.target.value; + updateTextPreview(); + }); + } + + // Frame scale + if (frameScaleInput) { + frameScaleInput.addEventListener('input', function(e) { + textGenState.frameScale = parseInt(e.target.value); + frameScaleValue.textContent = e.target.value + '%'; + updateTextPreview(); + }); + } + + // Frame padding + if (framePaddingInput) { + framePaddingInput.addEventListener('input', function(e) { + textGenState.framePadding = parseInt(e.target.value); + framePaddingValue.textContent = e.target.value + 'px'; + updateTextPreview(); + }); + } + + // Arrow length + if (arrowLengthInput) { + arrowLengthInput.addEventListener('input', function(e) { + textGenState.arrowLength = parseInt(e.target.value); + arrowLengthValue.textContent = e.target.value + 'px'; + updateTextPreview(); + }); + } + + // Arrow angle + if (arrowAngleInput) { + arrowAngleInput.addEventListener('input', function(e) { + textGenState.arrowAngle = parseInt(e.target.value); + arrowAngleValue.textContent = e.target.value + '\u00B0'; + updateTextPreview(); + }); + } + + // Arrow bend position + if (arrowBendInput) { + arrowBendInput.addEventListener('input', function(e) { + textGenState.arrowBend = parseInt(e.target.value); + arrowBendValue.textContent = e.target.value + '%'; + updateTextPreview(); + }); + } + + // Arrow size + if (arrowSizeInput) { + arrowSizeInput.addEventListener('input', function(e) { + textGenState.arrowSize = parseInt(e.target.value); + arrowSizeValue.textContent = e.target.value + 'px'; + updateTextPreview(); + }); + } + + // Shape buttons + document.querySelectorAll('.shape-btn').forEach(function(btn) { + btn.addEventListener('click', function() { + document.querySelectorAll('.shape-btn').forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + textGenState.shape = btn.dataset.shape; + updateFrameScaleVisibility(); + updateTextPreview(); + }); + }); + + // Line style buttons + document.querySelectorAll('.line-btn').forEach(function(btn) { + btn.addEventListener('click', function() { + document.querySelectorAll('.line-btn').forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + textGenState.lineStyle = btn.dataset.style; + updateTextPreview(); + }); + }); + + // Line weight buttons + document.querySelectorAll('.weight-btn').forEach(function(btn) { + btn.addEventListener('click', function() { + document.querySelectorAll('.weight-btn').forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + textGenState.lineWeight = parseInt(btn.dataset.weight); + updateTextPreview(); + }); + }); + + // Arrow buttons + document.querySelectorAll('.arrow-btn').forEach(function(btn) { + btn.addEventListener('click', function() { + document.querySelectorAll('.arrow-btn').forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + textGenState.arrow = btn.dataset.arrow; + updateArrowDetailsVisibility(); + updateTextPreview(); + }); + }); + + updateFrameScaleVisibility(); + updateArrowDetailsVisibility(); + updateTextPreview(); +} + +function updateFrameScaleVisibility() { + var row = document.getElementById('frameScaleRow'); + if (row) { + row.style.display = textGenState.shape === 'none' ? 'none' : 'flex'; + } +} + +function updateArrowDetailsVisibility() { + var row = document.getElementById('arrowDetailsRow'); + if (row) { + row.style.display = textGenState.arrow === 'none' ? 'none' : 'flex'; + } +} + +function toggleTextGenerator() { + var generator = document.querySelector('.text-generator'); + generator.classList.toggle('collapsed'); +} + +function getStrokeDasharray(style) { + switch(style) { + case 'dashed': return '8,4'; + case 'dotted': return '2,3'; + default: return 'none'; + } +} + +function generateTextSVG() { + var text = textGenState.text || 'Text'; + var fontSize = textGenState.fontSize; + var textColor = textGenState.textColor; + var frameColor = textGenState.frameColor; + var shape = textGenState.shape; + var frameScale = textGenState.frameScale / 100; + var padding = textGenState.framePadding; + var lineStyle = textGenState.lineStyle; + var lineWeight = textGenState.lineWeight; + var arrow = textGenState.arrow; + var arrowLength = textGenState.arrowLength; + var arrowAngle = textGenState.arrowAngle; + var arrowBend = textGenState.arrowBend / 100; + var arrowSize = textGenState.arrowSize; + + // Zeilen aufteilen + var lines = text.split('\n'); + if (lines.length === 0) lines = ['Text']; + + // Berechne die laengste Zeile + var maxLineLength = 0; + for (var i = 0; i < lines.length; i++) { + if (lines[i].length > maxLineLength) { + maxLineLength = lines[i].length; + } + } + if (maxLineLength === 0) maxLineLength = 4; + + // Dimensionen berechnen + var charWidth = fontSize * 0.6; + var lineHeight = fontSize * 1.3; + var textWidth = maxLineLength * charWidth; + var textHeight = lines.length * lineHeight; + + // Basis-SVG-Groesse (Text + Padding) + var baseWidth = textWidth + (padding * 2); + var baseHeight = textHeight + (padding * 2); + + // Rahmen mit Skalierung + var frameWidth = baseWidth * frameScale; + var frameHeight = baseHeight * frameScale; + + // Mindestgroesse + if (frameWidth < 40) frameWidth = 40; + if (frameHeight < 30) frameHeight = 30; + + // Pfeil-Raum berechnen (inkl. Pfeilspitze) + var arrowSpace = (arrow !== 'none') ? arrowLength + arrowSize + 5 : 0; + + // SVG-Groesse mit Pfeil-Raum + var svgWidth = frameWidth; + var svgHeight = frameHeight; + var offsetX = 0; + var offsetY = 0; + + if (arrow === 'left') { + svgWidth += arrowSpace; + offsetX = arrowSpace; + } else if (arrow === 'right') { + svgWidth += arrowSpace; + } else if (arrow === 'top') { + svgHeight += arrowSpace; + offsetY = arrowSpace; + } else if (arrow === 'bottom') { + svgHeight += arrowSpace; + } + + var cx = offsetX + frameWidth / 2; + var cy = offsetY + frameHeight / 2; + + var dashArray = getStrokeDasharray(lineStyle); + var dashAttr = dashArray !== 'none' ? ' stroke-dasharray="' + dashArray + '"' : ''; + + var shapeSvg = ''; + var shapeX = offsetX + lineWeight / 2; + var shapeY = offsetY + lineWeight / 2; + var shapeW = frameWidth - lineWeight; + var shapeH = frameHeight - lineWeight; + + switch(shape) { + case 'rect': + shapeSvg = ''; + break; + case 'square': + var squareSize = Math.max(shapeW, shapeH); + var sqX = cx - squareSize / 2; + var sqY = cy - squareSize / 2; + shapeSvg = ''; + break; + case 'circle': + var radius = Math.max(shapeW, shapeH) / 2; + shapeSvg = ''; + break; + case 'oval': + var rx = shapeW / 2; + var ry = shapeH / 2; + shapeSvg = ''; + break; + case 'diamond': + var halfW = shapeW / 2; + var halfH = shapeH / 2; + shapeSvg = ''; + break; + } + + // Pfeil generieren + var arrowSvg = ''; + if (arrow !== 'none') { + arrowSvg = generateArrowPath(arrow, cx, cy, frameWidth, frameHeight, offsetX, offsetY, arrowLength, arrowAngle, arrowBend, frameColor, lineWeight, dashAttr, arrowSize); + } + + // Text-Elemente erzeugen + var textSvg = ''; + + var totalTextHeight = lines.length * lineHeight; + var startY = cy - (totalTextHeight / 2) + (fontSize * 0.8); + + for (var j = 0; j < lines.length; j++) { + var lineY = startY + (j * lineHeight); + var lineText = lines[j] || ' '; + lineText = lineText.replace(/&/g, '&').replace(//g, '>'); + textSvg += '' + lineText + ''; + } + textSvg += ''; + + return '' + shapeSvg + arrowSvg + textSvg + ''; +} + +function generateArrowPath(direction, cx, cy, frameWidth, frameHeight, offsetX, offsetY, length, angle, bendPos, color, weight, dashAttr, arrowSize) { + var startX, startY, midX, midY, endX, endY; + var angleRad = angle * Math.PI / 180; + + // Startpunkt am Rahmen + switch(direction) { + case 'top': + startX = cx; + startY = offsetY; + // Erster Teil geht gerade nach oben bis zum Knickpunkt + midX = startX; + midY = startY - length * bendPos; + // Zweiter Teil knickt ab + endX = midX + Math.sin(angleRad) * (length * (1 - bendPos)); + endY = midY - Math.cos(angleRad) * (length * (1 - bendPos)); + break; + case 'bottom': + startX = cx; + startY = offsetY + frameHeight; + midX = startX; + midY = startY + length * bendPos; + endX = midX + Math.sin(angleRad) * (length * (1 - bendPos)); + endY = midY + Math.cos(angleRad) * (length * (1 - bendPos)); + break; + case 'left': + startX = offsetX; + startY = cy; + midX = startX - length * bendPos; + midY = startY; + endX = midX - Math.cos(angleRad) * (length * (1 - bendPos)); + endY = midY + Math.sin(angleRad) * (length * (1 - bendPos)); + break; + case 'right': + startX = offsetX + frameWidth; + startY = cy; + midX = startX + length * bendPos; + midY = startY; + endX = midX + Math.cos(angleRad) * (length * (1 - bendPos)); + endY = midY + Math.sin(angleRad) * (length * (1 - bendPos)); + break; + } + + // Pfad erstellen + var pathD; + if (angle !== 0 && bendPos > 0 && bendPos < 1) { + // Mit Knick + pathD = 'M ' + startX + ' ' + startY + ' L ' + midX + ' ' + midY + ' L ' + endX + ' ' + endY; + } else { + // Ohne Knick - gerade Linie + pathD = 'M ' + startX + ' ' + startY + ' L ' + endX + ' ' + endY; + } + + // Pfeilspitze: Richtung basiert auf dem LETZTEN Segment + var lastSegmentStartX, lastSegmentStartY; + if (angle !== 0 && bendPos > 0 && bendPos < 1) { + // Letztes Segment ist von mid zu end + lastSegmentStartX = midX; + lastSegmentStartY = midY; + } else { + // Letztes Segment ist von start zu end + lastSegmentStartX = startX; + lastSegmentStartY = startY; + } + + // Winkel des letzten Segments berechnen + var arrowHeadAngle = Math.atan2(endY - lastSegmentStartY, endX - lastSegmentStartX); + + // Pfeilspitze Punkte + var ah1x = endX - arrowSize * Math.cos(arrowHeadAngle - Math.PI / 6); + var ah1y = endY - arrowSize * Math.sin(arrowHeadAngle - Math.PI / 6); + var ah2x = endX - arrowSize * Math.cos(arrowHeadAngle + Math.PI / 6); + var ah2y = endY - arrowSize * Math.sin(arrowHeadAngle + Math.PI / 6); + + var arrowHead = ''; + + return '' + arrowHead; +} + +function updateTextPreview() { + var preview = document.getElementById('textPreview'); + if (preview) { + preview.innerHTML = generateTextSVG(); + } +} + +// Hilfsfunktion: SVG zu Canvas rendern +async function svgToCanvas(svg, scale) { + var parser = new DOMParser(); + var svgDoc = parser.parseFromString(svg, 'image/svg+xml'); + var svgEl = svgDoc.documentElement; + var svgWidth = parseFloat(svgEl.getAttribute('width')) || 100; + var svgHeight = parseFloat(svgEl.getAttribute('height')) || 100; + + if (!scale) { + scale = Math.max(200 / Math.min(svgWidth, svgHeight), 1); + scale = Math.min(scale, 512 / Math.max(svgWidth, svgHeight)); + } + + var canvasWidth = Math.round(svgWidth * scale); + var canvasHeight = Math.round(svgHeight * scale); + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + canvas.width = canvasWidth; + canvas.height = canvasHeight; + + var img = new Image(); + var svgBlob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' }); + var url = URL.createObjectURL(svgBlob); + + await new Promise(function(resolve, reject) { + img.onload = resolve; + img.onerror = reject; + img.src = url; + }); + + ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight); + URL.revokeObjectURL(url); + + return canvas; +} + +async function copyTextAsImage() { + var svg = generateTextSVG(); + + try { + var canvas = await svgToCanvas(svg); + + canvas.toBlob(async function(blob) { + try { + await navigator.clipboard.write([ + new ClipboardItem({ 'image/png': blob }) + ]); + showNotification('In Zwischenablage kopiert!'); + } catch (err) { + var link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = 'text-symbol.png'; + link.click(); + showNotification('PNG heruntergeladen'); + } + }, 'image/png'); + } catch (err) { + console.error('Fehler:', err); + showNotification('Fehler beim Kopieren', 'error'); + } +} + +function downloadTextSVG() { + var svg = generateTextSVG(); + var blob = new Blob([svg], { type: 'image/svg+xml' }); + var url = URL.createObjectURL(blob); + var link = document.createElement('a'); + link.href = url; + var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol'; + link.download = 'text-' + filename + '.svg'; + link.click(); + URL.revokeObjectURL(url); + showNotification('SVG heruntergeladen!'); +} + +async function downloadTextPNG() { + var svg = generateTextSVG(); + try { + var canvas = await svgToCanvas(svg, 2); // 2x Skalierung fuer bessere Qualitaet + + canvas.toBlob(function(blob) { + var link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol'; + link.download = 'text-' + filename + '.png'; + link.click(); + URL.revokeObjectURL(link.href); + showNotification('PNG heruntergeladen!'); + }, 'image/png'); + } catch (err) { + console.error('Fehler:', err); + showNotification('Fehler beim PNG-Export', 'error'); + } +} + +async function downloadTextJPG() { + var svg = generateTextSVG(); + try { + var canvas = await svgToCanvas(svg, 2); + var ctx = canvas.getContext('2d'); + + // Weisser Hintergrund fuer JPG + var tempCanvas = document.createElement('canvas'); + tempCanvas.width = canvas.width; + tempCanvas.height = canvas.height; + var tempCtx = tempCanvas.getContext('2d'); + tempCtx.fillStyle = '#FFFFFF'; + tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height); + tempCtx.drawImage(canvas, 0, 0); + + tempCanvas.toBlob(function(blob) { + var link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol'; + link.download = 'text-' + filename + '.jpg'; + link.click(); + URL.revokeObjectURL(link.href); + showNotification('JPG heruntergeladen!'); + }, 'image/jpeg', 0.95); + } catch (err) { + console.error('Fehler:', err); + showNotification('Fehler beim JPG-Export', 'error'); + } +} + +function downloadTextDXF() { + var svg = generateTextSVG(); + var dxf = svgToDxf(svg, 1); + var blob = new Blob([dxf], { type: 'application/dxf' }); + var url = URL.createObjectURL(blob); + var link = document.createElement('a'); + link.href = url; + var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol'; + link.download = 'text-' + filename + '.dxf'; + link.click(); + URL.revokeObjectURL(url); + showNotification('DXF heruntergeladen!'); +} + +function addTextToLegend() { + var svg = generateTextSVG(); + var text = textGenState.text || 'Text'; + var displayName = text.replace(/\n/g, ' ').substring(0, 30); + + legendItems.push({ + id: 'custom_text_' + Date.now(), + name: displayName, + svg: svg, + description: '' + }); + + updateLegendCount(); + saveLegendToStorage(); + showNotification('Zur Legende hinzugefuegt!'); +} + +// ========== CUSTOM SYMBOLS STORAGE ========== + +function loadCustomSymbols() { + try { + var stored = localStorage.getItem('customSymbols'); + if (stored) { + customSymbols = JSON.parse(stored); + } + } catch (e) { + console.error('Fehler beim Laden der eigenen Symbole:', e); + customSymbols = []; + } +} + +function saveCustomSymbols() { + try { + localStorage.setItem('customSymbols', JSON.stringify(customSymbols)); + } catch (e) { + console.error('Fehler beim Speichern der eigenen Symbole:', e); + } +} + +function openSaveModal() { + var modal = document.getElementById('saveModal'); + var nameInput = document.getElementById('symbolName'); + var descInput = document.getElementById('symbolDescription'); + + var suggestedName = textGenState.text ? textGenState.text.replace(/\n/g, ' ').substring(0, 30) : ''; + nameInput.value = suggestedName; + descInput.value = ''; + + modal.style.display = 'flex'; + nameInput.focus(); +} + +function closeSaveModal() { + var modal = document.getElementById('saveModal'); + modal.style.display = 'none'; +} + +function saveCustomSymbol() { + var nameInput = document.getElementById('symbolName'); + var descInput = document.getElementById('symbolDescription'); + + var name = nameInput.value.trim(); + if (!name) { + showNotification('Bitte einen Namen eingeben!', 'error'); + return; + } + + var svg = generateTextSVG(); + + var newSymbol = { + id: 'custom_' + Date.now(), + name: name, + description: descInput.value.trim(), + svg: svg, + category: 'custom', + tags: ['eigene', 'custom', name.toLowerCase()], + createdAt: new Date().toISOString(), + state: JSON.parse(JSON.stringify(textGenState)) + }; + + customSymbols.push(newSymbol); + saveCustomSymbols(); + + closeSaveModal(); + showNotification('Symbol "' + name + '" gespeichert!'); + + var activeFilter = document.querySelector('.filter-pill.active'); + if (activeFilter && activeFilter.dataset.filter === 'custom') { + renderSymbolGrid(); + } +} + +function deleteCustomSymbol(id) { + if (!confirm('Symbol wirklich loeschen?')) return; + + customSymbols = customSymbols.filter(function(s) { return s.id !== id; }); + saveCustomSymbols(); + renderSymbolGrid(); + showNotification('Symbol geloescht'); +} + +var originalRenderSymbolGrid = typeof renderSymbolGrid === 'function' ? renderSymbolGrid : null; + +function renderSymbolGridWithCustom() { + var activeFilter = document.querySelector('.filter-pill.active'); + var filter = activeFilter ? activeFilter.dataset.filter : 'all'; + + if (filter === 'custom') { + renderCustomSymbolsOnly(); + } else if (originalRenderSymbolGrid) { + originalRenderSymbolGrid(); + if (filter === 'all') { + appendCustomSymbols(); + } + } +} + +function renderCustomSymbolsOnly() { + var grid = document.getElementById('symbolGrid'); + if (!grid) return; + + grid.innerHTML = ''; + + if (customSymbols.length === 0) { + grid.innerHTML = '
Noch keine eigenen Symbole gespeichert.
Erstelle ein Text-Symbol und klicke auf "Speichern".
'; + return; + } + + customSymbols.forEach(function(symbol) { + var card = createCustomSymbolCard(symbol); + grid.appendChild(card); + }); +} + +function appendCustomSymbols() { + var grid = document.getElementById('symbolGrid'); + if (!grid || customSymbols.length === 0) return; + + customSymbols.forEach(function(symbol) { + var card = createCustomSymbolCard(symbol); + grid.appendChild(card); + }); +} + +function createCustomSymbolCard(symbol) { + var card = document.createElement('div'); + card.className = 'symbol-card'; + card.innerHTML = + '' + + '
' + symbol.svg + '
' + + '
' + escapeHtml(symbol.name) + '
' + + '
' + + '' + + '' + + '' + + '' + + '
'; + return card; +} + +function escapeHtml(text) { + var div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + +function getSymbolById(id, isCustom) { + if (isCustom) { + return customSymbols.find(function(s) { return s.id === id; }); + } + return allSymbols.find(function(s) { return s.id === id; }); +} + +// ========== IMPRESSUM ========== +function openImpressum() { + var modal = document.getElementById('impressumModal'); + if (modal) modal.style.display = 'flex'; +} + +function closeImpressum() { + var modal = document.getElementById('impressumModal'); + if (modal) modal.style.display = 'none'; +} + +// Init beim Laden +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initTextGenerator); +} else { + initTextGenerator(); +} + + +// ========== PNG/JPG DOWNLOAD FUER ALLE SYMBOLE ========== + +async function downloadSymbolPNG(id) { + var symbol = findSymbol(id); + if (!symbol) return; + + var svg = symbol.svg; + try { + var canvas = await svgToCanvas(svg, 2); + canvas.toBlob(function(blob) { + var link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = symbol.id + '.png'; + link.click(); + URL.revokeObjectURL(link.href); + showNotification('PNG heruntergeladen!'); + }, 'image/png'); + } catch (err) { + console.error('Fehler:', err); + showNotification('Fehler beim PNG-Export', 'error'); + } +} + +async function downloadSymbolJPG(id) { + var symbol = findSymbol(id); + if (!symbol) return; + + var svg = symbol.svg; + try { + var canvas = await svgToCanvas(svg, 2); + + // Weisser Hintergrund fuer JPG + var tempCanvas = document.createElement('canvas'); + tempCanvas.width = canvas.width; + tempCanvas.height = canvas.height; + var tempCtx = tempCanvas.getContext('2d'); + tempCtx.fillStyle = '#FFFFFF'; + tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height); + tempCtx.drawImage(canvas, 0, 0); + + tempCanvas.toBlob(function(blob) { + var link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = symbol.id + '.jpg'; + link.click(); + URL.revokeObjectURL(link.href); + showNotification('JPG heruntergeladen!'); + }, 'image/jpeg', 0.95); + } catch (err) { + console.error('Fehler:', err); + showNotification('Fehler beim JPG-Export', 'error'); + } +} + +// SVG zu Canvas Hilfsfunktion (falls nicht vorhanden) +if (typeof svgToCanvas !== 'function') { + window.svgToCanvas = async function(svg, scale) { + var parser = new DOMParser(); + var svgDoc = parser.parseFromString(svg, 'image/svg+xml'); + var svgEl = svgDoc.documentElement; + var svgWidth = parseFloat(svgEl.getAttribute('width')) || parseFloat(svgEl.getAttribute('viewBox').split(' ')[2]) || 64; + var svgHeight = parseFloat(svgEl.getAttribute('height')) || parseFloat(svgEl.getAttribute('viewBox').split(' ')[3]) || 64; + + if (!scale) scale = 2; + var canvasWidth = Math.round(svgWidth * scale); + var canvasHeight = Math.round(svgHeight * scale); + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + canvas.width = canvasWidth; + canvas.height = canvasHeight; + + var img = new Image(); + var svgBlob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' }); + var url = URL.createObjectURL(svgBlob); + + await new Promise(function(resolve, reject) { + img.onload = resolve; + img.onerror = reject; + img.src = url; + }); + + ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight); + URL.revokeObjectURL(url); + + return canvas; + }; +} diff --git a/symbols/js/symbols.js b/symbols/js/symbols.js new file mode 100644 index 0000000..8d3f327 --- /dev/null +++ b/symbols/js/symbols.js @@ -0,0 +1,870 @@ +// ============================================ +// SYMBOL-DEFINITIONEN +// Gutachter Symbolbibliothek v2.0 +// ============================================ + +const SYMBOLS = { + // ========== SCHADENSARTEN ========== + schaeden: { + name: "Schadensarten", + icon: "🔥", + items: [ + { + id: "wasserschaden", + name: "Wasserschaden", + filename: "wasserschaden_symbol.svg", + tags: ["wasser", "feuchtigkeit", "nass"], + svg: `` + }, + { + id: "brandschaden", + name: "Brandschaden", + filename: "brandschaden_symbol.svg", + tags: ["feuer", "brand", "flamme"], + svg: `` + }, + { + id: "rauchschaden", + name: "Rauchschaden", + filename: "rauchschaden_symbol.svg", + tags: ["rauch", "russ", "qualm"], + svg: `` + }, + { + id: "leitungswasser", + name: "Leitungswasser / Rohrbruch", + filename: "leitungswasserschaden_symbol.svg", + tags: ["rohr", "leitung", "bruch", "wasser"], + svg: `` + }, + { + id: "schimmel", + name: "Schimmelschaden", + filename: "schimmelschaden_symbol.svg", + tags: ["schimmel", "pilz", "feucht", "sporen"], + svg: `!` + }, + { + id: "sturm", + name: "Sturmschaden", + filename: "sturmschaden_symbol.svg", + tags: ["sturm", "wind", "dach", "unwetter"], + svg: `` + }, + { + id: "einbruch", + name: "Einbruchschaden", + filename: "einbruchschaden_symbol.svg", + tags: ["einbruch", "diebstahl", "fenster", "tür"], + svg: `` + }, + { + id: "elektro", + name: "Elektroschaden", + filename: "elektroschaden_symbol.svg", + tags: ["elektro", "strom", "blitz", "kurzschluss"], + svg: `` + }, + { + id: "hagel", + name: "Hagelschaden", + filename: "hagelschaden_symbol.svg", + tags: ["hagel", "eis", "dellen", "unwetter"], + svg: `` + }, + { + id: "vandalismus", + name: "Vandalismus", + filename: "vandalismus_symbol.svg", + tags: ["vandalismus", "graffiti", "zerstörung", "sachbeschädigung"], + svg: `TAG` + } + ] + }, + + // ========== WERKZEUGE & MARKIERUNGEN ========== + werkzeuge: { + name: "Werkzeuge & Markierungen", + icon: "🔧", + items: [ + { + id: "massstab", + name: "Maßstab 1m", + filename: "massstab_1m.svg", + tags: ["maßstab", "meter", "lineal", "messen"], + svg: `0501001 Meter` + }, + { + id: "messpunkt", + name: "Messpunkt", + filename: "messpunkt.svg", + tags: ["messpunkt", "markierung", "punkt", "messen"], + svg: `` + }, + { + id: "kamera", + name: "Fotostandpunkt", + filename: "fotostandpunkt.svg", + tags: ["foto", "kamera", "standpunkt", "aufnahme"], + svg: `` + }, + { + id: "lupe", + name: "Detailbereich", + filename: "detailbereich.svg", + tags: ["detail", "lupe", "vergrößerung", "zoom"], + svg: `+` + }, + { + id: "notiz", + name: "Notiz / Hinweis", + filename: "notiz_hinweis.svg", + tags: ["notiz", "hinweis", "anmerkung", "text"], + svg: `` + }, + { + id: "warnung", + name: "Warnung / Achtung", + filename: "warnung_achtung.svg", + tags: ["warnung", "achtung", "gefahr", "vorsicht"], + svg: `!` + }, + { + id: "info", + name: "Information", + filename: "information.svg", + tags: ["info", "information", "hinweis", "details"], + svg: `` + }, + { + id: "haken", + name: "Erledigt / OK", + filename: "erledigt_ok.svg", + tags: ["ok", "erledigt", "fertig", "haken", "check"], + svg: `` + }, + { + id: "kreuz", + name: "Fehler / Mangel", + filename: "fehler_mangel.svg", + tags: ["fehler", "mangel", "falsch", "kreuz"], + svg: `` + }, + { + id: "fragezeichen", + name: "Unklar / Prüfen", + filename: "unklar_pruefen.svg", + tags: ["unklar", "prüfen", "frage", "unbekannt"], + svg: `?` + } + ] + }, + + // ========== BAUTEILE ========== + bauteile: { + name: "Bauteile", + icon: "🏗️", + items: [ + { + id: "fenster", + name: "Fenster", + filename: "bauteil_fenster.svg", + tags: ["fenster", "verglasung", "rahmen"], + svg: `` + }, + { + id: "tuer", + name: "Tür", + filename: "bauteil_tuer.svg", + tags: ["tür", "türblatt", "eingang"], + svg: `` + }, + { + id: "wand", + name: "Wand (Mauerwerk)", + filename: "bauteil_wand.svg", + tags: ["wand", "mauer", "mauerwerk", "ziegel"], + svg: `` + }, + { + id: "wand_beton", + name: "Wand (Beton)", + filename: "bauteil_wand_beton.svg", + tags: ["wand", "beton", "stahlbeton", "massiv"], + svg: `` + }, + { + id: "boden_fliesen", + name: "Fliesen", + filename: "bauteil_fliesen.svg", + tags: ["fliesen", "boden", "wand", "keramik", "kacheln"], + svg: `` + }, + { + id: "boden_parkett", + name: "Parkett / Holzboden", + filename: "bauteil_parkett.svg", + tags: ["parkett", "holz", "boden", "laminat", "dielen"], + svg: `` + }, + { + id: "dach", + name: "Dach", + filename: "bauteil_dach.svg", + tags: ["dach", "dachstuhl", "ziegel", "bedachung"], + svg: `` + }, + { + id: "treppe", + name: "Treppe", + filename: "bauteil_treppe.svg", + tags: ["treppe", "stufen", "aufgang", "treppenhaus"], + svg: `` + }, + { + id: "daemmung", + name: "Dämmung / Isolierung", + filename: "bauteil_daemmung.svg", + tags: ["dämmung", "isolierung", "wärme", "kälte"], + svg: `` + }, + { + id: "rohr", + name: "Rohrleitung", + filename: "bauteil_rohr.svg", + tags: ["rohr", "leitung", "rohrleitung", "installation"], + svg: `` + } + ] + }, + + // ========== MÖBEL ========== + moebel: { + name: "Möbel", + icon: "🛋️", + items: [ + { + id: "sofa", + name: "Sofa / Couch", + filename: "moebel_sofa.svg", + tags: ["sofa", "couch", "sitzmoebel", "wohnzimmer"], + svg: ``, + dxfSvg: `` + }, + { + id: "tisch", + name: "Tisch", + filename: "moebel_tisch.svg", + tags: ["tisch", "esstisch", "schreibtisch", "möbel"], + svg: ``, + dxfSvg: `` + }, + { + id: "stuhl", + name: "Stuhl", + filename: "moebel_stuhl.svg", + tags: ["stuhl", "sitz", "möbel", "esszimmer"], + svg: ``, + dxfSvg: `` + }, + { + id: "schrank", + name: "Schrank", + filename: "moebel_schrank.svg", + tags: ["schrank", "kleiderschrank", "möbel", "stauraum"], + svg: ``, + dxfSvg: `` + }, + { + id: "bett", + name: "Bett", + filename: "moebel_bett.svg", + tags: ["bett", "schlafzimmer", "möbel", "schlafen"], + svg: ``, + dxfSvg: `` + }, + { + id: "regal", + name: "Regal", + filename: "moebel_regal.svg", + tags: ["regal", "bücherregal", "möbel", "stauraum"], + svg: ``, + dxfSvg: `` + } + ] + }, + + // ========== BAD / SANITÄR ========== + bad: { + name: "Bad & Sanitär", + icon: "🚿", + items: [ + { + id: "wc", + name: "WC / Toilette", + filename: "wc_draufsicht.svg", + tags: ["wc", "toilette", "klo", "bad", "sanitär"], + svg: ``, + dxfSvg: `` + }, + { + id: "waschbecken", + name: "Waschbecken", + filename: "waschbecken_draufsicht.svg", + tags: ["waschbecken", "waschtisch", "bad", "sanitär", "lavabo"], + svg: ``, + dxfSvg: `` + }, + { + id: "badewanne", + name: "Badewanne", + filename: "badewanne_draufsicht.svg", + tags: ["badewanne", "wanne", "bad", "sanitär", "baden"], + svg: ``, + dxfSvg: `` + }, + { + id: "dusche", + name: "Dusche", + filename: "dusche_draufsicht.svg", + tags: ["dusche", "duschwanne", "bad", "sanitär", "brause"], + svg: ``, + dxfSvg: `` + }, + { + id: "bidet", + name: "Bidet", + filename: "bidet_draufsicht.svg", + tags: ["bidet", "bad", "sanitär"], + svg: ``, + dxfSvg: `` + }, + { + id: "doppelwaschbecken", + name: "Doppelwaschbecken", + filename: "doppelwaschbecken_draufsicht.svg", + tags: ["doppelwaschbecken", "waschtisch", "bad", "sanitär", "doppel"], + svg: ``, + dxfSvg: `` + } + ] + }, + // ========== KÜCHE ========== + kueche: { + name: "Küche", + icon: "🍳", + items: [ + { + id: "herd", + name: "Herd / Kochfeld", + filename: "kueche_herd.svg", + tags: ["herd", "kochfeld", "küche", "kochen"], + svg: ``, + dxfSvg: `` + }, + { + id: "spuele", + name: "Spüle", + filename: "kueche_spuele.svg", + tags: ["spüle", "waschbecken", "küche", "abwasch"], + svg: ``, + dxfSvg: `` + }, + { + id: "kuehlschrank", + name: "Kühlschrank", + filename: "kueche_kuehlschrank.svg", + tags: ["kühlschrank", "kühlen", "küche", "elektrogerät"], + svg: ``, + dxfSvg: `` + }, + { + id: "backofen", + name: "Backofen", + filename: "kueche_backofen.svg", + tags: ["backofen", "ofen", "küche", "backen"], + svg: ``, + dxfSvg: `` + }, + { + id: "spuelmaschine", + name: "Spülmaschine", + filename: "kueche_spuelmaschine.svg", + tags: ["spülmaschine", "geschirrspüler", "küche", "elektrogerät"], + svg: ``, + dxfSvg: `` + }, + { + id: "dunstabzug", + name: "Dunstabzugshaube", + filename: "kueche_dunstabzug.svg", + tags: ["dunstabzug", "dunstabzugshaube", "küche", "abzug"], + svg: ``, + dxfSvg: `` + } + ] + }, + + // ========== PFEILE (dynamisch) ========== + pfeile: { + name: "Richtungspfeile (Rot)", + icon: "➡️", + items: [] + }, + + // ========== KOMPASS (dynamisch) ========== + kompass: { + name: "Nordpfeile / Kompass", + icon: "🧭", + items: [] + }, + + // ========== VERMESSUNG - STATUS ========== + vermessung_status: { + name: "Vermessung - Status", + icon: "📋", + items: [ + { + id: "vm_reparatur", + name: "Reparatur", + filename: "vermessung_reparatur.svg", + tags: ["reparatur", "instandsetzung", "vermessung"], + svg: `R` + }, + { + id: "vm_neu", + name: "Neu", + filename: "vermessung_neu.svg", + tags: ["neu", "neubau", "vermessung"], + svg: `N` + }, + { + id: "vm_bestand", + name: "Bestand", + filename: "vermessung_bestand.svg", + tags: ["bestand", "bestehend", "vermessung"], + svg: `B` + }, + { + id: "vm_abriss", + name: "Abriss", + filename: "vermessung_abriss.svg", + tags: ["abriss", "rückbau", "vermessung"], + svg: `` + }, + { + id: "vm_geplant", + name: "Geplant", + filename: "vermessung_geplant.svg", + tags: ["geplant", "planung", "vermessung"], + svg: `P` + } + ] + }, + + // ========== VERMESSUNG - GRENZEN ========== + vermessung_grenzen: { + name: "Vermessung - Grenzen", + icon: "📍", + items: [ + { + id: "vm_grundstuecksgrenze", + name: "Grundstücksgrenze", + filename: "vermessung_grundstuecksgrenze.svg", + tags: ["grundstück", "grenze", "flurstück", "vermessung"], + svg: `` + }, + { + id: "vm_grenzpunkt_vermarkt", + name: "Grenzpunkt (vermarkt)", + filename: "vermessung_grenzpunkt_vermarkt.svg", + tags: ["grenzpunkt", "grenzstein", "vermarkt", "vermessung"], + svg: `` + }, + { + id: "vm_grenzpunkt_unvermarkt", + name: "Grenzpunkt (unvermarkt)", + filename: "vermessung_grenzpunkt_unvermarkt.svg", + tags: ["grenzpunkt", "unvermarkt", "vermessung"], + svg: `` + }, + { + id: "vm_flurstucksgrenze", + name: "Flurstücksgrenze", + filename: "vermessung_flurstucksgrenze.svg", + tags: ["flurstück", "grenze", "kataster", "vermessung"], + svg: `` + }, + { + id: "vm_zaun", + name: "Zaun", + filename: "vermessung_zaun.svg", + tags: ["zaun", "einfriedung", "grenze", "vermessung"], + svg: `` + }, + { + id: "vm_mauer", + name: "Mauer", + filename: "vermessung_mauer.svg", + tags: ["mauer", "wand", "einfriedung", "vermessung"], + svg: `` + }, + { + id: "vm_hecke", + name: "Hecke", + filename: "vermessung_hecke.svg", + tags: ["hecke", "grün", "bepflanzung", "vermessung"], + svg: `` + } + ] + }, + + // ========== VERMESSUNG - WASSER ========== + vermessung_wasser: { + name: "Vermessung - Wasser", + icon: "💧", + items: [ + { + id: "vm_hydrant_unterflur", + name: "Hydrant (Unterflur)", + filename: "vermessung_hydrant_unterflur.svg", + tags: ["hydrant", "unterflur", "wasser", "feuerwehr", "vermessung"], + svg: `` + }, + { + id: "vm_hydrant_ueberflur", + name: "Hydrant (Überflur)", + filename: "vermessung_hydrant_ueberflur.svg", + tags: ["hydrant", "überflur", "wasser", "feuerwehr", "vermessung"], + svg: `` + }, + { + id: "vm_wasserschacht", + name: "Trinkwasserschacht", + filename: "vermessung_wasserschacht.svg", + tags: ["schacht", "wasser", "trinkwasser", "vermessung"], + svg: `W` + }, + { + id: "vm_wasserschieber", + name: "Wasserschieber", + filename: "vermessung_wasserschieber.svg", + tags: ["schieber", "absperrer", "wasser", "vermessung"], + svg: `` + }, + { + id: "vm_brunnen", + name: "Brunnen", + filename: "vermessung_brunnen.svg", + tags: ["brunnen", "wasser", "quelle", "vermessung"], + svg: `` + }, + { + id: "vm_wasserleitung", + name: "Wasserleitung", + filename: "vermessung_wasserleitung.svg", + tags: ["leitung", "wasser", "rohr", "vermessung"], + svg: `W` + } + ] + }, + + // ========== VERMESSUNG - ABWASSER ========== + vermessung_abwasser: { + name: "Vermessung - Abwasser", + icon: "🚰", + items: [ + { + id: "vm_abwasserschacht", + name: "Abwasserschacht", + filename: "vermessung_abwasserschacht.svg", + tags: ["schacht", "abwasser", "kanal", "vermessung"], + svg: `S` + }, + { + id: "vm_schacht_rund", + name: "Schacht (rund)", + filename: "vermessung_schacht_rund.svg", + tags: ["schacht", "rund", "kanal", "vermessung"], + svg: `` + }, + { + id: "vm_schacht_eckig", + name: "Schacht (eckig)", + filename: "vermessung_schacht_eckig.svg", + tags: ["schacht", "eckig", "kanal", "vermessung"], + svg: `` + }, + { + id: "vm_einlauf", + name: "Einlauf / Gully", + filename: "vermessung_einlauf.svg", + tags: ["einlauf", "gully", "straßenablauf", "vermessung"], + svg: `` + }, + { + id: "vm_abwasserleitung", + name: "Abwasserleitung", + filename: "vermessung_abwasserleitung.svg", + tags: ["leitung", "abwasser", "kanal", "vermessung"], + svg: `` + } + ] + }, + + // ========== VERMESSUNG - STROM ========== + vermessung_strom: { + name: "Vermessung - Strom", + icon: "⚡", + items: [ + { + id: "vm_hausanschluss_elektro", + name: "Hausanschluss Elektro", + filename: "vermessung_hausanschluss_elektro.svg", + tags: ["hausanschluss", "elektro", "strom", "vermessung"], + svg: `` + }, + { + id: "vm_laterne", + name: "Laterne / Mast", + filename: "vermessung_laterne.svg", + tags: ["laterne", "mast", "beleuchtung", "vermessung"], + svg: `` + }, + { + id: "vm_stromkabel", + name: "Stromkabel", + filename: "vermessung_stromkabel.svg", + tags: ["kabel", "strom", "leitung", "vermessung"], + svg: `E` + }, + { + id: "vm_schaltkasten", + name: "Schaltkasten", + filename: "vermessung_schaltkasten.svg", + tags: ["schaltkasten", "verteiler", "strom", "vermessung"], + svg: `E` + }, + { + id: "vm_trafostation", + name: "Trafostation", + filename: "vermessung_trafostation.svg", + tags: ["trafo", "station", "umspanner", "vermessung"], + svg: `` + }, + { + id: "vm_mast_holz", + name: "Mast (Holz)", + filename: "vermessung_mast_holz.svg", + tags: ["mast", "holz", "freileitung", "vermessung"], + svg: `H` + }, + { + id: "vm_mast_beton", + name: "Mast (Beton)", + filename: "vermessung_mast_beton.svg", + tags: ["mast", "beton", "freileitung", "vermessung"], + svg: `` + }, + { + id: "vm_mast_stahl", + name: "Mast (Stahl)", + filename: "vermessung_mast_stahl.svg", + tags: ["mast", "stahl", "freileitung", "vermessung"], + svg: `` + } + ] + }, + + // ========== VERMESSUNG - GAS ========== + vermessung_gas: { + name: "Vermessung - Gas", + icon: "🔥", + items: [ + { + id: "vm_gasschieber", + name: "Gasschieber", + filename: "vermessung_gasschieber.svg", + tags: ["schieber", "absperrer", "gas", "vermessung"], + svg: `G` + }, + { + id: "vm_gasleitung", + name: "Gasleitung", + filename: "vermessung_gasleitung.svg", + tags: ["leitung", "gas", "rohr", "vermessung"], + svg: `G` + }, + { + id: "vm_hausanschluss_gas", + name: "Hausanschluss Gas", + filename: "vermessung_hausanschluss_gas.svg", + tags: ["hausanschluss", "gas", "anschluss", "vermessung"], + svg: `G` + } + ] + }, + + // ========== VERMESSUNG - VERKEHR ========== + vermessung_verkehr: { + name: "Vermessung - Verkehr", + icon: "🚗", + items: [ + { + id: "vm_gleise", + name: "Gleise / Schienen", + filename: "vermessung_gleise.svg", + tags: ["gleise", "schienen", "bahn", "vermessung"], + svg: `` + }, + { + id: "vm_prellbock", + name: "Prellbock", + filename: "vermessung_prellbock.svg", + tags: ["prellbock", "gleisende", "bahn", "vermessung"], + svg: `` + }, + { + id: "vm_verkehrsschild", + name: "Verkehrsschild", + filename: "vermessung_verkehrsschild.svg", + tags: ["schild", "verkehr", "straße", "vermessung"], + svg: `` + }, + { + id: "vm_ampel", + name: "Ampel", + filename: "vermessung_ampel.svg", + tags: ["ampel", "signal", "verkehr", "vermessung"], + svg: `` + }, + { + id: "vm_haltestelle", + name: "Haltestelle", + filename: "vermessung_haltestelle.svg", + tags: ["haltestelle", "bus", "bahn", "vermessung"], + svg: `H` + }, + { + id: "vm_parkplatz", + name: "Parkplatz", + filename: "vermessung_parkplatz.svg", + tags: ["parkplatz", "parken", "stellplatz", "vermessung"], + svg: `P` + }, + { + id: "vm_schranke", + name: "Schranke", + filename: "vermessung_schranke.svg", + tags: ["schranke", "bahnübergang", "absperrung", "vermessung"], + svg: `` + } + ] + }, + + // ========== VERMESSUNG - TOPOGRAFIE ========== + vermessung_topografie: { + name: "Vermessung - Topografie", + icon: "🌳", + items: [ + { + id: "vm_laubbaum", + name: "Laubbaum", + filename: "vermessung_laubbaum.svg", + tags: ["baum", "laubbaum", "vegetation", "vermessung"], + svg: `` + }, + { + id: "vm_nadelbaum", + name: "Nadelbaum", + filename: "vermessung_nadelbaum.svg", + tags: ["baum", "nadelbaum", "tanne", "vermessung"], + svg: `` + }, + { + id: "vm_gebaeude", + name: "Gebäude", + filename: "vermessung_gebaeude.svg", + tags: ["gebäude", "haus", "bauwerk", "vermessung"], + svg: `` + }, + { + id: "vm_hoehenpunkt", + name: "Höhenpunkt", + filename: "vermessung_hoehenpunkt.svg", + tags: ["höhe", "nivellement", "punkt", "vermessung"], + svg: `HP` + }, + { + id: "vm_boeschung", + name: "Böschung", + filename: "vermessung_boeschung.svg", + tags: ["böschung", "hang", "gelände", "vermessung"], + svg: `` + }, + { + id: "vm_fliessrichtung", + name: "Fließrichtung", + filename: "vermessung_fliessrichtung.svg", + tags: ["fließrichtung", "gewässer", "bach", "vermessung"], + svg: `` + }, + { + id: "vm_quelle", + name: "Quelle", + filename: "vermessung_quelle.svg", + tags: ["quelle", "wasser", "ursprung", "vermessung"], + svg: `` + }, + { + id: "vm_durchlass", + name: "Durchlass", + filename: "vermessung_durchlass.svg", + tags: ["durchlass", "rohr", "kanal", "vermessung"], + svg: `` + }, + { + id: "vm_kilometerstein", + name: "Kilometerstein", + filename: "vermessung_kilometerstein.svg", + tags: ["kilometer", "stein", "markierung", "vermessung"], + svg: `km` + }, + { + id: "vm_poller", + name: "Poller", + filename: "vermessung_poller.svg", + tags: ["poller", "absperrung", "pfosten", "vermessung"], + svg: `` + } + ] + } +}; + +// ========== DYNAMISCHE PFEILE GENERIEREN ========== +function generateArrowSVG(angle) { + return ``; +} + +function generateNorthArrowSVG(angle) { + return `N`; +} + +// Pfeile und Kompass generieren +for (let angle = 0; angle < 360; angle += 15) { + SYMBOLS.pfeile.items.push({ + id: `pfeil_${angle}`, + name: `${angle}°`, + filename: `richtungspfeil_rot_${angle}grad.svg`, + tags: ["pfeil", "richtung", "rot", angle.toString()], + svg: generateArrowSVG(angle) + }); + + SYMBOLS.kompass.items.push({ + id: `nord_${angle}`, + name: `${angle}°`, + filename: `kompass_nord_${angle}grad.svg`, + tags: ["nord", "kompass", "himmelsrichtung", angle.toString()], + svg: generateNorthArrowSVG(angle) + }); +} diff --git a/symbols/js/symbols.js.backup3 b/symbols/js/symbols.js.backup3 new file mode 100644 index 0000000..88cf58b --- /dev/null +++ b/symbols/js/symbols.js.backup3 @@ -0,0 +1,859 @@ +// ============================================ +// SYMBOL-DEFINITIONEN +// Gutachter Symbolbibliothek v2.0 +// ============================================ + +const SYMBOLS = { + // ========== SCHADENSARTEN ========== + schaeden: { + name: "Schadensarten", + icon: "🔥", + items: [ + { + id: "wasserschaden", + name: "Wasserschaden", + filename: "wasserschaden_symbol.svg", + tags: ["wasser", "feuchtigkeit", "nass"], + svg: `` + }, + { + id: "brandschaden", + name: "Brandschaden", + filename: "brandschaden_symbol.svg", + tags: ["feuer", "brand", "flamme"], + svg: `` + }, + { + id: "rauchschaden", + name: "Rauchschaden", + filename: "rauchschaden_symbol.svg", + tags: ["rauch", "russ", "qualm"], + svg: `` + }, + { + id: "leitungswasser", + name: "Leitungswasser / Rohrbruch", + filename: "leitungswasserschaden_symbol.svg", + tags: ["rohr", "leitung", "bruch", "wasser"], + svg: `` + }, + { + id: "schimmel", + name: "Schimmelschaden", + filename: "schimmelschaden_symbol.svg", + tags: ["schimmel", "pilz", "feucht", "sporen"], + svg: `!` + }, + { + id: "sturm", + name: "Sturmschaden", + filename: "sturmschaden_symbol.svg", + tags: ["sturm", "wind", "dach", "unwetter"], + svg: `` + }, + { + id: "einbruch", + name: "Einbruchschaden", + filename: "einbruchschaden_symbol.svg", + tags: ["einbruch", "diebstahl", "fenster", "tür"], + svg: `` + }, + { + id: "elektro", + name: "Elektroschaden", + filename: "elektroschaden_symbol.svg", + tags: ["elektro", "strom", "blitz", "kurzschluss"], + svg: `` + }, + { + id: "hagel", + name: "Hagelschaden", + filename: "hagelschaden_symbol.svg", + tags: ["hagel", "eis", "dellen", "unwetter"], + svg: `` + }, + { + id: "vandalismus", + name: "Vandalismus", + filename: "vandalismus_symbol.svg", + tags: ["vandalismus", "graffiti", "zerstörung", "sachbeschädigung"], + svg: `TAG` + } + ] + }, + + // ========== WERKZEUGE & MARKIERUNGEN ========== + werkzeuge: { + name: "Werkzeuge & Markierungen", + icon: "🔧", + items: [ + { + id: "massstab", + name: "Maßstab 1m", + filename: "massstab_1m.svg", + tags: ["maßstab", "meter", "lineal", "messen"], + svg: `0501001 Meter` + }, + { + id: "messpunkt", + name: "Messpunkt", + filename: "messpunkt.svg", + tags: ["messpunkt", "markierung", "punkt", "messen"], + svg: `` + }, + { + id: "kamera", + name: "Fotostandpunkt", + filename: "fotostandpunkt.svg", + tags: ["foto", "kamera", "standpunkt", "aufnahme"], + svg: `` + }, + { + id: "lupe", + name: "Detailbereich", + filename: "detailbereich.svg", + tags: ["detail", "lupe", "vergrößerung", "zoom"], + svg: `+` + }, + { + id: "notiz", + name: "Notiz / Hinweis", + filename: "notiz_hinweis.svg", + tags: ["notiz", "hinweis", "anmerkung", "text"], + svg: `` + }, + { + id: "warnung", + name: "Warnung / Achtung", + filename: "warnung_achtung.svg", + tags: ["warnung", "achtung", "gefahr", "vorsicht"], + svg: `!` + }, + { + id: "info", + name: "Information", + filename: "information.svg", + tags: ["info", "information", "hinweis", "details"], + svg: `` + }, + { + id: "haken", + name: "Erledigt / OK", + filename: "erledigt_ok.svg", + tags: ["ok", "erledigt", "fertig", "haken", "check"], + svg: `` + }, + { + id: "kreuz", + name: "Fehler / Mangel", + filename: "fehler_mangel.svg", + tags: ["fehler", "mangel", "falsch", "kreuz"], + svg: `` + }, + { + id: "fragezeichen", + name: "Unklar / Prüfen", + filename: "unklar_pruefen.svg", + tags: ["unklar", "prüfen", "frage", "unbekannt"], + svg: `?` + } + ] + }, + + // ========== BAUTEILE ========== + bauteile: { + name: "Bauteile", + icon: "🏗️", + items: [ + { + id: "fenster", + name: "Fenster", + filename: "bauteil_fenster.svg", + tags: ["fenster", "verglasung", "rahmen"], + svg: `` + }, + { + id: "tuer", + name: "Tür", + filename: "bauteil_tuer.svg", + tags: ["tür", "türblatt", "eingang"], + svg: `` + }, + { + id: "wand", + name: "Wand (Mauerwerk)", + filename: "bauteil_wand.svg", + tags: ["wand", "mauer", "mauerwerk", "ziegel"], + svg: `` + }, + { + id: "wand_beton", + name: "Wand (Beton)", + filename: "bauteil_wand_beton.svg", + tags: ["wand", "beton", "stahlbeton", "massiv"], + svg: `` + }, + { + id: "boden_fliesen", + name: "Fliesen", + filename: "bauteil_fliesen.svg", + tags: ["fliesen", "boden", "wand", "keramik", "kacheln"], + svg: `` + }, + { + id: "boden_parkett", + name: "Parkett / Holzboden", + filename: "bauteil_parkett.svg", + tags: ["parkett", "holz", "boden", "laminat", "dielen"], + svg: `` + }, + { + id: "dach", + name: "Dach", + filename: "bauteil_dach.svg", + tags: ["dach", "dachstuhl", "ziegel", "bedachung"], + svg: `` + }, + { + id: "treppe", + name: "Treppe", + filename: "bauteil_treppe.svg", + tags: ["treppe", "stufen", "aufgang", "treppenhaus"], + svg: `` + }, + { + id: "daemmung", + name: "Dämmung / Isolierung", + filename: "bauteil_daemmung.svg", + tags: ["dämmung", "isolierung", "wärme", "kälte"], + svg: `` + }, + { + id: "rohr", + name: "Rohrleitung", + filename: "bauteil_rohr.svg", + tags: ["rohr", "leitung", "rohrleitung", "installation"], + svg: `` + } + ] + }, + + // ========== MÖBEL ========== + moebel: { + name: "Möbel", + icon: "🛋️", + items: [ + { + id: "sofa", + name: "Sofa / Couch", + filename: "moebel_sofa.svg", + tags: ["sofa", "couch", "sitzmoebel", "wohnzimmer"], + svg: `` + }, + { + id: "tisch", + name: "Tisch", + filename: "moebel_tisch.svg", + tags: ["tisch", "esstisch", "schreibtisch", "möbel"], + svg: `` + }, + { + id: "stuhl", + name: "Stuhl", + filename: "moebel_stuhl.svg", + tags: ["stuhl", "sitz", "möbel", "esszimmer"], + svg: `` + }, + { + id: "schrank", + name: "Schrank", + filename: "moebel_schrank.svg", + tags: ["schrank", "kleiderschrank", "möbel", "stauraum"], + svg: `` + }, + { + id: "bett", + name: "Bett", + filename: "moebel_bett.svg", + tags: ["bett", "schlafzimmer", "möbel", "schlafen"], + svg: `` + }, + { + id: "regal", + name: "Regal", + filename: "moebel_regal.svg", + tags: ["regal", "bücherregal", "möbel", "stauraum"], + svg: `` + } + ] + }, + + // ========== KÜCHE ========== + kueche: { + + // ========== BAD / SANITÄR ========== + bad: { + name: "Bad & Sanitär", + icon: "🚿", + items: [ + { + id: "wc", + name: "WC / Toilette", + filename: "wc_draufsicht.svg", + tags: ["wc", "toilette", "klo", "bad", "sanitär"], + svg: ``, + dxfSvg: `` + }, + { + id: "waschbecken", + name: "Waschbecken", + filename: "waschbecken_draufsicht.svg", + tags: ["waschbecken", "waschtisch", "bad", "sanitär", "lavabo"], + svg: ``, + dxfSvg: `` + }, + { + id: "badewanne", + name: "Badewanne", + filename: "badewanne_draufsicht.svg", + tags: ["badewanne", "wanne", "bad", "sanitär", "baden"], + svg: ``, + dxfSvg: `` + }, + { + id: "dusche", + name: "Dusche", + filename: "dusche_draufsicht.svg", + tags: ["dusche", "duschwanne", "bad", "sanitär", "brause"], + svg: ``, + dxfSvg: `` + }, + { + id: "bidet", + name: "Bidet", + filename: "bidet_draufsicht.svg", + tags: ["bidet", "bad", "sanitär"], + svg: ``, + dxfSvg: `` + }, + { + id: "doppelwaschbecken", + name: "Doppelwaschbecken", + filename: "doppelwaschbecken_draufsicht.svg", + tags: ["doppelwaschbecken", "waschtisch", "bad", "sanitär", "doppel"], + svg: ``, + dxfSvg: `` + } + ] + }, + name: "Küche", + icon: "🍳", + items: [ + { + id: "herd", + name: "Herd / Kochfeld", + filename: "kueche_herd.svg", + tags: ["herd", "kochfeld", "küche", "kochen"], + svg: `` + }, + { + id: "spuele", + name: "Spüle", + filename: "kueche_spuele.svg", + tags: ["spüle", "waschbecken", "küche", "abwasch"], + svg: `` + }, + { + id: "kuehlschrank", + name: "Kühlschrank", + filename: "kueche_kuehlschrank.svg", + tags: ["kühlschrank", "kühlen", "küche", "elektrogerät"], + svg: `` + }, + { + id: "backofen", + name: "Backofen", + filename: "kueche_backofen.svg", + tags: ["backofen", "ofen", "küche", "backen"], + svg: `` + }, + { + id: "spuelmaschine", + name: "Spülmaschine", + filename: "kueche_spuelmaschine.svg", + tags: ["spülmaschine", "geschirrspüler", "küche", "elektrogerät"], + svg: `` + }, + { + id: "dunstabzug", + name: "Dunstabzugshaube", + filename: "kueche_dunstabzug.svg", + tags: ["dunstabzug", "dunstabzugshaube", "küche", "abzug"], + svg: `` + } + ] + }, + + // ========== PFEILE (dynamisch) ========== + pfeile: { + name: "Richtungspfeile (Rot)", + icon: "➡️", + items: [] + }, + + // ========== KOMPASS (dynamisch) ========== + kompass: { + name: "Nordpfeile / Kompass", + icon: "🧭", + items: [] + }, + + // ========== VERMESSUNG - STATUS ========== + vermessung_status: { + name: "Vermessung - Status", + icon: "📋", + items: [ + { + id: "vm_reparatur", + name: "Reparatur", + filename: "vermessung_reparatur.svg", + tags: ["reparatur", "instandsetzung", "vermessung"], + svg: `R` + }, + { + id: "vm_neu", + name: "Neu", + filename: "vermessung_neu.svg", + tags: ["neu", "neubau", "vermessung"], + svg: `N` + }, + { + id: "vm_bestand", + name: "Bestand", + filename: "vermessung_bestand.svg", + tags: ["bestand", "bestehend", "vermessung"], + svg: `B` + }, + { + id: "vm_abriss", + name: "Abriss", + filename: "vermessung_abriss.svg", + tags: ["abriss", "rückbau", "vermessung"], + svg: `` + }, + { + id: "vm_geplant", + name: "Geplant", + filename: "vermessung_geplant.svg", + tags: ["geplant", "planung", "vermessung"], + svg: `P` + } + ] + }, + + // ========== VERMESSUNG - GRENZEN ========== + vermessung_grenzen: { + name: "Vermessung - Grenzen", + icon: "📍", + items: [ + { + id: "vm_grundstuecksgrenze", + name: "Grundstücksgrenze", + filename: "vermessung_grundstuecksgrenze.svg", + tags: ["grundstück", "grenze", "flurstück", "vermessung"], + svg: `` + }, + { + id: "vm_grenzpunkt_vermarkt", + name: "Grenzpunkt (vermarkt)", + filename: "vermessung_grenzpunkt_vermarkt.svg", + tags: ["grenzpunkt", "grenzstein", "vermarkt", "vermessung"], + svg: `` + }, + { + id: "vm_grenzpunkt_unvermarkt", + name: "Grenzpunkt (unvermarkt)", + filename: "vermessung_grenzpunkt_unvermarkt.svg", + tags: ["grenzpunkt", "unvermarkt", "vermessung"], + svg: `` + }, + { + id: "vm_flurstucksgrenze", + name: "Flurstücksgrenze", + filename: "vermessung_flurstucksgrenze.svg", + tags: ["flurstück", "grenze", "kataster", "vermessung"], + svg: `` + }, + { + id: "vm_zaun", + name: "Zaun", + filename: "vermessung_zaun.svg", + tags: ["zaun", "einfriedung", "grenze", "vermessung"], + svg: `` + }, + { + id: "vm_mauer", + name: "Mauer", + filename: "vermessung_mauer.svg", + tags: ["mauer", "wand", "einfriedung", "vermessung"], + svg: `` + }, + { + id: "vm_hecke", + name: "Hecke", + filename: "vermessung_hecke.svg", + tags: ["hecke", "grün", "bepflanzung", "vermessung"], + svg: `` + } + ] + }, + + // ========== VERMESSUNG - WASSER ========== + vermessung_wasser: { + name: "Vermessung - Wasser", + icon: "💧", + items: [ + { + id: "vm_hydrant_unterflur", + name: "Hydrant (Unterflur)", + filename: "vermessung_hydrant_unterflur.svg", + tags: ["hydrant", "unterflur", "wasser", "feuerwehr", "vermessung"], + svg: `` + }, + { + id: "vm_hydrant_ueberflur", + name: "Hydrant (Überflur)", + filename: "vermessung_hydrant_ueberflur.svg", + tags: ["hydrant", "überflur", "wasser", "feuerwehr", "vermessung"], + svg: `` + }, + { + id: "vm_wasserschacht", + name: "Trinkwasserschacht", + filename: "vermessung_wasserschacht.svg", + tags: ["schacht", "wasser", "trinkwasser", "vermessung"], + svg: `W` + }, + { + id: "vm_wasserschieber", + name: "Wasserschieber", + filename: "vermessung_wasserschieber.svg", + tags: ["schieber", "absperrer", "wasser", "vermessung"], + svg: `` + }, + { + id: "vm_brunnen", + name: "Brunnen", + filename: "vermessung_brunnen.svg", + tags: ["brunnen", "wasser", "quelle", "vermessung"], + svg: `` + }, + { + id: "vm_wasserleitung", + name: "Wasserleitung", + filename: "vermessung_wasserleitung.svg", + tags: ["leitung", "wasser", "rohr", "vermessung"], + svg: `W` + } + ] + }, + + // ========== VERMESSUNG - ABWASSER ========== + vermessung_abwasser: { + name: "Vermessung - Abwasser", + icon: "🚰", + items: [ + { + id: "vm_abwasserschacht", + name: "Abwasserschacht", + filename: "vermessung_abwasserschacht.svg", + tags: ["schacht", "abwasser", "kanal", "vermessung"], + svg: `S` + }, + { + id: "vm_schacht_rund", + name: "Schacht (rund)", + filename: "vermessung_schacht_rund.svg", + tags: ["schacht", "rund", "kanal", "vermessung"], + svg: `` + }, + { + id: "vm_schacht_eckig", + name: "Schacht (eckig)", + filename: "vermessung_schacht_eckig.svg", + tags: ["schacht", "eckig", "kanal", "vermessung"], + svg: `` + }, + { + id: "vm_einlauf", + name: "Einlauf / Gully", + filename: "vermessung_einlauf.svg", + tags: ["einlauf", "gully", "straßenablauf", "vermessung"], + svg: `` + }, + { + id: "vm_abwasserleitung", + name: "Abwasserleitung", + filename: "vermessung_abwasserleitung.svg", + tags: ["leitung", "abwasser", "kanal", "vermessung"], + svg: `` + } + ] + }, + + // ========== VERMESSUNG - STROM ========== + vermessung_strom: { + name: "Vermessung - Strom", + icon: "⚡", + items: [ + { + id: "vm_hausanschluss_elektro", + name: "Hausanschluss Elektro", + filename: "vermessung_hausanschluss_elektro.svg", + tags: ["hausanschluss", "elektro", "strom", "vermessung"], + svg: `` + }, + { + id: "vm_laterne", + name: "Laterne / Mast", + filename: "vermessung_laterne.svg", + tags: ["laterne", "mast", "beleuchtung", "vermessung"], + svg: `` + }, + { + id: "vm_stromkabel", + name: "Stromkabel", + filename: "vermessung_stromkabel.svg", + tags: ["kabel", "strom", "leitung", "vermessung"], + svg: `E` + }, + { + id: "vm_schaltkasten", + name: "Schaltkasten", + filename: "vermessung_schaltkasten.svg", + tags: ["schaltkasten", "verteiler", "strom", "vermessung"], + svg: `E` + }, + { + id: "vm_trafostation", + name: "Trafostation", + filename: "vermessung_trafostation.svg", + tags: ["trafo", "station", "umspanner", "vermessung"], + svg: `` + }, + { + id: "vm_mast_holz", + name: "Mast (Holz)", + filename: "vermessung_mast_holz.svg", + tags: ["mast", "holz", "freileitung", "vermessung"], + svg: `H` + }, + { + id: "vm_mast_beton", + name: "Mast (Beton)", + filename: "vermessung_mast_beton.svg", + tags: ["mast", "beton", "freileitung", "vermessung"], + svg: `` + }, + { + id: "vm_mast_stahl", + name: "Mast (Stahl)", + filename: "vermessung_mast_stahl.svg", + tags: ["mast", "stahl", "freileitung", "vermessung"], + svg: `` + } + ] + }, + + // ========== VERMESSUNG - GAS ========== + vermessung_gas: { + name: "Vermessung - Gas", + icon: "🔥", + items: [ + { + id: "vm_gasschieber", + name: "Gasschieber", + filename: "vermessung_gasschieber.svg", + tags: ["schieber", "absperrer", "gas", "vermessung"], + svg: `G` + }, + { + id: "vm_gasleitung", + name: "Gasleitung", + filename: "vermessung_gasleitung.svg", + tags: ["leitung", "gas", "rohr", "vermessung"], + svg: `G` + }, + { + id: "vm_hausanschluss_gas", + name: "Hausanschluss Gas", + filename: "vermessung_hausanschluss_gas.svg", + tags: ["hausanschluss", "gas", "anschluss", "vermessung"], + svg: `G` + } + ] + }, + + // ========== VERMESSUNG - VERKEHR ========== + vermessung_verkehr: { + name: "Vermessung - Verkehr", + icon: "🚗", + items: [ + { + id: "vm_gleise", + name: "Gleise / Schienen", + filename: "vermessung_gleise.svg", + tags: ["gleise", "schienen", "bahn", "vermessung"], + svg: `` + }, + { + id: "vm_prellbock", + name: "Prellbock", + filename: "vermessung_prellbock.svg", + tags: ["prellbock", "gleisende", "bahn", "vermessung"], + svg: `` + }, + { + id: "vm_verkehrsschild", + name: "Verkehrsschild", + filename: "vermessung_verkehrsschild.svg", + tags: ["schild", "verkehr", "straße", "vermessung"], + svg: `` + }, + { + id: "vm_ampel", + name: "Ampel", + filename: "vermessung_ampel.svg", + tags: ["ampel", "signal", "verkehr", "vermessung"], + svg: `` + }, + { + id: "vm_haltestelle", + name: "Haltestelle", + filename: "vermessung_haltestelle.svg", + tags: ["haltestelle", "bus", "bahn", "vermessung"], + svg: `H` + }, + { + id: "vm_parkplatz", + name: "Parkplatz", + filename: "vermessung_parkplatz.svg", + tags: ["parkplatz", "parken", "stellplatz", "vermessung"], + svg: `P` + }, + { + id: "vm_schranke", + name: "Schranke", + filename: "vermessung_schranke.svg", + tags: ["schranke", "bahnübergang", "absperrung", "vermessung"], + svg: `` + } + ] + }, + + // ========== VERMESSUNG - TOPOGRAFIE ========== + vermessung_topografie: { + name: "Vermessung - Topografie", + icon: "🌳", + items: [ + { + id: "vm_laubbaum", + name: "Laubbaum", + filename: "vermessung_laubbaum.svg", + tags: ["baum", "laubbaum", "vegetation", "vermessung"], + svg: `` + }, + { + id: "vm_nadelbaum", + name: "Nadelbaum", + filename: "vermessung_nadelbaum.svg", + tags: ["baum", "nadelbaum", "tanne", "vermessung"], + svg: `` + }, + { + id: "vm_gebaeude", + name: "Gebäude", + filename: "vermessung_gebaeude.svg", + tags: ["gebäude", "haus", "bauwerk", "vermessung"], + svg: `` + }, + { + id: "vm_hoehenpunkt", + name: "Höhenpunkt", + filename: "vermessung_hoehenpunkt.svg", + tags: ["höhe", "nivellement", "punkt", "vermessung"], + svg: `HP` + }, + { + id: "vm_boeschung", + name: "Böschung", + filename: "vermessung_boeschung.svg", + tags: ["böschung", "hang", "gelände", "vermessung"], + svg: `` + }, + { + id: "vm_fliessrichtung", + name: "Fließrichtung", + filename: "vermessung_fliessrichtung.svg", + tags: ["fließrichtung", "gewässer", "bach", "vermessung"], + svg: `` + }, + { + id: "vm_quelle", + name: "Quelle", + filename: "vermessung_quelle.svg", + tags: ["quelle", "wasser", "ursprung", "vermessung"], + svg: `` + }, + { + id: "vm_durchlass", + name: "Durchlass", + filename: "vermessung_durchlass.svg", + tags: ["durchlass", "rohr", "kanal", "vermessung"], + svg: `` + }, + { + id: "vm_kilometerstein", + name: "Kilometerstein", + filename: "vermessung_kilometerstein.svg", + tags: ["kilometer", "stein", "markierung", "vermessung"], + svg: `km` + }, + { + id: "vm_poller", + name: "Poller", + filename: "vermessung_poller.svg", + tags: ["poller", "absperrung", "pfosten", "vermessung"], + svg: `` + } + ] + } +}; + +// ========== DYNAMISCHE PFEILE GENERIEREN ========== +function generateArrowSVG(angle) { + return ``; +} + +function generateNorthArrowSVG(angle) { + return `N`; +} + +// Pfeile und Kompass generieren +for (let angle = 0; angle < 360; angle += 15) { + SYMBOLS.pfeile.items.push({ + id: `pfeil_${angle}`, + name: `${angle}°`, + filename: `richtungspfeil_rot_${angle}grad.svg`, + tags: ["pfeil", "richtung", "rot", angle.toString()], + svg: generateArrowSVG(angle) + }); + + SYMBOLS.kompass.items.push({ + id: `nord_${angle}`, + name: `${angle}°`, + filename: `kompass_nord_${angle}grad.svg`, + tags: ["nord", "kompass", "himmelsrichtung", angle.toString()], + svg: generateNorthArrowSVG(angle) + }); +} diff --git a/symbols/js/text-generator.js b/symbols/js/text-generator.js new file mode 100644 index 0000000..67754a0 --- /dev/null +++ b/symbols/js/text-generator.js @@ -0,0 +1,1154 @@ + +// ========== TEXT-GENERATOR v5 ========== +// Features: Separate Padding pro Kante, Reset-Button, Einzelner Pfeil-Export, Pfeilspitzenlaenge + +var textGenDefaults = { + text: '', + fontSize: 16, + textColor: '#000000', + frameColor: '#000000', + shape: 'none', + frameScale: 100, + paddingTop: 10, + paddingRight: 10, + paddingBottom: 10, + paddingLeft: 10, + lineStyle: 'solid', + lineWeight: 2, + arrow: 'none', + arrowLength: 40, + arrowAngle: 0, + arrowBend: 50, + arrowSize: 10, + arrowTipLength: 15 +}; + +var textGenState = JSON.parse(JSON.stringify(textGenDefaults)); + +// Custom Symbols aus localStorage +var customSymbols = []; + +function initTextGenerator() { + loadCustomSymbols(); + + var textInput = document.getElementById('customText'); + var fontSizeInput = document.getElementById('fontSize'); + var fontSizeValue = document.getElementById('fontSizeValue'); + var textColorInput = document.getElementById('textColor'); + var textColorValue = document.getElementById('textColorValue'); + var frameColorInput = document.getElementById('frameColor'); + var frameColorValue = document.getElementById('frameColorValue'); + var frameScaleInput = document.getElementById('frameScale'); + var frameScaleValue = document.getElementById('frameScaleValue'); + + // Gesamt-Padding Input + var paddingAllInput = document.getElementById("paddingAll"); + var paddingAllValueEl = document.getElementById("paddingAllValue"); + + // Separate Padding Inputs + var paddingTopInput = document.getElementById('paddingTop'); + var paddingTopValue = document.getElementById('paddingTopValue'); + var paddingRightInput = document.getElementById('paddingRight'); + var paddingRightValue = document.getElementById('paddingRightValue'); + var paddingBottomInput = document.getElementById('paddingBottom'); + var paddingBottomValue = document.getElementById('paddingBottomValue'); + var paddingLeftInput = document.getElementById('paddingLeft'); + var paddingLeftValue = document.getElementById('paddingLeftValue'); + + var arrowLengthInput = document.getElementById('arrowLength'); + var arrowLengthValue = document.getElementById('arrowLengthValue'); + var arrowAngleInput = document.getElementById('arrowAngle'); + var arrowAngleValue = document.getElementById('arrowAngleValue'); + var arrowBendInput = document.getElementById('arrowBend'); + var arrowBendValue = document.getElementById('arrowBendValue'); + var arrowSizeInput = document.getElementById('arrowSize'); + var arrowSizeValue = document.getElementById('arrowSizeValue'); + var arrowTipLengthInput = document.getElementById('arrowTipLength'); + var arrowTipLengthValue = document.getElementById('arrowTipLengthValue'); + + // Text input + if (textInput) { + textInput.addEventListener('input', function(e) { + textGenState.text = e.target.value; + updateTextPreview(); + }); + } + + // Font size + if (fontSizeInput) { + fontSizeInput.addEventListener('input', function(e) { + textGenState.fontSize = parseInt(e.target.value); + fontSizeValue.textContent = e.target.value + 'px'; + updateTextPreview(); + }); + } + + // Text color + if (textColorInput) { + textColorInput.addEventListener('input', function(e) { + textGenState.textColor = e.target.value; + textColorValue.textContent = e.target.value; + updateTextPreview(); + }); + } + + // Frame color + if (frameColorInput) { + frameColorInput.addEventListener('input', function(e) { + textGenState.frameColor = e.target.value; + frameColorValue.textContent = e.target.value; + updateTextPreview(); + }); + } + + // Frame scale + if (frameScaleInput) { + frameScaleInput.addEventListener('input', function(e) { + textGenState.frameScale = parseInt(e.target.value); + frameScaleValue.textContent = e.target.value + '%'; + updateTextPreview(); + }); + } + + // Gesamt-Padding handler (setzt alle 4 Seiten) + if (paddingAllInput) { + paddingAllInput.addEventListener("input", function(e) { + var val = parseInt(e.target.value); + textGenState.paddingTop = val; + textGenState.paddingRight = val; + textGenState.paddingBottom = val; + textGenState.paddingLeft = val; + paddingAllValueEl.textContent = val + "px"; + // Update einzelne Slider + if (paddingTopInput) { paddingTopInput.value = val; paddingTopValue.textContent = val + "px"; } + if (paddingRightInput) { paddingRightInput.value = val; paddingRightValue.textContent = val + "px"; } + if (paddingBottomInput) { paddingBottomInput.value = val; paddingBottomValue.textContent = val + "px"; } + if (paddingLeftInput) { paddingLeftInput.value = val; paddingLeftValue.textContent = val + "px"; } + updateTextPreview(); + }); + } + + // Separate Padding handlers + if (paddingTopInput) { + paddingTopInput.addEventListener('input', function(e) { + textGenState.paddingTop = parseInt(e.target.value); + paddingTopValue.textContent = e.target.value + 'px'; + updateTextPreview(); + }); + } + if (paddingRightInput) { + paddingRightInput.addEventListener('input', function(e) { + textGenState.paddingRight = parseInt(e.target.value); + paddingRightValue.textContent = e.target.value + 'px'; + updateTextPreview(); + }); + } + if (paddingBottomInput) { + paddingBottomInput.addEventListener('input', function(e) { + textGenState.paddingBottom = parseInt(e.target.value); + paddingBottomValue.textContent = e.target.value + 'px'; + updateTextPreview(); + }); + } + if (paddingLeftInput) { + paddingLeftInput.addEventListener('input', function(e) { + textGenState.paddingLeft = parseInt(e.target.value); + paddingLeftValue.textContent = e.target.value + 'px'; + updateTextPreview(); + }); + } + + // Arrow length + if (arrowLengthInput) { + arrowLengthInput.addEventListener('input', function(e) { + textGenState.arrowLength = parseInt(e.target.value); + arrowLengthValue.textContent = e.target.value + 'px'; + updateTextPreview(); + try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); } + }); + } + + // Arrow angle + if (arrowAngleInput) { + arrowAngleInput.addEventListener('input', function(e) { + textGenState.arrowAngle = parseInt(e.target.value); + arrowAngleValue.textContent = e.target.value + '\u00B0'; + updateTextPreview(); + try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); } + }); + } + + // Arrow bend position + if (arrowBendInput) { + arrowBendInput.addEventListener('input', function(e) { + textGenState.arrowBend = parseInt(e.target.value); + arrowBendValue.textContent = e.target.value + '%'; + updateTextPreview(); + try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); } + }); + } + + // Arrow size (width) + if (arrowSizeInput) { + arrowSizeInput.addEventListener('input', function(e) { + textGenState.arrowSize = parseInt(e.target.value); + arrowSizeValue.textContent = e.target.value + 'px'; + updateTextPreview(); + try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); } + }); + } + + // Arrow tip length (laenger = schmaler) + if (arrowTipLengthInput) { + arrowTipLengthInput.addEventListener('input', function(e) { + textGenState.arrowTipLength = parseInt(e.target.value); + arrowTipLengthValue.textContent = e.target.value + 'px'; + updateTextPreview(); + try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); } + }); + } + + // Shape buttons + document.querySelectorAll('.shape-btn').forEach(function(btn) { + btn.addEventListener('click', function() { + document.querySelectorAll('.shape-btn').forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + textGenState.shape = btn.dataset.shape; + updateFrameScaleVisibility(); + updateTextPreview(); + }); + }); + + // Line style buttons + document.querySelectorAll('.line-btn').forEach(function(btn) { + btn.addEventListener('click', function() { + document.querySelectorAll('.line-btn').forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + textGenState.lineStyle = btn.dataset.style; + updateTextPreview(); + try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); } + }); + }); + + // Line weight buttons + document.querySelectorAll('.weight-btn').forEach(function(btn) { + btn.addEventListener('click', function() { + document.querySelectorAll('.weight-btn').forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + textGenState.lineWeight = parseInt(btn.dataset.weight); + updateTextPreview(); + try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); } + }); + }); + + // Arrow buttons + document.querySelectorAll('.arrow-btn').forEach(function(btn) { + btn.addEventListener('click', function() { + document.querySelectorAll('.arrow-btn').forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + textGenState.arrow = btn.dataset.arrow; + updateArrowDetailsVisibility(); + updateTextPreview(); + }); + }); + + updateFrameScaleVisibility(); + updateArrowDetailsVisibility(); + updateTextPreview(); + try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); } +} + +// ========== RESET FUNCTION ========== +function resetToDefaults() { + textGenState = JSON.parse(JSON.stringify(textGenDefaults)); + + // Update all UI elements + var textInput = document.getElementById('customText'); + if (textInput) textInput.value = ''; + + var fontSizeInput = document.getElementById('fontSize'); + var fontSizeValue = document.getElementById('fontSizeValue'); + if (fontSizeInput) fontSizeInput.value = textGenDefaults.fontSize; + if (fontSizeValue) fontSizeValue.textContent = textGenDefaults.fontSize + 'px'; + + var textColorInput = document.getElementById('textColor'); + var textColorValue = document.getElementById('textColorValue'); + if (textColorInput) textColorInput.value = textGenDefaults.textColor; + if (textColorValue) textColorValue.textContent = textGenDefaults.textColor; + + var frameColorInput = document.getElementById('frameColor'); + var frameColorValue = document.getElementById('frameColorValue'); + if (frameColorInput) frameColorInput.value = textGenDefaults.frameColor; + if (frameColorValue) frameColorValue.textContent = textGenDefaults.frameColor; + + var frameScaleInput = document.getElementById('frameScale'); + var frameScaleValue = document.getElementById('frameScaleValue'); + if (frameScaleInput) frameScaleInput.value = textGenDefaults.frameScale; + if (frameScaleValue) frameScaleValue.textContent = textGenDefaults.frameScale + '%'; + + // Reset Gesamt-Padding + var paddingAllInput = document.getElementById('paddingAll'); + var paddingAllValue = document.getElementById('paddingAllValue'); + if (paddingAllInput) paddingAllInput.value = textGenDefaults.paddingTop; + if (paddingAllValue) paddingAllValue.textContent = textGenDefaults.paddingTop + 'px'; + + // Reset separate padding + ['Top', 'Right', 'Bottom', 'Left'].forEach(function(side) { + var input = document.getElementById('padding' + side); + var value = document.getElementById('padding' + side + 'Value'); + var defaultVal = textGenDefaults['padding' + side]; + if (input) input.value = defaultVal; + if (value) value.textContent = defaultVal + 'px'; + }); + + var arrowLengthInput = document.getElementById('arrowLength'); + var arrowLengthValue = document.getElementById('arrowLengthValue'); + if (arrowLengthInput) arrowLengthInput.value = textGenDefaults.arrowLength; + if (arrowLengthValue) arrowLengthValue.textContent = textGenDefaults.arrowLength + 'px'; + + var arrowAngleInput = document.getElementById('arrowAngle'); + var arrowAngleValue = document.getElementById('arrowAngleValue'); + if (arrowAngleInput) arrowAngleInput.value = textGenDefaults.arrowAngle; + if (arrowAngleValue) arrowAngleValue.textContent = textGenDefaults.arrowAngle + '\u00B0'; + + var arrowBendInput = document.getElementById('arrowBend'); + var arrowBendValue = document.getElementById('arrowBendValue'); + if (arrowBendInput) arrowBendInput.value = textGenDefaults.arrowBend; + if (arrowBendValue) arrowBendValue.textContent = textGenDefaults.arrowBend + '%'; + + var arrowSizeInput = document.getElementById('arrowSize'); + var arrowSizeValue = document.getElementById('arrowSizeValue'); + if (arrowSizeInput) arrowSizeInput.value = textGenDefaults.arrowSize; + if (arrowSizeValue) arrowSizeValue.textContent = textGenDefaults.arrowSize + 'px'; + + var arrowTipLengthInput = document.getElementById('arrowTipLength'); + var arrowTipLengthValue = document.getElementById('arrowTipLengthValue'); + if (arrowTipLengthInput) arrowTipLengthInput.value = textGenDefaults.arrowTipLength; + if (arrowTipLengthValue) arrowTipLengthValue.textContent = textGenDefaults.arrowTipLength + 'px'; + + // Reset buttons + document.querySelectorAll('.shape-btn').forEach(function(btn) { + btn.classList.toggle('active', btn.dataset.shape === 'none'); + }); + document.querySelectorAll('.line-btn').forEach(function(btn) { + btn.classList.toggle('active', btn.dataset.style === 'solid'); + }); + document.querySelectorAll('.weight-btn').forEach(function(btn) { + btn.classList.toggle('active', btn.dataset.weight === '2'); + }); + document.querySelectorAll('.arrow-btn').forEach(function(btn) { + btn.classList.toggle('active', btn.dataset.arrow === 'none'); + }); + + updateFrameScaleVisibility(); + updateArrowDetailsVisibility(); + updateTextPreview(); + try { updateStandaloneArrowPreview(); } catch(e) { console.log("Standalone arrow preview error:", e); } + + showNotification('Auf Standard zurueckgesetzt'); +} + +function updateFrameScaleVisibility() { + console.log("updateFrameScaleVisibility called, shape:", textGenState.shape); + var row = document.getElementById('frameScaleRow'); + var paddingAllRow = document.getElementById('framePaddingAllRow'); + var paddingRow = document.getElementById('framePaddingRow'); + if (row) { + row.style.display = textGenState.shape === 'none' ? 'none' : 'flex'; + } + if (paddingAllRow) { + paddingAllRow.style.display = textGenState.shape === 'none' ? 'none' : 'flex'; + } + if (paddingRow) { + paddingRow.style.display = textGenState.shape === 'none' ? 'none' : 'flex'; + } +} + +function updateArrowDetailsVisibility() { + var row = document.getElementById('arrowDetailsRow'); + var row2 = document.getElementById('arrowDetailsRow2'); + if (row) { + row.style.display = textGenState.arrow === 'none' ? 'none' : 'flex'; + } + if (row2) { + row2.style.display = textGenState.arrow === 'none' ? 'none' : 'flex'; + } +} + +function toggleTextGenerator() { + var generator = document.querySelector('.text-generator'); + generator.classList.toggle('collapsed'); +} + +function getStrokeDasharray(style) { + switch(style) { + case 'dashed': return '8,4'; + case 'dotted': return '2,3'; + default: return 'none'; + } +} + +function generateTextSVG() { + var text = textGenState.text || 'Text'; + var fontSize = textGenState.fontSize; + var textColor = textGenState.textColor; + var frameColor = textGenState.frameColor; + var shape = textGenState.shape; + var frameScale = textGenState.frameScale / 100; + var paddingTop = textGenState.paddingTop; + var paddingRight = textGenState.paddingRight; + var paddingBottom = textGenState.paddingBottom; + var paddingLeft = textGenState.paddingLeft; + var lineStyle = textGenState.lineStyle; + var lineWeight = textGenState.lineWeight; + var arrow = textGenState.arrow; + var arrowLength = textGenState.arrowLength; + var arrowAngle = textGenState.arrowAngle; + var arrowBend = textGenState.arrowBend / 100; + var arrowSize = textGenState.arrowSize; + var arrowTipLength = textGenState.arrowTipLength; + + var lines = text.split('\n'); + if (lines.length === 0) lines = ['Text']; + + var maxLineLength = 0; + for (var i = 0; i < lines.length; i++) { + if (lines[i].length > maxLineLength) maxLineLength = lines[i].length; + } + if (maxLineLength === 0) maxLineLength = 4; + + var charWidth = fontSize * 0.6; + var lineHeight = fontSize * 1.3; + var textWidth = maxLineLength * charWidth; + var textHeight = lines.length * lineHeight; + + var frameWidth = (textWidth + paddingLeft + paddingRight) * frameScale; + var frameHeight = (textHeight + paddingTop + paddingBottom) * frameScale; + if (frameWidth < 40) frameWidth = 40; + if (frameHeight < 30) frameHeight = 30; + + var arrowSpace = (arrow !== 'none') ? arrowLength + arrowTipLength + 5 : 0; + var svgWidth = frameWidth; + var svgHeight = frameHeight; + var offsetX = 0, offsetY = 0; + + if (arrow === 'left') { svgWidth += arrowSpace; offsetX = arrowSpace; } + else if (arrow === 'right') { svgWidth += arrowSpace; } + else if (arrow === 'top') { svgHeight += arrowSpace; offsetY = arrowSpace; } + else if (arrow === 'bottom') { svgHeight += arrowSpace; } + + var frameCx = offsetX + frameWidth / 2; + var frameCy = offsetY + frameHeight / 2; + + // Text-Verschiebung durch asymmetrisches Padding + var textOffsetX = ((paddingLeft - paddingRight) / 2) * frameScale; + var textOffsetY = ((paddingTop - paddingBottom) / 2) * frameScale; + var textCx = frameCx + textOffsetX; + var textCy = frameCy + textOffsetY; + + var dashArray = getStrokeDasharray(lineStyle); + var dashAttr = dashArray !== 'none' ? ' stroke-dasharray="' + dashArray + '"' : ''; + + var shapeSvg = ''; + var shapeX = offsetX + lineWeight / 2; + var shapeY = offsetY + lineWeight / 2; + var shapeW = frameWidth - lineWeight; + var shapeH = frameHeight - lineWeight; + + switch(shape) { + case 'rect': + shapeSvg = ''; + break; + case 'square': + var squareSize = Math.max(shapeW, shapeH); + shapeSvg = ''; + break; + case 'circle': + var radius = Math.max(shapeW, shapeH) / 2; + shapeSvg = ''; + break; + case 'oval': + shapeSvg = ''; + break; + case 'diamond': + var halfW = shapeW / 2, halfH = shapeH / 2; + shapeSvg = ''; + break; + } + + var arrowSvg = ''; + if (arrow !== 'none') { + arrowSvg = generateArrowPath(arrow, frameCx, frameCy, frameWidth, frameHeight, offsetX, offsetY, arrowLength, arrowAngle, arrowBend, frameColor, lineWeight, dashAttr, arrowSize, arrowTipLength); + } + + var textSvg = ''; + var totalTextHeight = lines.length * lineHeight; + var startY = textCy - (totalTextHeight / 2) + (fontSize * 0.8); + + for (var j = 0; j < lines.length; j++) { + var lineY = startY + (j * lineHeight); + var lineText = lines[j] || ' '; + lineText = lineText.replace(/&/g, '&').replace(//g, '>'); + textSvg += '' + lineText + ''; + } + textSvg += ''; + + return '' + shapeSvg + arrowSvg + textSvg + ''; +} + + +function generateArrowPath(direction, cx, cy, frameWidth, frameHeight, offsetX, offsetY, length, angle, bendPos, color, weight, dashAttr, arrowSize, tipLength) { + var startX, startY, midX, midY, endX, endY; + var angleRad = angle * Math.PI / 180; + + // Startpunkt am Rahmen + switch(direction) { + case 'top': + startX = cx; + startY = offsetY; + midX = startX; + midY = startY - length * bendPos; + endX = midX + Math.sin(angleRad) * (length * (1 - bendPos)); + endY = midY - Math.cos(angleRad) * (length * (1 - bendPos)); + break; + case 'bottom': + startX = cx; + startY = offsetY + frameHeight; + midX = startX; + midY = startY + length * bendPos; + endX = midX + Math.sin(angleRad) * (length * (1 - bendPos)); + endY = midY + Math.cos(angleRad) * (length * (1 - bendPos)); + break; + case 'left': + startX = offsetX; + startY = cy; + midX = startX - length * bendPos; + midY = startY; + endX = midX - Math.cos(angleRad) * (length * (1 - bendPos)); + endY = midY + Math.sin(angleRad) * (length * (1 - bendPos)); + break; + case 'right': + startX = offsetX + frameWidth; + startY = cy; + midX = startX + length * bendPos; + midY = startY; + endX = midX + Math.cos(angleRad) * (length * (1 - bendPos)); + endY = midY + Math.sin(angleRad) * (length * (1 - bendPos)); + break; + } + + // Pfad erstellen + var pathD; + if (angle !== 0 && bendPos > 0 && bendPos < 1) { + pathD = 'M ' + startX + ' ' + startY + ' L ' + midX + ' ' + midY + ' L ' + endX + ' ' + endY; + } else { + pathD = 'M ' + startX + ' ' + startY + ' L ' + endX + ' ' + endY; + } + + // Pfeilspitze: Richtung basiert auf dem LETZTEN Segment + var lastSegmentStartX, lastSegmentStartY; + if (angle !== 0 && bendPos > 0 && bendPos < 1) { + lastSegmentStartX = midX; + lastSegmentStartY = midY; + } else { + lastSegmentStartX = startX; + lastSegmentStartY = startY; + } + + // Winkel des letzten Segments berechnen + var arrowHeadAngle = Math.atan2(endY - lastSegmentStartY, endX - lastSegmentStartX); + + // Pfeilspitze Punkte - tipLength bestimmt wie lang (und damit schmaler) die Spitze ist + // arrowSize bestimmt die Breite an der Basis + // Je laenger tipLength, desto spitzer/schmaler wird der Pfeil + var tipHalfWidth = arrowSize / 2; + + // Basis der Pfeilspitze (zurueck von der Spitze) + var baseX = endX - tipLength * Math.cos(arrowHeadAngle); + var baseY = endY - tipLength * Math.sin(arrowHeadAngle); + + // Seitenpunkte der Pfeilspitze + var perpAngle = arrowHeadAngle + Math.PI / 2; + var ah1x = baseX + tipHalfWidth * Math.cos(perpAngle); + var ah1y = baseY + tipHalfWidth * Math.sin(perpAngle); + var ah2x = baseX - tipHalfWidth * Math.cos(perpAngle); + var ah2y = baseY - tipHalfWidth * Math.sin(perpAngle); + + var arrowHead = ''; + + return '' + arrowHead; +} + +// ========== STANDALONE ARROW (Einzelner loser Pfeil) ========== + +function generateStandaloneArrowSVG() { + var length = textGenState.arrowLength; + var angle = textGenState.arrowAngle; + var bendPos = textGenState.arrowBend / 100; + var color = textGenState.frameColor; + var weight = textGenState.lineWeight; + var lineStyle = textGenState.lineStyle; + var arrowSize = textGenState.arrowSize; + var tipLength = textGenState.arrowTipLength; + + var dashArray = getStrokeDasharray(lineStyle); + var dashAttr = dashArray !== 'none' ? ' stroke-dasharray="' + dashArray + '"' : ''; + + // Padding fuer SVG + var padding = Math.max(arrowSize, tipLength) + 5; + var angleRad = angle * Math.PI / 180; + + // Pfeil geht von links nach rechts + var startX = padding; + var startY = padding + length / 2; + + // Knickpunkt + var midX = startX + length * bendPos; + var midY = startY; + + // Endpunkt mit Winkel + var endX, endY; + if (angle !== 0 && bendPos > 0 && bendPos < 1) { + endX = midX + Math.cos(angleRad) * (length * (1 - bendPos)); + endY = midY - Math.sin(angleRad) * (length * (1 - bendPos)); + } else { + endX = startX + length; + endY = startY; + } + + // SVG Dimensionen berechnen + var minX = Math.min(startX, midX, endX) - padding; + var maxX = Math.max(startX, midX, endX) + padding + tipLength; + var minY = Math.min(startY, midY, endY) - padding - arrowSize; + var maxY = Math.max(startY, midY, endY) + padding + arrowSize; + + var svgWidth = maxX - minX; + var svgHeight = maxY - minY; + + // Offset korrigieren + var offsetX = -minX; + var offsetY = -minY; + + startX += offsetX; + startY += offsetY; + midX += offsetX; + midY += offsetY; + endX += offsetX; + endY += offsetY; + + // Pfad erstellen + var pathD; + if (angle !== 0 && bendPos > 0 && bendPos < 1) { + pathD = 'M ' + startX + ' ' + startY + ' L ' + midX + ' ' + midY + ' L ' + endX + ' ' + endY; + } else { + pathD = 'M ' + startX + ' ' + startY + ' L ' + endX + ' ' + endY; + } + + // Pfeilspitze + var lastSegmentStartX, lastSegmentStartY; + if (angle !== 0 && bendPos > 0 && bendPos < 1) { + lastSegmentStartX = midX; + lastSegmentStartY = midY; + } else { + lastSegmentStartX = startX; + lastSegmentStartY = startY; + } + + var arrowHeadAngle = Math.atan2(endY - lastSegmentStartY, endX - lastSegmentStartX); + + var tipHalfWidth = arrowSize / 2; + var baseX = endX - tipLength * Math.cos(arrowHeadAngle); + var baseY = endY - tipLength * Math.sin(arrowHeadAngle); + + var perpAngle = arrowHeadAngle + Math.PI / 2; + var ah1x = baseX + tipHalfWidth * Math.cos(perpAngle); + var ah1y = baseY + tipHalfWidth * Math.sin(perpAngle); + var ah2x = baseX - tipHalfWidth * Math.cos(perpAngle); + var ah2y = baseY - tipHalfWidth * Math.sin(perpAngle); + + var arrowHead = ''; + var pathSvg = ''; + + return '' + pathSvg + arrowHead + ''; +} + +function updateStandaloneArrowPreview() { + // Nur anzeigen wenn Pfeil ausgewaehlt + if (textGenState.arrow === "none") { + var preview = document.getElementById("standaloneArrowPreview"); + if (preview) preview.innerHTML = '

Waehle einen Pfeil aus

'; + return; + } + var preview = document.getElementById('standaloneArrowPreview'); + if (preview) { + preview.innerHTML = generateStandaloneArrowSVG(); + } +} + +async function copyStandaloneArrow() { + var svg = generateStandaloneArrowSVG(); + try { + var canvas = await svgToCanvas(svg, 2); + canvas.toBlob(async function(blob) { + try { + await navigator.clipboard.write([ + new ClipboardItem({ 'image/png': blob }) + ]); + showNotification('Pfeil in Zwischenablage kopiert!'); + } catch (err) { + downloadStandaloneArrowPNG(); + } + }, 'image/png'); + } catch (err) { + showNotification('Fehler beim Kopieren', 'error'); + } +} + +function downloadStandaloneArrowSVG() { + var svg = generateStandaloneArrowSVG(); + var blob = new Blob([svg], { type: 'image/svg+xml' }); + var url = URL.createObjectURL(blob); + var link = document.createElement('a'); + link.href = url; + link.download = 'pfeil.svg'; + link.click(); + URL.revokeObjectURL(url); + showNotification('Pfeil SVG heruntergeladen!'); +} + +async function downloadStandaloneArrowPNG() { + var svg = generateStandaloneArrowSVG(); + try { + var canvas = await svgToCanvas(svg, 2); + canvas.toBlob(function(blob) { + var link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = 'pfeil.png'; + link.click(); + URL.revokeObjectURL(link.href); + showNotification('Pfeil PNG heruntergeladen!'); + }, 'image/png'); + } catch (err) { + showNotification('Fehler beim PNG-Export', 'error'); + } +} + +async function downloadStandaloneArrowJPG() { + var svg = generateStandaloneArrowSVG(); + try { + var canvas = await svgToCanvas(svg, 2); + var tempCanvas = document.createElement('canvas'); + tempCanvas.width = canvas.width; + tempCanvas.height = canvas.height; + var tempCtx = tempCanvas.getContext('2d'); + tempCtx.fillStyle = '#FFFFFF'; + tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height); + tempCtx.drawImage(canvas, 0, 0); + + tempCanvas.toBlob(function(blob) { + var link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = 'pfeil.jpg'; + link.click(); + URL.revokeObjectURL(link.href); + showNotification('Pfeil JPG heruntergeladen!'); + }, 'image/jpeg', 0.95); + } catch (err) { + showNotification('Fehler beim JPG-Export', 'error'); + } +} + +function downloadStandaloneArrowDXF() { + var svg = generateStandaloneArrowSVG(); + var dxf = svgToDxf(svg, 1); + var blob = new Blob([dxf], { type: 'application/dxf' }); + var url = URL.createObjectURL(blob); + var link = document.createElement('a'); + link.href = url; + link.download = 'pfeil.dxf'; + link.click(); + URL.revokeObjectURL(url); + showNotification('Pfeil DXF heruntergeladen!'); +} + +function addStandaloneArrowToLegend() { + var svg = generateStandaloneArrowSVG(); + legendItems.push({ + id: 'arrow_' + Date.now(), + name: 'Pfeil', + svg: svg, + description: '' + }); + updateLegendCount(); + saveLegendToStorage(); + showNotification('Pfeil zur Legende hinzugefuegt!'); +} + +function updateTextPreview() { + console.log("updateTextPreview called"); + var preview = document.getElementById('textPreview'); + if (preview) { + var svg = generateTextSVG(); console.log("Generated SVG length:", svg.length); preview.innerHTML = svg; + } +} + +// Hilfsfunktion: SVG zu Canvas rendern +async function svgToCanvas(svg, scale) { + var parser = new DOMParser(); + var svgDoc = parser.parseFromString(svg, 'image/svg+xml'); + var svgEl = svgDoc.documentElement; + var svgWidth = parseFloat(svgEl.getAttribute('width')) || 100; + var svgHeight = parseFloat(svgEl.getAttribute('height')) || 100; + + if (!scale) { + scale = Math.max(200 / Math.min(svgWidth, svgHeight), 1); + scale = Math.min(scale, 512 / Math.max(svgWidth, svgHeight)); + } + + var canvasWidth = Math.round(svgWidth * scale); + var canvasHeight = Math.round(svgHeight * scale); + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + canvas.width = canvasWidth; + canvas.height = canvasHeight; + + var img = new Image(); + var svgBlob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' }); + var url = URL.createObjectURL(svgBlob); + + await new Promise(function(resolve, reject) { + img.onload = resolve; + img.onerror = reject; + img.src = url; + }); + + ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight); + URL.revokeObjectURL(url); + + return canvas; +} + +async function copyTextAsImage() { + var svg = generateTextSVG(); + + try { + var canvas = await svgToCanvas(svg); + + canvas.toBlob(async function(blob) { + try { + await navigator.clipboard.write([ + new ClipboardItem({ 'image/png': blob }) + ]); + showNotification('In Zwischenablage kopiert!'); + } catch (err) { + var link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = 'text-symbol.png'; + link.click(); + showNotification('PNG heruntergeladen'); + } + }, 'image/png'); + } catch (err) { + console.error('Fehler:', err); + showNotification('Fehler beim Kopieren', 'error'); + } +} + +function downloadTextSVG() { + var svg = generateTextSVG(); + var blob = new Blob([svg], { type: 'image/svg+xml' }); + var url = URL.createObjectURL(blob); + var link = document.createElement('a'); + link.href = url; + var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol'; + link.download = 'text-' + filename + '.svg'; + link.click(); + URL.revokeObjectURL(url); + showNotification('SVG heruntergeladen!'); +} + +async function downloadTextPNG() { + var svg = generateTextSVG(); + try { + var canvas = await svgToCanvas(svg, 2); + + canvas.toBlob(function(blob) { + var link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol'; + link.download = 'text-' + filename + '.png'; + link.click(); + URL.revokeObjectURL(link.href); + showNotification('PNG heruntergeladen!'); + }, 'image/png'); + } catch (err) { + console.error('Fehler:', err); + showNotification('Fehler beim PNG-Export', 'error'); + } +} + +async function downloadTextJPG() { + var svg = generateTextSVG(); + try { + var canvas = await svgToCanvas(svg, 2); + var ctx = canvas.getContext('2d'); + + var tempCanvas = document.createElement('canvas'); + tempCanvas.width = canvas.width; + tempCanvas.height = canvas.height; + var tempCtx = tempCanvas.getContext('2d'); + tempCtx.fillStyle = '#FFFFFF'; + tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height); + tempCtx.drawImage(canvas, 0, 0); + + tempCanvas.toBlob(function(blob) { + var link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol'; + link.download = 'text-' + filename + '.jpg'; + link.click(); + URL.revokeObjectURL(link.href); + showNotification('JPG heruntergeladen!'); + }, 'image/jpeg', 0.95); + } catch (err) { + console.error('Fehler:', err); + showNotification('Fehler beim JPG-Export', 'error'); + } +} + +function downloadTextDXF() { + var svg = generateTextSVG(); + var dxf = svgToDxf(svg, 1); + var blob = new Blob([dxf], { type: 'application/dxf' }); + var url = URL.createObjectURL(blob); + var link = document.createElement('a'); + link.href = url; + var filename = textGenState.text ? textGenState.text.replace(/\n/g, '_').replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 20) : 'symbol'; + link.download = 'text-' + filename + '.dxf'; + link.click(); + URL.revokeObjectURL(url); + showNotification('DXF heruntergeladen!'); +} + +function addTextToLegend() { + var svg = generateTextSVG(); + var text = textGenState.text || 'Text'; + var displayName = text.replace(/\n/g, ' ').substring(0, 30); + + legendItems.push({ + id: 'custom_text_' + Date.now(), + name: displayName, + svg: svg, + description: '' + }); + + updateLegendCount(); + saveLegendToStorage(); + showNotification('Zur Legende hinzugefuegt!'); +} + +// ========== CUSTOM SYMBOLS STORAGE ========== + +function loadCustomSymbols() { + try { + var stored = localStorage.getItem('customSymbols'); + if (stored) { + customSymbols = JSON.parse(stored); + } + } catch (e) { + console.error('Fehler beim Laden der eigenen Symbole:', e); + customSymbols = []; + } +} + +function saveCustomSymbols() { + try { + localStorage.setItem('customSymbols', JSON.stringify(customSymbols)); + } catch (e) { + console.error('Fehler beim Speichern der eigenen Symbole:', e); + } +} + +function openSaveModal() { + var modal = document.getElementById('saveModal'); + var nameInput = document.getElementById('symbolName'); + var descInput = document.getElementById('symbolDescription'); + + var suggestedName = textGenState.text ? textGenState.text.replace(/\n/g, ' ').substring(0, 30) : ''; + nameInput.value = suggestedName; + descInput.value = ''; + + modal.style.display = 'flex'; + nameInput.focus(); +} + +function closeSaveModal() { + var modal = document.getElementById('saveModal'); + modal.style.display = 'none'; +} + +function saveCustomSymbol() { + var nameInput = document.getElementById('symbolName'); + var descInput = document.getElementById('symbolDescription'); + + var name = nameInput.value.trim(); + if (!name) { + showNotification('Bitte einen Namen eingeben!', 'error'); + return; + } + + var svg = generateTextSVG(); + + var newSymbol = { + id: 'custom_' + Date.now(), + name: name, + description: descInput.value.trim(), + svg: svg, + category: 'custom', + tags: ['eigene', 'custom', name.toLowerCase()], + createdAt: new Date().toISOString(), + state: JSON.parse(JSON.stringify(textGenState)) + }; + + customSymbols.push(newSymbol); + saveCustomSymbols(); + + closeSaveModal(); + showNotification('Symbol "' + name + '" gespeichert!'); + + var activeFilter = document.querySelector('.filter-pill.active'); + if (activeFilter && activeFilter.dataset.filter === 'custom') { + renderSymbolGrid(); + } +} + +function deleteCustomSymbol(id) { + if (!confirm('Symbol wirklich loeschen?')) return; + + customSymbols = customSymbols.filter(function(s) { return s.id !== id; }); + saveCustomSymbols(); + renderSymbolGrid(); + showNotification('Symbol geloescht'); +} + +var originalRenderSymbolGrid = typeof renderSymbolGrid === 'function' ? renderSymbolGrid : null; + +function renderSymbolGridWithCustom() { + var activeFilter = document.querySelector('.filter-pill.active'); + var filter = activeFilter ? activeFilter.dataset.filter : 'all'; + + if (filter === 'custom') { + renderCustomSymbolsOnly(); + } else if (originalRenderSymbolGrid) { + originalRenderSymbolGrid(); + if (filter === 'all') { + appendCustomSymbols(); + } + } +} + +function renderCustomSymbolsOnly() { + var grid = document.getElementById('symbolGrid'); + if (!grid) return; + + grid.innerHTML = ''; + + if (customSymbols.length === 0) { + grid.innerHTML = '
Noch keine eigenen Symbole gespeichert.
Erstelle ein Text-Symbol und klicke auf "Speichern".
'; + return; + } + + customSymbols.forEach(function(symbol) { + var card = createCustomSymbolCard(symbol); + grid.appendChild(card); + }); +} + +function appendCustomSymbols() { + var grid = document.getElementById('symbolGrid'); + if (!grid || customSymbols.length === 0) return; + + customSymbols.forEach(function(symbol) { + var card = createCustomSymbolCard(symbol); + grid.appendChild(card); + }); +} + +function createCustomSymbolCard(symbol) { + var card = document.createElement('div'); + card.className = 'symbol-card'; + card.innerHTML = + '' + + '
' + symbol.svg + '
' + + '
' + escapeHtml(symbol.name) + '
' + + '
' + + '' + + '' + + '' + + '' + + '
'; + return card; +} + +function escapeHtml(text) { + var div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + +function getSymbolById(id, isCustom) { + if (isCustom) { + return customSymbols.find(function(s) { return s.id === id; }); + } + return allSymbols.find(function(s) { return s.id === id; }); +} + +// ========== IMPRESSUM & DATENSCHUTZ ========== +function openImpressum() { + var modal = document.getElementById('impressumModal'); + if (modal) modal.style.display = 'flex'; +} + +function closeImpressum() { + var modal = document.getElementById('impressumModal'); + if (modal) modal.style.display = 'none'; +} + +function openDatenschutz() { + var modal = document.getElementById('datenschutzModal'); + if (modal) modal.style.display = 'flex'; +} + +function closeDatenschutz() { + var modal = document.getElementById('datenschutzModal'); + if (modal) modal.style.display = 'none'; +} + +// Init beim Laden +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initTextGenerator); +} else { + initTextGenerator(); +} + +// ========== GLOBAL EXPORTS fuer onclick Handler ========== +window.resetToDefaults = resetToDefaults; +window.copyStandaloneArrow = copyStandaloneArrow; +window.downloadStandaloneArrowSVG = downloadStandaloneArrowSVG; +window.downloadStandaloneArrowPNG = downloadStandaloneArrowPNG; +window.downloadStandaloneArrowJPG = downloadStandaloneArrowJPG; +window.downloadStandaloneArrowDXF = downloadStandaloneArrowDXF; +window.addStandaloneArrowToLegend = addStandaloneArrowToLegend; +window.generateTextSVG = generateTextSVG; +window.copyTextAsImage = copyTextAsImage; +window.downloadTextSVG = downloadTextSVG; +window.downloadTextPNG = downloadTextPNG; +window.downloadTextJPG = downloadTextJPG; +window.downloadTextDXF = downloadTextDXF; +window.addTextToLegend = addTextToLegend; +window.toggleTextGenerator = toggleTextGenerator; +window.openImpressum = openImpressum; +window.closeImpressum = closeImpressum; +window.openDatenschutz = openDatenschutz; +window.closeDatenschutz = closeDatenschutz; diff --git a/symbols/js/text-generator/export.js b/symbols/js/text-generator/export.js new file mode 100644 index 0000000..3b77d10 --- /dev/null +++ b/symbols/js/text-generator/export.js @@ -0,0 +1,321 @@ +/** + * Export-Funktionen fuer Text-Symbole + */ + +var TextExport = { + + // SVG zu Canvas rendern + svgToCanvas: function(svgString, scale) { + return new Promise(function(resolve, reject) { + var img = new Image(); + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + + var parser = new DOMParser(); + var svgDoc = parser.parseFromString(svgString, 'image/svg+xml'); + var svgEl = svgDoc.documentElement; + + var width = parseFloat(svgEl.getAttribute('width')) || 200; + var height = parseFloat(svgEl.getAttribute('height')) || 100; + + canvas.width = width * scale; + canvas.height = height * scale; + + var blob = new Blob([svgString], { type: 'image/svg+xml' }); + var url = URL.createObjectURL(blob); + + img.onload = function() { + ctx.scale(scale, scale); + ctx.drawImage(img, 0, 0); + URL.revokeObjectURL(url); + resolve(canvas); + }; + + img.onerror = function(e) { + URL.revokeObjectURL(url); + reject(e); + }; + + img.src = url; + }); + }, + + // In Zwischenablage kopieren (als PNG) + copyAsImage: function(svgString, scale) { + var self = this; + scale = scale || 3; + + return this.svgToCanvas(svgString, scale).then(function(canvas) { + return new Promise(function(resolve, reject) { + canvas.toBlob(function(blob) { + if (!blob) { + reject(new Error('Canvas toBlob failed')); + return; + } + navigator.clipboard.write([ + new ClipboardItem({ 'image/png': blob }) + ]).then(resolve).catch(reject); + }, 'image/png'); + }); + }); + }, + + // SVG in Zwischenablage + copySvg: function(svgString) { + return navigator.clipboard.writeText(svgString); + }, + + // SVG herunterladen + downloadSvg: function(svgString, filename) { + var blob = new Blob([svgString], { type: 'image/svg+xml' }); + var url = URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = filename || 'symbol.svg'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, + + // PNG herunterladen + downloadPng: function(svgString, filename, scale) { + scale = scale || 3; + + return this.svgToCanvas(svgString, scale).then(function(canvas) { + var url = canvas.toDataURL('image/png'); + var a = document.createElement('a'); + a.href = url; + a.download = filename || 'symbol.png'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + }); + }, + + // JPG herunterladen + downloadJpg: function(svgString, filename, scale, bgColor) { + scale = scale || 3; + bgColor = bgColor || '#ffffff'; + + return this.svgToCanvas(svgString, scale).then(function(canvas) { + // Neues Canvas mit Hintergrund + var bgCanvas = document.createElement('canvas'); + bgCanvas.width = canvas.width; + bgCanvas.height = canvas.height; + var ctx = bgCanvas.getContext('2d'); + ctx.fillStyle = bgColor; + ctx.fillRect(0, 0, bgCanvas.width, bgCanvas.height); + ctx.drawImage(canvas, 0, 0); + + var url = bgCanvas.toDataURL('image/jpeg', 0.95); + var a = document.createElement('a'); + a.href = url; + a.download = filename || 'symbol.jpg'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + }); + }, + + // DXF generieren (vereinfacht - nur Linien) + svgToDxf: function(svgString) { + var parser = new DOMParser(); + var svgDoc = parser.parseFromString(svgString, 'image/svg+xml'); + var svgEl = svgDoc.documentElement; + + var width = parseFloat(svgEl.getAttribute('width')) || 200; + var height = parseFloat(svgEl.getAttribute('height')) || 100; + + var dxf = '0\nSECTION\n2\nENTITIES\n'; + + // Rechtecke + var rects = svgEl.querySelectorAll('rect'); + rects.forEach(function(rect) { + var x = parseFloat(rect.getAttribute('x')) || 0; + var y = parseFloat(rect.getAttribute('y')) || 0; + var w = parseFloat(rect.getAttribute('width')) || 0; + var h = parseFloat(rect.getAttribute('height')) || 0; + var fy = height - y; + + dxf += '0\nLINE\n8\n0\n'; + dxf += '10\n' + x + '\n20\n' + fy + '\n30\n0\n'; + dxf += '11\n' + (x + w) + '\n21\n' + fy + '\n31\n0\n'; + + dxf += '0\nLINE\n8\n0\n'; + dxf += '10\n' + (x + w) + '\n20\n' + fy + '\n30\n0\n'; + dxf += '11\n' + (x + w) + '\n21\n' + (fy - h) + '\n31\n0\n'; + + dxf += '0\nLINE\n8\n0\n'; + dxf += '10\n' + (x + w) + '\n20\n' + (fy - h) + '\n30\n0\n'; + dxf += '11\n' + x + '\n21\n' + (fy - h) + '\n31\n0\n'; + + dxf += '0\nLINE\n8\n0\n'; + dxf += '10\n' + x + '\n20\n' + (fy - h) + '\n30\n0\n'; + dxf += '11\n' + x + '\n21\n' + fy + '\n31\n0\n'; + }); + + // Pfade (vereinfacht) + var paths = svgEl.querySelectorAll('path'); + paths.forEach(function(path) { + var d = path.getAttribute('d'); + if (!d) return; + + var points = []; + var parts = d.match(/[ML]\s*[\d.-]+\s+[\d.-]+/g); + if (parts) { + parts.forEach(function(part) { + var nums = part.match(/[\d.-]+/g); + if (nums && nums.length >= 2) { + points.push({ x: parseFloat(nums[0]), y: parseFloat(nums[1]) }); + } + }); + } + + for (var i = 0; i < points.length - 1; i++) { + dxf += '0\nLINE\n8\n0\n'; + dxf += '10\n' + points[i].x + '\n20\n' + (height - points[i].y) + '\n30\n0\n'; + dxf += '11\n' + points[i + 1].x + '\n21\n' + (height - points[i + 1].y) + '\n31\n0\n'; + } + }); + + // Text (als Hinweis) + var texts = svgEl.querySelectorAll('text'); + texts.forEach(function(text) { + var x = parseFloat(text.getAttribute('x')) || 0; + var y = parseFloat(text.getAttribute('y')) || 0; + var content = text.textContent || ''; + + dxf += '0\nTEXT\n8\n0\n'; + dxf += '10\n' + x + '\n20\n' + (height - y) + '\n30\n0\n'; + dxf += '40\n10\n1\n' + content + '\n'; + }); + + dxf += '0\nENDSEC\n0\nEOF\n'; + return dxf; + }, + + // DXF herunterladen + downloadDxf: function(svgString, filename) { + var dxf = this.svgToDxf(svgString); + var blob = new Blob([dxf], { type: 'application/dxf' }); + var url = URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = filename || 'symbol.dxf'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, + + // Feedback anzeigen + showFeedback: function(message, isError) { + var existing = document.querySelector('.export-feedback'); + if (existing) existing.remove(); + + var feedback = document.createElement('div'); + feedback.className = 'export-feedback'; + feedback.style.cssText = 'position: fixed; bottom: 20px; right: 20px; padding: 12px 20px; border-radius: 8px; font-size: 14px; z-index: 10000; animation: fadeIn 0.3s;'; + feedback.style.background = isError ? '#ef4444' : '#10b981'; + feedback.style.color = 'white'; + feedback.textContent = message; + document.body.appendChild(feedback); + + setTimeout(function() { + feedback.style.opacity = '0'; + feedback.style.transition = 'opacity 0.3s'; + setTimeout(function() { feedback.remove(); }, 300); + }, 2000); + } +}; + +window.TextExport = TextExport; + +// Globale Export-Funktionen fuer onclick-Handler +window.copyTextAsImage = function() { + var svg = window.SvgGenerator.generate(window.TextGenState.current); + window.TextExport.copyAsImage(svg).then(function() { + window.TextExport.showFeedback('Als PNG kopiert!'); + }).catch(function(e) { + window.TextExport.showFeedback('Fehler: ' + e.message, true); + }); +}; + +window.copyTextSVG = function() { + var svg = window.SvgGenerator.generate(window.TextGenState.current); + window.TextExport.copySvg(svg).then(function() { + window.TextExport.showFeedback('SVG kopiert!'); + }); +}; + +window.downloadTextPNG = function() { + var svg = window.SvgGenerator.generate(window.TextGenState.current); + window.TextExport.downloadPng(svg, 'text-symbol.png'); +}; + +window.downloadTextJPG = function() { + var svg = window.SvgGenerator.generate(window.TextGenState.current); + window.TextExport.downloadJpg(svg, 'text-symbol.jpg'); +}; + +window.downloadTextSVG = function() { + var svg = window.SvgGenerator.generate(window.TextGenState.current); + window.TextExport.downloadSvg(svg, 'text-symbol.svg'); +}; + +window.downloadTextDXF = function() { + var svg = window.SvgGenerator.generate(window.TextGenState.current); + window.TextExport.downloadDxf(svg, 'text-symbol.dxf'); +}; + +window.resetToDefaults = function() { + window.UiBindings.resetToDefaults(); +}; + +// Standalone-Pfeil Funktionen +window.copyStandaloneArrow = function() { + var svg = window.SvgGenerator.generateArrowOnly(window.TextGenState.current); + if (svg) { + window.TextExport.copyAsImage(svg).then(function() { + window.TextExport.showFeedback('Pfeil als PNG kopiert!'); + }); + } +}; + +window.copyStandaloneArrowSVG = function() { + var svg = window.SvgGenerator.generateArrowOnly(window.TextGenState.current); + if (svg) { + window.TextExport.copySvg(svg).then(function() { + window.TextExport.showFeedback('Pfeil-SVG kopiert!'); + }); + } +}; + +window.downloadStandaloneArrowSVG = function() { + var svg = window.SvgGenerator.generateArrowOnly(window.TextGenState.current); + if (svg) { + window.TextExport.downloadSvg(svg, 'pfeil.svg'); + } +}; + +window.downloadStandaloneArrowPNG = function() { + var svg = window.SvgGenerator.generateArrowOnly(window.TextGenState.current); + if (svg) { + window.TextExport.downloadPng(svg, 'pfeil.png'); + } +}; + +window.downloadStandaloneArrowJPG = function() { + var svg = window.SvgGenerator.generateArrowOnly(window.TextGenState.current); + if (svg) { + window.TextExport.downloadJpg(svg, 'pfeil.jpg'); + } +}; + +window.downloadStandaloneArrowDXF = function() { + var svg = window.SvgGenerator.generateArrowOnly(window.TextGenState.current); + if (svg) { + window.TextExport.downloadDxf(svg, 'pfeil.dxf'); + } +}; diff --git a/symbols/js/text-generator/state.js b/symbols/js/text-generator/state.js new file mode 100644 index 0000000..2639c0b --- /dev/null +++ b/symbols/js/text-generator/state.js @@ -0,0 +1,95 @@ +/** + * Text-Generator State Management + * Zentrale Zustandsverwaltung für alle Text-Symbol-Einstellungen + */ + +const TextGenState = { + // Aktuelle Werte + current: { + text: 'Text', + fontSize: 14, + textColor: '#000000', + frameColor: '#000000', + shape: 'rect', + frameScale: 120, + paddingTop: 10, + paddingRight: 10, + paddingBottom: 10, + paddingLeft: 10, + lineStyle: 'solid', + lineWeight: 2, + arrow: 'none', + arrowLength: 40, + arrowAngle: 0, + arrowBend: 50, + arrowSize: 10, + arrowTipLength: 15 + }, + + // Standardwerte für Reset + defaults: { + text: 'Text', + fontSize: 14, + textColor: '#000000', + frameColor: '#000000', + shape: 'rect', + frameScale: 120, + paddingTop: 10, + paddingRight: 10, + paddingBottom: 10, + paddingLeft: 10, + lineStyle: 'solid', + lineWeight: 2, + arrow: 'none', + arrowLength: 40, + arrowAngle: 0, + arrowBend: 50, + arrowSize: 10, + arrowTipLength: 15 + }, + + // State aktualisieren + set(key, value) { + if (key in this.current) { + this.current[key] = value; + this.onChange(key, value); + } + }, + + // Mehrere Werte setzen + setMultiple(updates) { + Object.entries(updates).forEach(([key, value]) => { + if (key in this.current) { + this.current[key] = value; + } + }); + this.onChange('multiple', updates); + }, + + // Auf Defaults zurücksetzen + reset() { + this.current = { ...this.defaults }; + this.onChange('reset', this.current); + }, + + // Getter + get(key) { + return this.current[key]; + }, + + // Alle Werte + getAll() { + return { ...this.current }; + }, + + // Change-Callback (wird von außen gesetzt) + onChange: function(key, value) { + // Wird von ui-bindings überschrieben + } +}; + +// Export für ES6 Module und globalen Zugriff +if (typeof module !== 'undefined' && module.exports) { + module.exports = TextGenState; +} +window.TextGenState = TextGenState; diff --git a/symbols/js/text-generator/svg-generator.js b/symbols/js/text-generator/svg-generator.js new file mode 100644 index 0000000..c3aabbe --- /dev/null +++ b/symbols/js/text-generator/svg-generator.js @@ -0,0 +1,371 @@ +/** + * SVG Generator mit SVG.js + * Erzeugt Text-Symbole mit Rahmen und Pfeilen + */ + +var SvgGenerator = { + + getDashArray: function(style, weight) { + switch(style) { + case 'dashed': return (weight * 4) + ',' + (weight * 2); + case 'dotted': return weight + ',' + (weight * 1.5); + default: return null; + } + }, + + measureText: function(text, fontSize) { + var lines = text.split('\n'); + var charWidth = fontSize * 0.6; + var lineHeight = fontSize * 1.3; + + var maxLength = 0; + for (var i = 0; i < lines.length; i++) { + if (lines[i].length > maxLength) maxLength = lines[i].length; + } + if (maxLength === 0) maxLength = 4; + + return { + lines: lines, + width: maxLength * charWidth, + height: lines.length * lineHeight, + lineHeight: lineHeight, + charWidth: charWidth + }; + }, + + createShape: function(draw, shape, x, y, width, height, style) { + var shapeEl; + var halfW = width / 2; + var halfH = height / 2; + var cx = x + halfW; + var cy = y + halfH; + + switch(shape) { + case 'rect': + shapeEl = draw.rect(width, height).move(x, y).radius(4); + break; + case 'rounded': + shapeEl = draw.rect(width, height).move(x, y).radius(Math.min(halfW, halfH) * 0.5); + break; + case 'oval': + shapeEl = draw.ellipse(width, height).center(cx, cy); + break; + case 'diamond': + shapeEl = draw.polygon([ + [cx, y], + [x + width, cy], + [cx, y + height], + [x, cy] + ]); + break; + default: + return null; + } + + if (shapeEl) { + shapeEl.fill('none').stroke({ color: style.color, width: style.weight }); + if (style.dashArray) { + shapeEl.attr('stroke-dasharray', style.dashArray); + } + } + return shapeEl; + }, + + calculateArrowHead: function(endX, endY, prevX, prevY, size, tipLength) { + // Berechne den Winkel der Pfeilrichtung + var angle = Math.atan2(endY - prevY, endX - prevX); + + // Die zwei hinteren Punkte der Pfeilspitze: + // - tipLength zurueck in Pfeilrichtung + // - size/2 seitlich (links und rechts) + var baseX = endX - tipLength * Math.cos(angle); + var baseY = endY - tipLength * Math.sin(angle); + + // Senkrecht zur Pfeilrichtung (90 Grad = PI/2) + var perpAngle = angle + Math.PI / 2; + var halfWidth = size / 2; + + return [ + [endX, endY], // Spitze + [baseX + halfWidth * Math.cos(perpAngle), baseY + halfWidth * Math.sin(perpAngle)], // Links + [baseX - halfWidth * Math.cos(perpAngle), baseY - halfWidth * Math.sin(perpAngle)] // Rechts + ]; + }, + + createArrow: function(draw, direction, frameRect, options) { + var length = options.length; + var angle = options.angle; + var bendPos = options.bendPos; + var color = options.color; + var weight = options.weight; + var dashArray = options.dashArray; + var arrowSize = options.arrowSize; + var tipLength = options.tipLength; + + var angleRad = angle * Math.PI / 180; + var bend = bendPos / 100; + + var startX, startY, midX, midY, endX, endY; + var fx = frameRect.x; + var fy = frameRect.y; + var fw = frameRect.width; + var fh = frameRect.height; + + // Fuer Standalone-Pfeile (width=0, height=0) ist fx,fy der Startpunkt + var isStandalone = (fw === 0 && fh === 0); + var cx = isStandalone ? fx : fx + fw / 2; + var cy = isStandalone ? fy : fy + fh / 2; + + switch(direction) { + case 'top': + startX = cx; + startY = isStandalone ? fy : fy; + midX = startX; midY = startY - length * bend; + endX = midX + Math.sin(angleRad) * (length * (1 - bend)); + endY = midY - Math.cos(angleRad) * (length * (1 - bend)); + break; + case 'bottom': + startX = cx; + startY = isStandalone ? fy : fy + fh; + midX = startX; midY = startY + length * bend; + endX = midX + Math.sin(angleRad) * (length * (1 - bend)); + endY = midY + Math.cos(angleRad) * (length * (1 - bend)); + break; + case 'left': + startX = isStandalone ? fx : fx; + startY = cy; + midX = startX - length * bend; midY = startY; + endX = midX - Math.cos(angleRad) * (length * (1 - bend)); + endY = midY + Math.sin(angleRad) * (length * (1 - bend)); + break; + case 'right': + startX = fx + fw; startY = cy; + midX = startX + length * bend; midY = startY; + endX = midX + Math.cos(angleRad) * (length * (1 - bend)); + endY = midY + Math.sin(angleRad) * (length * (1 - bend)); + break; + default: + return null; + } + + var points = (angle !== 0 && bend > 0 && bend < 1) + ? [[startX, startY], [midX, midY], [endX, endY]] + : [[startX, startY], [endX, endY]]; + + var pathData = ''; + for (var i = 0; i < points.length; i++) { + pathData += (i === 0 ? 'M' : 'L') + points[i][0] + ' ' + points[i][1] + ' '; + } + + var line = draw.path(pathData).fill('none').stroke({ color: color, width: weight }); + if (dashArray) line.attr('stroke-dasharray', dashArray); + + var lastPoint = points[points.length - 1]; + var prevPoint = points[points.length - 2]; + var headPoints = this.calculateArrowHead(lastPoint[0], lastPoint[1], prevPoint[0], prevPoint[1], arrowSize, tipLength); + var arrowHead = draw.polygon(headPoints).fill(color).stroke({ color: color, width: 1 }); + + return draw.group().add(line).add(arrowHead); + }, + + createText: function(draw, text, x, y, options) { + var fontSize = options.fontSize; + var color = options.color; + var lines = options.lines; + var lineHeight = options.lineHeight; + var group = draw.group(); + + var totalHeight = lines.length * lineHeight; + var startY = y - (totalHeight / 2) + (fontSize * 0.8); + + for (var i = 0; i < lines.length; i++) { + var lineY = startY + (i * lineHeight); + var lineText = lines[i] || ' '; + // Verwende plain SVG text element fuer bessere Kontrolle + var textEl = draw.plain(lineText) + .font({ + family: 'Arial, sans-serif', + size: fontSize, + anchor: 'middle' + }) + .fill(color) + .attr('x', x) + .attr('y', lineY) + .attr('text-anchor', 'middle'); + group.add(textEl); + } + return group; + }, + + generate: function(state) { + var s = state; + var textMetrics = this.measureText(s.text || 'Text', s.fontSize); + + var frameScale = s.frameScale / 100; + var frameWidth = (textMetrics.width + s.paddingLeft + s.paddingRight) * frameScale; + var frameHeight = (textMetrics.height + s.paddingTop + s.paddingBottom) * frameScale; + + var minWidth = Math.max(frameWidth, 40); + var minHeight = Math.max(frameHeight, 30); + + // Pfeil-Raum berechnen inkl. Winkel-Abweichung + var arrowSpace = 0; + var arrowSideSpace = 0; + if (s.arrow !== 'none') { + var angleRad = Math.abs(s.arrowAngle) * Math.PI / 180; + var bendFactor = s.arrowBend / 100; + var effectiveLength = s.arrowLength * (1 - bendFactor); + + // Hauptrichtung: volle Laenge + Spitze + Puffer + arrowSpace = s.arrowLength + s.arrowTipLength + 20; + + // Seitliche Abweichung durch Winkel + arrowSideSpace = Math.abs(Math.sin(angleRad) * effectiveLength) + s.arrowSize + 10; + } + + var svgWidth = minWidth; + var svgHeight = minHeight; + var offsetX = 0, offsetY = 0; + + switch(s.arrow) { + case 'left': + svgWidth += arrowSpace; + svgHeight = Math.max(svgHeight, minHeight + arrowSideSpace * 2); + offsetX = arrowSpace; + offsetY = (svgHeight - minHeight) / 2; + break; + case 'right': + svgWidth += arrowSpace; + svgHeight = Math.max(svgHeight, minHeight + arrowSideSpace * 2); + offsetY = (svgHeight - minHeight) / 2; + break; + case 'top': + svgHeight += arrowSpace; + svgWidth = Math.max(svgWidth, minWidth + arrowSideSpace * 2); + offsetY = arrowSpace; + offsetX = (svgWidth - minWidth) / 2; + break; + case 'bottom': + svgHeight += arrowSpace; + svgWidth = Math.max(svgWidth, minWidth + arrowSideSpace * 2); + offsetX = (svgWidth - minWidth) / 2; + break; + } + + var draw = SVG().size(svgWidth, svgHeight); + + var frameX = offsetX; + var frameY = offsetY; + var frameCx = frameX + minWidth / 2; + var frameCy = frameY + minHeight / 2; + + // Text-Verschiebung basierend auf asymmetrischem Padding + // Wenn links mehr Padding ist, verschiebt sich der Text nach rechts + var textOffsetX = (s.paddingLeft - s.paddingRight) / 2; + var textOffsetY = (s.paddingTop - s.paddingBottom) / 2; + var textCx = frameCx + textOffsetX; + var textCy = frameCy + textOffsetY; + + var dashArray = this.getDashArray(s.lineStyle, s.lineWeight); + var strokeStyle = { color: s.frameColor, weight: s.lineWeight, dashArray: dashArray }; + + if (s.shape !== 'none') { + this.createShape(draw, s.shape, frameX, frameY, minWidth, minHeight, strokeStyle); + } + + if (s.arrow !== 'none') { + this.createArrow(draw, s.arrow, { + x: frameX, y: frameY, width: minWidth, height: minHeight + }, { + length: s.arrowLength, angle: s.arrowAngle, bendPos: s.arrowBend, + color: s.frameColor, weight: s.lineWeight, dashArray: dashArray, + arrowSize: s.arrowSize, tipLength: s.arrowTipLength + }); + } + + this.createText(draw, s.text, textCx, textCy, { + fontSize: s.fontSize, color: s.textColor, + lines: textMetrics.lines, lineHeight: textMetrics.lineHeight + }); + + return draw.svg(); + }, + + generateArrowOnly: function(state) { + var s = state; + if (s.arrow === 'none') return null; + + var length = s.arrowLength; + var tipLength = s.arrowTipLength; + var arrowSize = s.arrowSize; + var padding = 40; + + // Berechne die tatsaechlichen Endpunkte des Pfeils + var angleRad = s.arrowAngle * Math.PI / 180; + var bendFactor = s.arrowBend / 100; + var straightPart = length * bendFactor; + var angledPart = length * (1 - bendFactor); + + // Seitliche Ausdehnung durch den Winkel (mit Vorzeichen) + var sideExtent = Math.sin(angleRad) * angledPart; + var mainExtent = Math.cos(angleRad) * angledPart; + + // Mindestgroesse fuer die seitliche Ausdehnung + var minSideSpace = Math.abs(sideExtent) + arrowSize + tipLength + padding; + + var width, height, startX, startY; + + switch(s.arrow) { + case 'right': + width = straightPart + mainExtent + tipLength + padding * 2; + height = Math.max(arrowSize * 4, minSideSpace * 2); + startX = padding; + // Bei positivem Winkel geht der Pfeil nach unten, also Start weiter oben + startY = height / 2 - sideExtent / 2; + break; + case 'left': + width = straightPart + mainExtent + tipLength + padding * 2; + height = Math.max(arrowSize * 4, minSideSpace * 2); + startX = width - padding; + startY = height / 2 + sideExtent / 2; + break; + case 'bottom': + width = Math.max(arrowSize * 4, minSideSpace * 2); + height = straightPart + mainExtent + tipLength + padding * 2; + startX = width / 2 - sideExtent / 2; + startY = padding; + break; + case 'top': + width = Math.max(arrowSize * 4, minSideSpace * 2); + height = straightPart + mainExtent + tipLength + padding * 2; + startX = width / 2 + sideExtent / 2; + startY = height - padding; + break; + } + + // Stelle sicher, dass alle Werte positiv sind + width = Math.max(width, 80); + height = Math.max(height, 60); + + var draw = SVG().size(width, height); + + // Simuliere einen Rahmen-Punkt als Startpunkt + var frameRect = { + x: startX, + y: startY, + width: 0, + height: 0 + }; + + this.createArrow(draw, s.arrow, frameRect, { + length: s.arrowLength, angle: s.arrowAngle, bendPos: s.arrowBend, + color: s.frameColor, weight: s.lineWeight, + dashArray: this.getDashArray(s.lineStyle, s.lineWeight), + arrowSize: s.arrowSize, tipLength: s.arrowTipLength + }); + + return draw.svg(); + } +}; + +window.SvgGenerator = SvgGenerator; diff --git a/symbols/js/text-generator/ui-bindings.js b/symbols/js/text-generator/ui-bindings.js new file mode 100644 index 0000000..72740c5 --- /dev/null +++ b/symbols/js/text-generator/ui-bindings.js @@ -0,0 +1,344 @@ +/** + * UI Bindings - Verbindet HTML-Elemente mit dem State + */ + +var UiBindings = { + elements: {}, + + init: function() { + this.cacheElements(); + this.bindEvents(); + this.syncFromState(); + this.updateVisibility(); + this.updatePreview(); + this.updateStandaloneArrowPreview(); + }, + + cacheElements: function() { + var e = this.elements; + + // Text & Farben + e.textInput = document.getElementById('textInput'); + e.fontSizeInput = document.getElementById('fontSize'); + e.fontSizeValue = document.getElementById('fontSizeValue'); + e.textColorInput = document.getElementById('textColor'); + e.frameColorInput = document.getElementById('frameColor'); + + // Rahmen + e.frameScaleInput = document.getElementById('frameScale'); + e.frameScaleValue = document.getElementById('frameScaleValue'); + e.frameScaleRow = document.getElementById('frameScaleRow'); + + // Padding + e.paddingAllInput = document.getElementById('framePaddingAll'); + e.paddingAllValue = document.getElementById('framePaddingAllValue'); + e.paddingTopInput = document.getElementById('framePaddingTop'); + e.paddingTopValue = document.getElementById('framePaddingTopValue'); + e.paddingRightInput = document.getElementById('framePaddingRight'); + e.paddingRightValue = document.getElementById('framePaddingRightValue'); + e.paddingBottomInput = document.getElementById('framePaddingBottom'); + e.paddingBottomValue = document.getElementById('framePaddingBottomValue'); + e.paddingLeftInput = document.getElementById('framePaddingLeft'); + e.paddingLeftValue = document.getElementById('framePaddingLeftValue'); + e.paddingAllRow = document.getElementById('framePaddingAllRow'); + e.paddingRow = document.getElementById('framePaddingRow'); + + // Pfeil + e.arrowLengthInput = document.getElementById('arrowLength'); + e.arrowLengthValue = document.getElementById('arrowLengthValue'); + e.arrowAngleInput = document.getElementById('arrowAngle'); + e.arrowAngleValue = document.getElementById('arrowAngleValue'); + e.arrowBendInput = document.getElementById('arrowBend'); + e.arrowBendValue = document.getElementById('arrowBendValue'); + e.arrowSizeInput = document.getElementById('arrowSize'); + e.arrowSizeValue = document.getElementById('arrowSizeValue'); + e.arrowTipLengthInput = document.getElementById('arrowTipLength'); + e.arrowTipLengthValue = document.getElementById('arrowTipLengthValue'); + e.arrowDetailsRow = document.getElementById('arrowDetailsRow'); + e.arrowDetailsRow2 = document.getElementById('arrowDetailsRow2'); + + // Vorschau + e.textPreview = document.getElementById('textPreview'); + e.standaloneArrowPreview = document.getElementById('standaloneArrowPreview'); + + // Buttons + e.shapeButtons = document.querySelectorAll('.shape-btn'); + e.arrowButtons = document.querySelectorAll('.arrow-btn'); + e.lineStyleButtons = document.querySelectorAll('.line-style-btn'); + e.lineWeightButtons = document.querySelectorAll('.line-weight-btn'); + }, + + bindEvents: function() { + var self = this; + var e = this.elements; + var state = window.TextGenState; + + // Text-Input + if (e.textInput) { + e.textInput.addEventListener('input', function(ev) { + state.set('text', ev.target.value); + self.updatePreview(); + }); + } + + // Schriftgroesse + if (e.fontSizeInput) { + e.fontSizeInput.addEventListener('input', function(ev) { + var val = parseInt(ev.target.value); + state.set('fontSize', val); + if (e.fontSizeValue) e.fontSizeValue.textContent = val + 'px'; + self.updatePreview(); + }); + } + + // Farben + if (e.textColorInput) { + e.textColorInput.addEventListener('input', function(ev) { + state.set('textColor', ev.target.value); + self.updatePreview(); + }); + } + if (e.frameColorInput) { + e.frameColorInput.addEventListener('input', function(ev) { + state.set('frameColor', ev.target.value); + self.updatePreview(); + }); + } + + // Rahmen-Skalierung + if (e.frameScaleInput) { + e.frameScaleInput.addEventListener('input', function(ev) { + var val = parseInt(ev.target.value); + state.set('frameScale', val); + if (e.frameScaleValue) e.frameScaleValue.textContent = val + '%'; + self.updatePreview(); + }); + } + + // Gesamt-Padding + if (e.paddingAllInput) { + e.paddingAllInput.addEventListener('input', function(ev) { + var val = parseInt(ev.target.value); + state.setMultiple({ + paddingTop: val, + paddingRight: val, + paddingBottom: val, + paddingLeft: val + }); + if (e.paddingAllValue) e.paddingAllValue.textContent = val + 'px'; + // Einzelne Slider synchronisieren + if (e.paddingTopInput) e.paddingTopInput.value = val; + if (e.paddingTopValue) e.paddingTopValue.textContent = val + 'px'; + if (e.paddingRightInput) e.paddingRightInput.value = val; + if (e.paddingRightValue) e.paddingRightValue.textContent = val + 'px'; + if (e.paddingBottomInput) e.paddingBottomInput.value = val; + if (e.paddingBottomValue) e.paddingBottomValue.textContent = val + 'px'; + if (e.paddingLeftInput) e.paddingLeftInput.value = val; + if (e.paddingLeftValue) e.paddingLeftValue.textContent = val + 'px'; + self.updatePreview(); + }); + } + + // Einzelne Paddings + this.bindPaddingSlider('paddingTopInput', 'paddingTopValue', 'paddingTop'); + this.bindPaddingSlider('paddingRightInput', 'paddingRightValue', 'paddingRight'); + this.bindPaddingSlider('paddingBottomInput', 'paddingBottomValue', 'paddingBottom'); + this.bindPaddingSlider('paddingLeftInput', 'paddingLeftValue', 'paddingLeft'); + + // Pfeil-Slider + this.bindSlider('arrowLengthInput', 'arrowLengthValue', 'arrowLength', 'px'); + this.bindSlider('arrowAngleInput', 'arrowAngleValue', 'arrowAngle', '\u00B0'); + this.bindSlider('arrowBendInput', 'arrowBendValue', 'arrowBend', '%'); + this.bindSlider('arrowSizeInput', 'arrowSizeValue', 'arrowSize', 'px'); + this.bindSlider('arrowTipLengthInput', 'arrowTipLengthValue', 'arrowTipLength', 'px'); + + // Shape-Buttons + e.shapeButtons.forEach(function(btn) { + btn.addEventListener('click', function() { + e.shapeButtons.forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + state.set('shape', btn.dataset.shape); + self.updateVisibility(); + self.updatePreview(); + }); + }); + + // Arrow-Buttons + e.arrowButtons.forEach(function(btn) { + btn.addEventListener('click', function() { + e.arrowButtons.forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + state.set('arrow', btn.dataset.arrow); + self.updateVisibility(); + self.updatePreview(); + self.updateStandaloneArrowPreview(); + }); + }); + + // Line-Style-Buttons + e.lineStyleButtons.forEach(function(btn) { + btn.addEventListener('click', function() { + e.lineStyleButtons.forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + state.set('lineStyle', btn.dataset.style); + self.updatePreview(); + }); + }); + + // Line-Weight-Buttons + e.lineWeightButtons.forEach(function(btn) { + btn.addEventListener('click', function() { + e.lineWeightButtons.forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + state.set('lineWeight', parseInt(btn.dataset.weight)); + self.updatePreview(); + }); + }); + }, + + bindSlider: function(inputKey, valueKey, stateKey, unit) { + var self = this; + var input = this.elements[inputKey]; + var valueEl = this.elements[valueKey]; + var state = window.TextGenState; + + if (input) { + input.addEventListener('input', function(ev) { + var val = parseInt(ev.target.value); + state.set(stateKey, val); + if (valueEl) valueEl.textContent = val + unit; + self.updatePreview(); + if (stateKey.indexOf('arrow') === 0) { + self.updateStandaloneArrowPreview(); + } + }); + } + }, + + bindPaddingSlider: function(inputKey, valueKey, stateKey) { + var self = this; + var input = this.elements[inputKey]; + var valueEl = this.elements[valueKey]; + var state = window.TextGenState; + + if (input) { + input.addEventListener('input', function(ev) { + var val = parseInt(ev.target.value); + state.set(stateKey, val); + if (valueEl) valueEl.textContent = val + 'px'; + self.updatePreview(); + }); + } + }, + + syncFromState: function() { + var e = this.elements; + var s = window.TextGenState.current; + + if (e.textInput) e.textInput.value = s.text; + if (e.fontSizeInput) e.fontSizeInput.value = s.fontSize; + if (e.fontSizeValue) e.fontSizeValue.textContent = s.fontSize + 'px'; + if (e.textColorInput) e.textColorInput.value = s.textColor; + if (e.frameColorInput) e.frameColorInput.value = s.frameColor; + if (e.frameScaleInput) e.frameScaleInput.value = s.frameScale; + if (e.frameScaleValue) e.frameScaleValue.textContent = s.frameScale + '%'; + + // Padding + if (e.paddingAllInput) e.paddingAllInput.value = s.paddingTop; + if (e.paddingAllValue) e.paddingAllValue.textContent = s.paddingTop + 'px'; + if (e.paddingTopInput) e.paddingTopInput.value = s.paddingTop; + if (e.paddingTopValue) e.paddingTopValue.textContent = s.paddingTop + 'px'; + if (e.paddingRightInput) e.paddingRightInput.value = s.paddingRight; + if (e.paddingRightValue) e.paddingRightValue.textContent = s.paddingRight + 'px'; + if (e.paddingBottomInput) e.paddingBottomInput.value = s.paddingBottom; + if (e.paddingBottomValue) e.paddingBottomValue.textContent = s.paddingBottom + 'px'; + if (e.paddingLeftInput) e.paddingLeftInput.value = s.paddingLeft; + if (e.paddingLeftValue) e.paddingLeftValue.textContent = s.paddingLeft + 'px'; + + // Pfeil + if (e.arrowLengthInput) e.arrowLengthInput.value = s.arrowLength; + if (e.arrowLengthValue) e.arrowLengthValue.textContent = s.arrowLength + 'px'; + if (e.arrowAngleInput) e.arrowAngleInput.value = s.arrowAngle; + if (e.arrowAngleValue) e.arrowAngleValue.textContent = s.arrowAngle + '\u00B0'; + if (e.arrowBendInput) e.arrowBendInput.value = s.arrowBend; + if (e.arrowBendValue) e.arrowBendValue.textContent = s.arrowBend + '%'; + if (e.arrowSizeInput) e.arrowSizeInput.value = s.arrowSize; + if (e.arrowSizeValue) e.arrowSizeValue.textContent = s.arrowSize + 'px'; + if (e.arrowTipLengthInput) e.arrowTipLengthInput.value = s.arrowTipLength; + if (e.arrowTipLengthValue) e.arrowTipLengthValue.textContent = s.arrowTipLength + 'px'; + + // Buttons aktivieren + this.activateButton(e.shapeButtons, 'shape', s.shape); + this.activateButton(e.arrowButtons, 'arrow', s.arrow); + this.activateButton(e.lineStyleButtons, 'style', s.lineStyle); + this.activateButtonByValue(e.lineWeightButtons, 'weight', s.lineWeight); + }, + + activateButton: function(buttons, dataAttr, value) { + buttons.forEach(function(btn) { + btn.classList.remove('active'); + if (btn.dataset[dataAttr] === value) { + btn.classList.add('active'); + } + }); + }, + + activateButtonByValue: function(buttons, dataAttr, value) { + buttons.forEach(function(btn) { + btn.classList.remove('active'); + if (parseInt(btn.dataset[dataAttr]) === value) { + btn.classList.add('active'); + } + }); + }, + + updateVisibility: function() { + var e = this.elements; + var s = window.TextGenState.current; + var hasShape = s.shape !== 'none'; + var hasArrow = s.arrow !== 'none'; + + if (e.frameScaleRow) e.frameScaleRow.style.display = hasShape ? 'flex' : 'none'; + if (e.paddingAllRow) e.paddingAllRow.style.display = hasShape ? 'flex' : 'none'; + if (e.paddingRow) e.paddingRow.style.display = hasShape ? 'flex' : 'none'; + if (e.arrowDetailsRow) e.arrowDetailsRow.style.display = hasArrow ? 'flex' : 'none'; + if (e.arrowDetailsRow2) e.arrowDetailsRow2.style.display = hasArrow ? 'flex' : 'none'; + }, + + updatePreview: function() { + var e = this.elements; + if (e.textPreview && window.SvgGenerator) { + var svg = window.SvgGenerator.generate(window.TextGenState.current); + e.textPreview.innerHTML = svg; + } + }, + + updateStandaloneArrowPreview: function() { + var e = this.elements; + var s = window.TextGenState.current; + + if (!e.standaloneArrowPreview) return; + + if (s.arrow === 'none') { + e.standaloneArrowPreview.innerHTML = '

Waehle einen Pfeil aus

'; + return; + } + + if (window.SvgGenerator) { + var svg = window.SvgGenerator.generateArrowOnly(s); + if (svg) { + e.standaloneArrowPreview.innerHTML = svg; + } + } + }, + + resetToDefaults: function() { + window.TextGenState.reset(); + this.syncFromState(); + this.updateVisibility(); + this.updatePreview(); + this.updateStandaloneArrowPreview(); + } +}; + +window.UiBindings = UiBindings; diff --git a/symbols/lib/svg.min.js b/symbols/lib/svg.min.js new file mode 100644 index 0000000..06f7123 --- /dev/null +++ b/symbols/lib/svg.min.js @@ -0,0 +1,13 @@ +/*! @svgdotjs/svg.js v3.2.5 MIT*/; +/*! +* @svgdotjs/svg.js - A lightweight library for manipulating and animating SVG. +* @version 3.2.5 +* https://svgjs.dev/ +* +* @copyright Wout Fierens +* @license MIT +* +* BUILT: Mon Sep 15 2025 18:20:57 GMT+0200 (Central European Summer Time) +*/ +var SVG=function(){"use strict";const t={},e=[];function n(e,i){if(Array.isArray(e))for(const t of e)n(t,i);else if("object"!=typeof e)r(Object.getOwnPropertyNames(i)),t[e]=Object.assign(t[e]||{},i);else for(const t in e)n(t,e[t])}function i(e){return t[e]||{}}function r(t){e.push(...t)}function s(t,e){let n;const i=t.length,r=[];for(n=0;nf.has(t.nodeName),m=(t,e,n={})=>{const i={...e};for(const t in i)i[t].valueOf()===n[t]&&delete i[t];Object.keys(i).length?t.node.setAttribute("data-svgjs",JSON.stringify(i)):(t.node.removeAttribute("data-svgjs"),t.node.removeAttribute("svgjs:data"))};var p={__proto__:null,capitalize:a,degrees:function(t){return 180*t/Math.PI%360},filter:o,getOrigin:c,isDescriptive:d,map:s,proportionalSize:l,radians:h,unCamelCase:u,writeDataToDom:m};const y="http://www.w3.org/2000/svg",w="http://www.w3.org/1999/xhtml",g="http://www.w3.org/2000/xmlns/",_="http://www.w3.org/1999/xlink";var x={__proto__:null,html:w,svg:y,xlink:_,xmlns:g};const b={window:"undefined"==typeof window?null:window,document:"undefined"==typeof document?null:document};function v(t=null,e=null){b.window=t,b.document=e}const M={};function O(){M.window=b.window,M.document=b.document}function k(){b.window=M.window,b.document=M.document}function T(){return b.window}class C{}const N={},S="___SYMBOL___ROOT___";function E(t,e=y){return b.document.createElementNS(e,t)}function j(t,e=!1){if(t instanceof C)return t;if("object"==typeof t)return z(t);if(null==t)return new N[S];if("string"==typeof t&&"<"!==t.trim().charAt(0))return z(b.document.querySelector(t));const n=e?b.document.createElement("div"):E("svg");return n.innerHTML=t.trim(),t=z(n.firstElementChild),n.removeChild(n.firstElementChild),t}function D(t,e){return e&&(e instanceof b.window.Node||e.ownerDocument&&e instanceof e.ownerDocument.defaultView.Node)?e:E(t)}function I(t){if(!t)return null;if(t.instance instanceof C)return t.instance;if("#document-fragment"===t.nodeName)return new N.Fragment(t);let e=a(t.nodeName||"Dom");return"LinearGradient"===e||"RadialGradient"===e?e="Gradient":N[e]||(e="Dom"),new N[e](t)}let z=I;function P(t,e=t.name,n=!1){return N[e]=t,n&&(N[S]=t),r(Object.getOwnPropertyNames(t.prototype)),t}function R(t){return N[t]}let L=1e3;function q(t){return"Svgjs"+a(t)+L++}function F(t){for(let e=t.children.length-1;e>=0;e--)F(t.children[e]);return t.id?(t.id=q(t.nodeName),t):t}function X(t,e){let n,i;for(i=(t=Array.isArray(t)?t:[t]).length-1;i>=0;i--)for(n in e)t[i].prototype[n]=e[n]}function Y(t){return function(...e){const n=e[e.length-1];return!n||n.constructor!==Object||n instanceof Array?t.apply(this,e):t.apply(this,e.slice(0,-1)).attr(n)}}n("Dom",{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},prev:function(){return this.siblings()[this.position()-1]},forward:function(){const t=this.position();return this.parent().add(this.remove(),t+1),this},backward:function(){const t=this.position();return this.parent().add(this.remove(),t?t-1:0),this},front:function(){return this.parent().add(this.remove()),this},back:function(){return this.parent().add(this.remove(),0),this},before:function(t){(t=j(t)).remove();const e=this.position();return this.parent().add(t,e),this},after:function(t){(t=j(t)).remove();const e=this.position();return this.parent().add(t,e+1),this},insertBefore:function(t){return(t=j(t)).before(this),this},insertAfter:function(t){return(t=j(t)).after(this),this}});const B=/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,H=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,V=/rgb\((\d+),(\d+),(\d+)\)/,$=/(#[a-z_][a-z0-9\-_]*)/i,U=/\)\s*,?\s*/,W=/\s/g,Q=/^#[a-f0-9]{3}$|^#[a-f0-9]{6}$/i,J=/^rgb\(/,Z=/^(\s+)?$/,K=/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,tt=/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,et=/[\s,]+/,nt=/[MLHVCSQTAZ]/i;var it={__proto__:null,delimiter:et,hex:H,isBlank:Z,isHex:Q,isImage:tt,isNumber:K,isPathLetter:nt,isRgb:J,numberAndUnit:B,reference:$,rgb:V,transforms:U,whitespace:W};function rt(t){const e=Math.round(t),n=Math.max(0,Math.min(255,e)).toString(16);return 1===n.length?"0"+n:n}function st(t,e){for(let n=e.length;n--;)if(null==t[e[n]])return!1;return!0}function ot(t,e,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+(e-t)*(2/3-n)*6:t}n("Dom",{classes:function(){const t=this.attr("class");return null==t?[]:t.trim().split(et)},hasClass:function(t){return-1!==this.classes().indexOf(t)},addClass:function(t){if(!this.hasClass(t)){const e=this.classes();e.push(t),this.attr("class",e.join(" "))}return this},removeClass:function(t){return this.hasClass(t)&&this.attr("class",this.classes().filter((function(e){return e!==t})).join(" ")),this},toggleClass:function(t){return this.hasClass(t)?this.removeClass(t):this.addClass(t)}}),n("Dom",{css:function(t,e){const n={};if(0===arguments.length)return this.node.style.cssText.split(/\s*;\s*/).filter((function(t){return!!t.length})).forEach((function(t){const e=t.split(/\s*:\s*/);n[e[0]]=e[1]})),n;if(arguments.length<2){if(Array.isArray(t)){for(const e of t){const t=e;n[e]=this.node.style.getPropertyValue(t)}return n}if("string"==typeof t)return this.node.style.getPropertyValue(t);if("object"==typeof t)for(const e in t)this.node.style.setProperty(e,null==t[e]||Z.test(t[e])?"":t[e])}return 2===arguments.length&&this.node.style.setProperty(t,null==e||Z.test(e)?"":e),this},show:function(){return this.css("display","")},hide:function(){return this.css("display","none")},visible:function(){return"none"!==this.css("display")}}),n("Dom",{data:function(t,e,n){if(null==t)return this.data(s(o(this.node.attributes,(t=>0===t.nodeName.indexOf("data-"))),(t=>t.nodeName.slice(5))));if(t instanceof Array){const e={};for(const n of t)e[n]=this.data(n);return e}if("object"==typeof t)for(e in t)this.data(e,t[e]);else if(arguments.length<2)try{return JSON.parse(this.attr("data-"+t))}catch(e){return this.attr("data-"+t)}else this.attr("data-"+t,null===e?null:!0===n||"string"==typeof e||"number"==typeof e?e:JSON.stringify(e));return this}}),n("Dom",{remember:function(t,e){if("object"==typeof arguments[0])for(const e in t)this.remember(e,t[e]);else{if(1===arguments.length)return this.memory()[t];this.memory()[t]=e}return this},forget:function(){if(0===arguments.length)this._memory={};else for(let t=arguments.length-1;t>=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory=this._memory||{}}});class ht{constructor(...t){this.init(...t)}static isColor(t){return t&&(t instanceof ht||this.isRgb(t)||this.test(t))}static isRgb(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b}static random(t="vibrant",e){const{random:n,round:i,sin:r,PI:s}=Math;if("vibrant"===t){const t=24*n()+57,e=38*n()+45,i=360*n();return new ht(t,e,i,"lch")}if("sine"===t){const t=i(80*r(2*s*(e=null==e?n():e)/.5+.01)+150),o=i(50*r(2*s*e/.5+4.6)+200),h=i(100*r(2*s*e/.5+2.3)+150);return new ht(t,o,h)}if("pastel"===t){const t=8*n()+86,e=17*n()+9,i=360*n();return new ht(t,e,i,"lch")}if("dark"===t){const t=10+10*n(),e=50*n()+86,i=360*n();return new ht(t,e,i,"lch")}if("rgb"===t){const t=255*n(),e=255*n(),i=255*n();return new ht(t,e,i)}if("lab"===t){const t=100*n(),e=256*n()-128,i=256*n()-128;return new ht(t,e,i,"lab")}if("grey"===t){const t=255*n();return new ht(t,t,t)}throw new Error("Unsupported random color mode")}static test(t){return"string"==typeof t&&(Q.test(t)||J.test(t))}cmyk(){const{_a:t,_b:e,_c:n}=this.rgb(),[i,r,s]=[t,e,n].map((t=>t/255)),o=Math.min(1-i,1-r,1-s);if(1===o)return new ht(0,0,0,1,"cmyk");return new ht((1-i-o)/(1-o),(1-r-o)/(1-o),(1-s-o)/(1-o),o,"cmyk")}hsl(){const{_a:t,_b:e,_c:n}=this.rgb(),[i,r,s]=[t,e,n].map((t=>t/255)),o=Math.max(i,r,s),h=Math.min(i,r,s),u=(o+h)/2,a=o===h,l=o-h;return new ht(360*(a?0:o===i?((r-s)/l+(r.5?l/(2-o-h):l/(o+h)),100*u,"hsl")}init(t=0,e=0,n=0,i=0,r="rgb"){if(t=t||0,this.space)for(const t in this.space)delete this[this.space[t]];if("number"==typeof t)r="string"==typeof i?i:r,i="string"==typeof i?0:i,Object.assign(this,{_a:t,_b:e,_c:n,_d:i,space:r});else if(t instanceof Array)this.space=e||("string"==typeof t[3]?t[3]:t[4])||"rgb",Object.assign(this,{_a:t[0],_b:t[1],_c:t[2],_d:t[3]||0});else if(t instanceof Object){const n=function(t,e){const n=st(t,"rgb")?{_a:t.r,_b:t.g,_c:t.b,_d:0,space:"rgb"}:st(t,"xyz")?{_a:t.x,_b:t.y,_c:t.z,_d:0,space:"xyz"}:st(t,"hsl")?{_a:t.h,_b:t.s,_c:t.l,_d:0,space:"hsl"}:st(t,"lab")?{_a:t.l,_b:t.a,_c:t.b,_d:0,space:"lab"}:st(t,"lch")?{_a:t.l,_b:t.c,_c:t.h,_d:0,space:"lch"}:st(t,"cmyk")?{_a:t.c,_b:t.m,_c:t.y,_d:t.k,space:"cmyk"}:{_a:0,_b:0,_c:0,space:"rgb"};return n.space=e||n.space,n}(t,e);Object.assign(this,n)}else if("string"==typeof t)if(J.test(t)){const e=t.replace(W,""),[n,i,r]=V.exec(e).slice(1,4).map((t=>parseInt(t)));Object.assign(this,{_a:n,_b:i,_c:r,_d:0,space:"rgb"})}else{if(!Q.test(t))throw Error("Unsupported string format, can't construct Color");{const e=t=>parseInt(t,16),[,n,i,r]=H.exec(function(t){return 4===t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}(t)).map(e);Object.assign(this,{_a:n,_b:i,_c:r,_d:0,space:"rgb"})}}const{_a:s,_b:o,_c:h,_d:u}=this,a="rgb"===this.space?{r:s,g:o,b:h}:"xyz"===this.space?{x:s,y:o,z:h}:"hsl"===this.space?{h:s,s:o,l:h}:"lab"===this.space?{l:s,a:o,b:h}:"lch"===this.space?{l:s,c:o,h:h}:"cmyk"===this.space?{c:s,m:o,y:h,k:u}:{};Object.assign(this,a)}lab(){const{x:t,y:e,z:n}=this.xyz();return new ht(116*e-16,500*(t-e),200*(e-n),"lab")}lch(){const{l:t,a:e,b:n}=this.lab(),i=Math.sqrt(e**2+n**2);let r=180*Math.atan2(n,e)/Math.PI;r<0&&(r*=-1,r=360-r);return new ht(t,i,r,"lch")}rgb(){if("rgb"===this.space)return this;if("lab"===(t=this.space)||"xyz"===t||"lch"===t){let{x:t,y:e,z:n}=this;if("lab"===this.space||"lch"===this.space){let{l:i,a:r,b:s}=this;if("lch"===this.space){const{c:t,h:e}=this,n=Math.PI/180;r=t*Math.cos(n*e),s=t*Math.sin(n*e)}const o=(i+16)/116,h=r/500+o,u=o-s/200,a=16/116,l=.008856,c=7.787;t=.95047*(h**3>l?h**3:(h-a)/c),e=1*(o**3>l?o**3:(o-a)/c),n=1.08883*(u**3>l?u**3:(u-a)/c)}const i=3.2406*t+-1.5372*e+-.4986*n,r=-.9689*t+1.8758*e+.0415*n,s=.0557*t+-.204*e+1.057*n,o=Math.pow,h=.0031308,u=i>h?1.055*o(i,1/2.4)-.055:12.92*i,a=r>h?1.055*o(r,1/2.4)-.055:12.92*r,l=s>h?1.055*o(s,1/2.4)-.055:12.92*s;return new ht(255*u,255*a,255*l)}if("hsl"===this.space){let{h:t,s:e,l:n}=this;if(t/=360,e/=100,n/=100,0===e){n*=255;return new ht(n,n,n)}const i=n<.5?n*(1+e):n+e-n*e,r=2*n-i,s=255*ot(r,i,t+1/3),o=255*ot(r,i,t),h=255*ot(r,i,t-1/3);return new ht(s,o,h)}if("cmyk"===this.space){const{c:t,m:e,y:n,k:i}=this,r=255*(1-Math.min(1,t*(1-i)+i)),s=255*(1-Math.min(1,e*(1-i)+i)),o=255*(1-Math.min(1,n*(1-i)+i));return new ht(r,s,o)}return this;var t}toArray(){const{_a:t,_b:e,_c:n,_d:i,space:r}=this;return[t,e,n,i,r]}toHex(){const[t,e,n]=this._clamped().map(rt);return`#${t}${e}${n}`}toRgb(){const[t,e,n]=this._clamped();return`rgb(${t},${e},${n})`}toString(){return this.toHex()}xyz(){const{_a:t,_b:e,_c:n}=this.rgb(),[i,r,s]=[t,e,n].map((t=>t/255)),o=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92,h=r>.04045?Math.pow((r+.055)/1.055,2.4):r/12.92,u=s>.04045?Math.pow((s+.055)/1.055,2.4):s/12.92,a=(.4124*o+.3576*h+.1805*u)/.95047,l=(.2126*o+.7152*h+.0722*u)/1,c=(.0193*o+.1192*h+.9505*u)/1.08883,f=a>.008856?Math.pow(a,1/3):7.787*a+16/116,d=l>.008856?Math.pow(l,1/3):7.787*l+16/116,m=c>.008856?Math.pow(c,1/3):7.787*c+16/116;return new ht(f,d,m,"xyz")}_clamped(){const{_a:t,_b:e,_c:n}=this.rgb(),{max:i,min:r,round:s}=Math;return[t,e,n].map((t=>i(0,r(s(t),255))))}}class ut{constructor(...t){this.init(...t)}clone(){return new ut(this)}init(t,e){const n=0,i=0,r=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:{x:t,y:e};return this.x=null==r.x?n:r.x,this.y=null==r.y?i:r.y,this}toArray(){return[this.x,this.y]}transform(t){return this.clone().transformO(t)}transformO(t){lt.isMatrixLike(t)||(t=new lt(t));const{x:e,y:n}=this;return this.x=t.a*e+t.c*n+t.e,this.y=t.b*e+t.d*n+t.f,this}}function at(t,e,n){return Math.abs(e-t)<1e-6}class lt{constructor(...t){this.init(...t)}static formatTransforms(t){const e="both"===t.flip||!0===t.flip,n=t.flip&&(e||"x"===t.flip)?-1:1,i=t.flip&&(e||"y"===t.flip)?-1:1,r=t.skew&&t.skew.length?t.skew[0]:isFinite(t.skew)?t.skew:isFinite(t.skewX)?t.skewX:0,s=t.skew&&t.skew.length?t.skew[1]:isFinite(t.skew)?t.skew:isFinite(t.skewY)?t.skewY:0,o=t.scale&&t.scale.length?t.scale[0]*n:isFinite(t.scale)?t.scale*n:isFinite(t.scaleX)?t.scaleX*n:n,h=t.scale&&t.scale.length?t.scale[1]*i:isFinite(t.scale)?t.scale*i:isFinite(t.scaleY)?t.scaleY*i:i,u=t.shear||0,a=t.rotate||t.theta||0,l=new ut(t.origin||t.around||t.ox||t.originX,t.oy||t.originY),c=l.x,f=l.y,d=new ut(t.position||t.px||t.positionX||NaN,t.py||t.positionY||NaN),m=d.x,p=d.y,y=new ut(t.translate||t.tx||t.translateX,t.ty||t.translateY),w=y.x,g=y.y,_=new ut(t.relative||t.rx||t.relativeX,t.ry||t.relativeY);return{scaleX:o,scaleY:h,skewX:r,skewY:s,shear:u,theta:a,rx:_.x,ry:_.y,tx:w,ty:g,ox:c,oy:f,px:m,py:p}}static fromArray(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}static isMatrixLike(t){return null!=t.a||null!=t.b||null!=t.c||null!=t.d||null!=t.e||null!=t.f}static matrixMultiply(t,e,n){const i=t.a*e.a+t.c*e.b,r=t.b*e.a+t.d*e.b,s=t.a*e.c+t.c*e.d,o=t.b*e.c+t.d*e.d,h=t.e+t.a*e.e+t.c*e.f,u=t.f+t.b*e.e+t.d*e.f;return n.a=i,n.b=r,n.c=s,n.d=o,n.e=h,n.f=u,n}around(t,e,n){return this.clone().aroundO(t,e,n)}aroundO(t,e,n){const i=t||0,r=e||0;return this.translateO(-i,-r).lmultiplyO(n).translateO(i,r)}clone(){return new lt(this)}decompose(t=0,e=0){const n=this.a,i=this.b,r=this.c,s=this.d,o=this.e,h=this.f,u=n*s-i*r,a=u>0?1:-1,l=a*Math.sqrt(n*n+i*i),c=Math.atan2(a*i,a*n),f=180/Math.PI*c,d=Math.cos(c),m=Math.sin(c),p=(n*r+i*s)/u,y=r*l/(p*n-i)||s*l/(p*i+n);return{scaleX:l,scaleY:y,shear:p,rotate:f,translateX:o-t+t*d*l+e*(p*d*l-m*y),translateY:h-e+t*m*l+e*(p*m*l+d*y),originX:t,originY:e,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f}}equals(t){if(t===this)return!0;const e=new lt(t);return at(this.a,e.a)&&at(this.b,e.b)&&at(this.c,e.c)&&at(this.d,e.d)&&at(this.e,e.e)&&at(this.f,e.f)}flip(t,e){return this.clone().flipO(t,e)}flipO(t,e){return"x"===t?this.scaleO(-1,1,e,0):"y"===t?this.scaleO(1,-1,0,e):this.scaleO(-1,-1,t,e||t)}init(t){const e=lt.fromArray([1,0,0,1,0,0]);return t=t instanceof Element?t.matrixify():"string"==typeof t?lt.fromArray(t.split(et).map(parseFloat)):Array.isArray(t)?lt.fromArray(t):"object"==typeof t&<.isMatrixLike(t)?t:"object"==typeof t?(new lt).transform(t):6===arguments.length?lt.fromArray([].slice.call(arguments)):e,this.a=null!=t.a?t.a:e.a,this.b=null!=t.b?t.b:e.b,this.c=null!=t.c?t.c:e.c,this.d=null!=t.d?t.d:e.d,this.e=null!=t.e?t.e:e.e,this.f=null!=t.f?t.f:e.f,this}inverse(){return this.clone().inverseO()}inverseO(){const t=this.a,e=this.b,n=this.c,i=this.d,r=this.e,s=this.f,o=t*i-e*n;if(!o)throw new Error("Cannot invert "+this);const h=i/o,u=-e/o,a=-n/o,l=t/o,c=-(h*r+a*s),f=-(u*r+l*s);return this.a=h,this.b=u,this.c=a,this.d=l,this.e=c,this.f=f,this}lmultiply(t){return this.clone().lmultiplyO(t)}lmultiplyO(t){const e=t instanceof lt?t:new lt(t);return lt.matrixMultiply(e,this,this)}multiply(t){return this.clone().multiplyO(t)}multiplyO(t){const e=t instanceof lt?t:new lt(t);return lt.matrixMultiply(this,e,this)}rotate(t,e,n){return this.clone().rotateO(t,e,n)}rotateO(t,e=0,n=0){t=h(t);const i=Math.cos(t),r=Math.sin(t),{a:s,b:o,c:u,d:a,e:l,f:c}=this;return this.a=s*i-o*r,this.b=o*i+s*r,this.c=u*i-a*r,this.d=a*i+u*r,this.e=l*i-c*r+n*r-e*i+e,this.f=c*i+l*r-e*r-n*i+n,this}scale(){return this.clone().scaleO(...arguments)}scaleO(t,e=t,n=0,i=0){3===arguments.length&&(i=n,n=e,e=t);const{a:r,b:s,c:o,d:h,e:u,f:a}=this;return this.a=r*t,this.b=s*e,this.c=o*t,this.d=h*e,this.e=u*t-n*t+n,this.f=a*e-i*e+i,this}shear(t,e,n){return this.clone().shearO(t,e,n)}shearO(t,e=0,n=0){const{a:i,b:r,c:s,d:o,e:h,f:u}=this;return this.a=i+r*t,this.c=s+o*t,this.e=h+u*t-n*t,this}skew(){return this.clone().skewO(...arguments)}skewO(t,e=t,n=0,i=0){3===arguments.length&&(i=n,n=e,e=t),t=h(t),e=h(e);const r=Math.tan(t),s=Math.tan(e),{a:o,b:u,c:a,d:l,e:c,f:f}=this;return this.a=o+u*r,this.b=u+o*s,this.c=a+l*r,this.d=l+a*s,this.e=c+f*r-i*r,this.f=f+c*s-n*s,this}skewX(t,e,n){return this.skew(t,0,e,n)}skewY(t,e,n){return this.skew(0,t,e,n)}toArray(){return[this.a,this.b,this.c,this.d,this.e,this.f]}toString(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"}transform(t){if(lt.isMatrixLike(t)){return new lt(t).multiplyO(this)}const e=lt.formatTransforms(t),{x:n,y:i}=new ut(e.ox,e.oy).transform(this),r=(new lt).translateO(e.rx,e.ry).lmultiplyO(this).translateO(-n,-i).scaleO(e.scaleX,e.scaleY).skewO(e.skewX,e.skewY).shearO(e.shear).rotateO(e.theta).translateO(n,i);if(isFinite(e.px)||isFinite(e.py)){const t=new ut(n,i).transform(r),s=isFinite(e.px)?e.px-t.x:0,o=isFinite(e.py)?e.py-t.y:0;r.translateO(s,o)}return r.translateO(e.tx,e.ty),r}translate(t,e){return this.clone().translateO(t,e)}translateO(t,e){return this.e+=t||0,this.f+=e||0,this}valueOf(){return{a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f}}}function ct(){if(!ct.nodes){const t=j().size(2,0);t.node.style.cssText=["opacity: 0","position: absolute","left: -100%","top: -100%","overflow: hidden"].join(";"),t.attr("focusable","false"),t.attr("aria-hidden","true");const e=t.path().node;ct.nodes={svg:t,path:e}}if(!ct.nodes.svg.node.parentNode){const t=b.document.body||b.document.documentElement;ct.nodes.svg.addTo(t)}return ct.nodes}function ft(t){return!(t.width||t.height||t.x||t.y)}P(lt,"Matrix");class dt{constructor(...t){this.init(...t)}addOffset(){return this.x+=b.window.pageXOffset,this.y+=b.window.pageYOffset,new dt(this)}init(t){return t="string"==typeof t?t.split(et).map(parseFloat):Array.isArray(t)?t:"object"==typeof t?[null!=t.left?t.left:t.x,null!=t.top?t.top:t.y,t.width,t.height]:4===arguments.length?[].slice.call(arguments):[0,0,0,0],this.x=t[0]||0,this.y=t[1]||0,this.width=this.w=t[2]||0,this.height=this.h=t[3]||0,this.x2=this.x+this.w,this.y2=this.y+this.h,this.cx=this.x+this.w/2,this.cy=this.y+this.h/2,this}isNulled(){return ft(this)}merge(t){const e=Math.min(this.x,t.x),n=Math.min(this.y,t.y),i=Math.max(this.x+this.width,t.x+t.width)-e,r=Math.max(this.y+this.height,t.y+t.height)-n;return new dt(e,n,i,r)}toArray(){return[this.x,this.y,this.width,this.height]}toString(){return this.x+" "+this.y+" "+this.width+" "+this.height}transform(t){t instanceof lt||(t=new lt(t));let e=1/0,n=-1/0,i=1/0,r=-1/0;return[new ut(this.x,this.y),new ut(this.x2,this.y),new ut(this.x,this.y2),new ut(this.x2,this.y2)].forEach((function(s){s=s.transform(t),e=Math.min(e,s.x),n=Math.max(n,s.x),i=Math.min(i,s.y),r=Math.max(r,s.y)})),new dt(e,i,n-e,r-i)}}function mt(t,e,n){let i;try{if(i=e(t.node),ft(i)&&((r=t.node)!==b.document&&!(b.document.documentElement.contains||function(t){for(;t.parentNode;)t=t.parentNode;return t===b.document}).call(b.document.documentElement,r)))throw new Error("Element not in the dom")}catch(e){i=n(t)}var r;return i}n({viewbox:{viewbox(t,e,n,i){return null==t?new dt(this.attr("viewBox")):this.attr("viewBox",new dt(t,e,n,i))},zoom(t,e){let{width:n,height:i}=this.attr(["width","height"]);if((n||i)&&"string"!=typeof n&&"string"!=typeof i||(n=this.node.clientWidth,i=this.node.clientHeight),!n||!i)throw new Error("Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element");const r=this.viewbox(),s=n/r.width,o=i/r.height,h=Math.min(s,o);if(null==t)return h;let u=h/t;u===1/0&&(u=Number.MAX_SAFE_INTEGER/100),e=e||new ut(n/2/s+r.x,i/2/o+r.y);const a=new dt(r).transform(new lt({scale:u,origin:e}));return this.viewbox(a)}}}),P(dt,"Box");class pt extends Array{constructor(t=[],...e){if(super(t,...e),"number"==typeof t)return this;this.length=0,this.push(...t)}}X([pt],{each(t,...e){return"function"==typeof t?this.map(((e,n,i)=>t.call(e,e,n,i))):this.map((n=>n[t](...e)))},toArray(){return Array.prototype.concat.apply([],this)}});const yt=["toArray","constructor","each"];function wt(t,e){return new pt(s((e||b.document).querySelectorAll(t),(function(t){return I(t)})))}pt.extend=function(t){t=t.reduce(((t,e)=>(yt.includes(e)||"_"===e[0]||(e in Array.prototype&&(t["$"+e]=Array.prototype[e]),t[e]=function(...t){return this.each(e,...t)}),t)),{}),X([pt],t)};let gt=0;const _t={};function xt(t){let e=t.getEventHolder();return e===b.window&&(e=_t),e.events||(e.events={}),e.events}function bt(t){return t.getEventTarget()}function vt(t){let e=t.getEventHolder();e===b.window&&(e=_t),e.events&&(e.events={})}function Mt(t,e,n,i,r){const s=n.bind(i||t),o=j(t),h=xt(o),u=bt(o);e=Array.isArray(e)?e:e.split(et),n._svgjsListenerId||(n._svgjsListenerId=++gt),e.forEach((function(t){const e=t.split(".")[0],i=t.split(".")[1]||"*";h[e]=h[e]||{},h[e][i]=h[e][i]||{},h[e][i][n._svgjsListenerId]=s,u.addEventListener(e,s,r||!1)}))}function At(t,e,n,i){const r=j(t),s=xt(r),o=bt(r);("function"!=typeof n||(n=n._svgjsListenerId))&&(e=Array.isArray(e)?e:(e||"").split(et)).forEach((function(t){const e=t&&t.split(".")[0],h=t&&t.split(".")[1];let u,a;if(n)s[e]&&s[e][h||"*"]&&(o.removeEventListener(e,s[e][h||"*"][n],i||!1),delete s[e][h||"*"][n]);else if(e&&h){if(s[e]&&s[e][h]){for(a in s[e][h])At(o,[e,h].join("."),a);delete s[e][h]}}else if(h)for(t in s)for(u in s[t])h===u&&At(o,[t,h].join("."));else if(e){if(s[e]){for(u in s[e])At(o,[e,u].join("."));delete s[e]}}else{for(t in s)At(o,t);vt(r)}}))}function Ot(t,e,n,i){const r=bt(t);return e instanceof b.window.Event||(e=new b.window.CustomEvent(e,{detail:n,cancelable:!0,...i})),r.dispatchEvent(e),e}class kt extends C{addEventListener(){}dispatch(t,e,n){return Ot(this,t,e,n)}dispatchEvent(t){const e=this.getEventHolder().events;if(!e)return!0;const n=e[t.type];for(const e in n)for(const i in n[e])n[e][i](t);return!t.defaultPrevented}fire(t,e,n){return this.dispatch(t,e,n),this}getEventHolder(){return this}getEventTarget(){return this}off(t,e,n){return At(this,t,e,n),this}on(t,e,n,i){return Mt(this,t,e,n,i),this}removeEventListener(){}}function Tt(){}P(kt,"EventTarget");const Ct={duration:400,ease:">",delay:0},Nt={"fill-opacity":1,"stroke-opacity":1,"stroke-width":0,"stroke-linejoin":"miter","stroke-linecap":"butt",fill:"#000000",stroke:"#000000",opacity:1,x:0,y:0,cx:0,cy:0,width:0,height:0,r:0,rx:0,ry:0,offset:0,"stop-opacity":1,"stop-color":"#000000","text-anchor":"start"};var St={__proto__:null,attrs:Nt,noop:Tt,timeline:Ct};class Et extends Array{constructor(...t){super(...t),this.init(...t)}clone(){return new this.constructor(this)}init(t){return"number"==typeof t||(this.length=0,this.push(...this.parse(t))),this}parse(t=[]){return t instanceof Array?t:t.trim().split(et).map(parseFloat)}toArray(){return Array.prototype.concat.apply([],this)}toSet(){return new Set(this)}toString(){return this.join(" ")}valueOf(){const t=[];return t.push(...this),t}}class jt{constructor(...t){this.init(...t)}convert(t){return new jt(this.value,t)}divide(t){return t=new jt(t),new jt(this/t,this.unit||t.unit)}init(t,e){return e=Array.isArray(t)?t[1]:e,t=Array.isArray(t)?t[0]:t,this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-34e37:34e37:"string"==typeof t?(e=t.match(B))&&(this.value=parseFloat(e[1]),"%"===e[5]?this.value/=100:"s"===e[5]&&(this.value*=1e3),this.unit=e[5]):t instanceof jt&&(this.value=t.valueOf(),this.unit=t.unit),this}minus(t){return t=new jt(t),new jt(this-t,this.unit||t.unit)}plus(t){return t=new jt(t),new jt(this+t,this.unit||t.unit)}times(t){return t=new jt(t),new jt(this*t,this.unit||t.unit)}toArray(){return[this.value,this.unit]}toJSON(){return this.toString()}toString(){return("%"===this.unit?~~(1e8*this.value)/1e6:"s"===this.unit?this.value/1e3:this.value)+this.unit}valueOf(){return this.value}}const Dt=new Set(["fill","stroke","color","bgcolor","stop-color","flood-color","lighting-color"]),It=[];class Dom extends kt{constructor(t,e){super(),this.node=t,this.type=t.nodeName,e&&t!==e&&this.attr(e)}add(t,e){return(t=j(t)).removeNamespace&&this.node instanceof b.window.SVGElement&&t.removeNamespace(),null==e?this.node.appendChild(t.node):t.node!==this.node.childNodes[e]&&this.node.insertBefore(t.node,this.node.childNodes[e]),this}addTo(t,e){return j(t).put(this,e)}children(){return new pt(s(this.node.children,(function(t){return I(t)})))}clear(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this}clone(t=!0,e=!0){this.writeDataToDom();let n=this.node.cloneNode(t);return e&&(n=F(n)),new this.constructor(n)}each(t,e){const n=this.children();let i,r;for(i=0,r=n.length;i=0}html(t,e){return this.xml(t,e,w)}id(t){return void 0!==t||this.node.id||(this.node.id=q(this.type)),this.attr("id",t)}index(t){return[].slice.call(this.node.childNodes).indexOf(t.node)}last(){return I(this.node.lastChild)}matches(t){const e=this.node,n=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector||null;return n&&n.call(e,t)}parent(t){let e=this;if(!e.node.parentNode)return null;if(e=I(e.node.parentNode),!t)return e;do{if("string"==typeof t?e.matches(t):e instanceof t)return e}while(e=I(e.node.parentNode));return e}put(t,e){return t=j(t),this.add(t,e),t}putIn(t,e){return j(t).add(this,e)}remove(){return this.parent()&&this.parent().removeElement(this),this}removeElement(t){return this.node.removeChild(t.node),this}replace(t){return t=j(t),this.node.parentNode&&this.node.parentNode.replaceChild(t.node,this.node),t}round(t=2,e=null){const n=10**t,i=this.attr(e);for(const t in i)"number"==typeof i[t]&&(i[t]=Math.round(i[t]*n)/n);return this.attr(i),this}svg(t,e){return this.xml(t,e,y)}toString(){return this.id()}words(t){return this.node.textContent=t,this}wrap(t){const e=this.parent();if(!e)return this.addTo(t);const n=e.index(this);return e.put(t,n).put(this)}writeDataToDom(){return this.each((function(){this.writeDataToDom()})),this}xml(t,e,n){if("boolean"==typeof t&&(n=e,e=t,t=null),null==t||"function"==typeof t){e=null==e||e,this.writeDataToDom();let n=this;if(null!=t){if(n=I(n.node.cloneNode(!0)),e){const e=t(n);if(n=e||n,!1===e)return""}n.each((function(){const e=t(this),n=e||this;!1===e?this.remove():e&&this!==n&&this.replace(n)}),!0)}return e?n.node.outerHTML:n.node.innerHTML}e=null!=e&&e;const i=E("wrapper",n),r=b.document.createDocumentFragment();i.innerHTML=t;for(let t=i.children.length;t--;)r.appendChild(i.firstElementChild);const s=this.parent();return e?this.replace(r)&&s:this.add(r)}}X(Dom,{attr:function(t,e,n){if(null==t){t={},e=this.node.attributes;for(const n of e)t[n.nodeName]=K.test(n.nodeValue)?parseFloat(n.nodeValue):n.nodeValue;return t}if(t instanceof Array)return t.reduce(((t,e)=>(t[e]=this.attr(e),t)),{});if("object"==typeof t&&t.constructor===Object)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return null==(e=this.node.getAttribute(t))?Nt[t]:K.test(e)?parseFloat(e):e;"number"==typeof(e=It.reduce(((e,n)=>n(t,e,this)),e))?e=new jt(e):Dt.has(t)&&ht.isColor(e)?e=new ht(e):e.constructor===Array&&(e=new Et(e)),"leading"===t?this.leading&&this.leading(e):"string"==typeof n?this.node.setAttributeNS(n,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!==t&&"x"!==t||this.rebuild()}return this},find:function(t){return wt(t,this.node)},findOne:function(t){return I(this.node.querySelector(t))}}),P(Dom,"Dom");class Element extends Dom{constructor(t,e){super(t,e),this.dom={},this.node.instance=this,(t.hasAttribute("data-svgjs")||t.hasAttribute("svgjs:data"))&&this.setData(JSON.parse(t.getAttribute("data-svgjs"))??JSON.parse(t.getAttribute("svgjs:data"))??{})}center(t,e){return this.cx(t).cy(e)}cx(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)}cy(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)}defs(){const t=this.root();return t&&t.defs()}dmove(t,e){return this.dx(t).dy(e)}dx(t=0){return this.x(new jt(t).plus(this.x()))}dy(t=0){return this.y(new jt(t).plus(this.y()))}getEventHolder(){return this}height(t){return this.attr("height",t)}move(t,e){return this.x(t).y(e)}parents(t=this.root()){const e="string"==typeof t;e||(t=j(t));const n=new pt;let i=this;for(;(i=i.parent())&&i.node!==b.document&&"#document-fragment"!==i.nodeName&&(n.push(i),e||i.node!==t.node)&&(!e||!i.matches(t));)if(i.node===this.root().node)return null;return n}reference(t){if(!(t=this.attr(t)))return null;const e=(t+"").match($);return e?j(e[1]):null}root(){const t=this.parent(R(S));return t&&t.root()}setData(t){return this.dom=t,this}size(t,e){const n=l(this,t,e);return this.width(new jt(n.width)).height(new jt(n.height))}width(t){return this.attr("width",t)}writeDataToDom(){return m(this,this.dom),super.writeDataToDom()}x(t){return this.attr("x",t)}y(t){return this.attr("y",t)}}X(Element,{bbox:function(){const t=mt(this,(t=>t.getBBox()),(t=>{try{const e=t.clone().addTo(ct().svg).show(),n=e.node.getBBox();return e.remove(),n}catch(e){throw new Error(`Getting bbox of element "${t.node.nodeName}" is not possible: ${e.toString()}`)}}));return new dt(t)},rbox:function(t){const e=mt(this,(t=>t.getBoundingClientRect()),(t=>{throw new Error(`Getting rbox of element "${t.node.nodeName}" is not possible`)})),n=new dt(e);return t?n.transform(t.screenCTM().inverseO()):n.addOffset()},inside:function(t,e){const n=this.bbox();return t>n.x&&e>n.y&&t=0;i--)null!=e[zt[t][i]]&&this.attr(zt.prefix(t,zt[t][i]),e[zt[t][i]]);return this},n(["Element","Runner"],e)})),n(["Element","Runner"],{matrix:function(t,e,n,i,r,s){return null==t?new lt(this):this.attr("transform",new lt(t,e,n,i,r,s))},rotate:function(t,e,n){return this.transform({rotate:t,ox:e,oy:n},!0)},skew:function(t,e,n,i){return 1===arguments.length||3===arguments.length?this.transform({skew:t,ox:e,oy:n},!0):this.transform({skew:[t,e],ox:n,oy:i},!0)},shear:function(t,e,n){return this.transform({shear:t,ox:e,oy:n},!0)},scale:function(t,e,n,i){return 1===arguments.length||3===arguments.length?this.transform({scale:t,ox:e,oy:n},!0):this.transform({scale:[t,e],ox:n,oy:i},!0)},translate:function(t,e){return this.transform({translate:[t,e]},!0)},relative:function(t,e){return this.transform({relative:[t,e]},!0)},flip:function(t="both",e="center"){return-1==="xybothtrue".indexOf(t)&&(e=t,t="both"),this.transform({flip:t,origin:e},!0)},opacity:function(t){return this.attr("opacity",t)}}),n("radius",{radius:function(t,e=t){return"radialGradient"===(this._element||this).type?this.attr("r",new jt(t)):this.rx(t).ry(e)}}),n("Path",{length:function(){return this.node.getTotalLength()},pointAt:function(t){return new ut(this.node.getPointAtLength(t))}}),n(["Element","Runner"],{font:function(t,e){if("object"==typeof t){for(e in t)this.font(e,t[e]);return this}return"leading"===t?this.leading(e):"anchor"===t?this.attr("text-anchor",e):"size"===t||"family"===t||"weight"===t||"stretch"===t||"variant"===t||"style"===t?this.attr("font-"+t,e):this.attr(t,e)}});n("Element",["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","mouseenter","mouseleave","touchstart","touchmove","touchleave","touchend","touchcancel","contextmenu","wheel","pointerdown","pointermove","pointerup","pointerleave","pointercancel"].reduce((function(t,e){return t[e]=function(t){return null===t?this.off(e):this.on(e,t),this},t}),{})),n("Element",{untransform:function(){return this.attr("transform",null)},matrixify:function(){const t=(this.attr("transform")||"").split(U).slice(0,-1).map((function(t){const e=t.trim().split("(");return[e[0],e[1].split(et).map((function(t){return parseFloat(t)}))]})).reverse().reduce((function(t,e){return"matrix"===e[0]?t.lmultiply(lt.fromArray(e[1])):t[e[0]].apply(t,e[1])}),new lt);return t},toParent:function(t,e){if(this===t)return this;if(d(this.node))return this.addTo(t,e);const n=this.screenCTM(),i=t.screenCTM().inverse();return this.addTo(t,e).untransform().transform(i.multiply(n)),this},toRoot:function(t){return this.toParent(this.root(),t)},transform:function(t,e){if(null==t||"string"==typeof t){const e=new lt(this).decompose();return null==t?e:e[t]}lt.isMatrixLike(t)||(t={...t,origin:c(t,this)});const n=new lt(!0===e?this:e||!1).transform(t);return this.attr("transform",n)}});class Container extends Element{flatten(){return this.each((function(){if(this instanceof Container)return this.flatten().ungroup()})),this}ungroup(t=this.parent(),e=t.index(this)){return e=-1===e?t.children().length:e,this.each((function(n,i){return i[i.length-n-1].toParent(t,e)})),this.remove()}}P(Container,"Container");class Defs extends Container{constructor(t,e=t){super(D("defs",t),e)}flatten(){return this}ungroup(){return this}}P(Defs,"Defs");class Shape extends Element{}function Pt(t){return this.attr("rx",t)}function Rt(t){return this.attr("ry",t)}function Lt(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())}function qt(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())}function Ft(t){return this.attr("cx",t)}function Xt(t){return this.attr("cy",t)}function Yt(t){return null==t?2*this.rx():this.rx(new jt(t).divide(2))}function Bt(t){return null==t?2*this.ry():this.ry(new jt(t).divide(2))}P(Shape,"Shape");var Gt={__proto__:null,cx:Ft,cy:Xt,height:Bt,rx:Pt,ry:Rt,width:Yt,x:Lt,y:qt};class Ellipse extends Shape{constructor(t,e=t){super(D("ellipse",t),e)}size(t,e){const n=l(this,t,e);return this.rx(new jt(n.width).divide(2)).ry(new jt(n.height).divide(2))}}X(Ellipse,Gt),n("Container",{ellipse:Y((function(t=0,e=t){return this.put(new Ellipse).size(t,e).move(0,0)}))}),P(Ellipse,"Ellipse");class Ht extends Dom{constructor(t=b.document.createDocumentFragment()){super(t)}xml(t,e,n){if("boolean"==typeof t&&(n=e,e=t,t=null),null==t||"function"==typeof t){const t=new Dom(E("wrapper",n));return t.add(this.node.cloneNode(!0)),t.xml(!1,n)}return super.xml(t,!1,n)}}function Vt(t,e){return"radialGradient"===(this._element||this).type?this.attr({fx:new jt(t),fy:new jt(e)}):this.attr({x1:new jt(t),y1:new jt(e)})}function $t(t,e){return"radialGradient"===(this._element||this).type?this.attr({cx:new jt(t),cy:new jt(e)}):this.attr({x2:new jt(t),y2:new jt(e)})}P(Ht,"Fragment");var Ut,Wt={__proto__:null,from:Vt,to:$t};class Gradient extends Container{constructor(t,e){super(D(t+"Gradient","string"==typeof t?null:t),e)}attr(t,e,n){return"transform"===t&&(t="gradientTransform"),super.attr(t,e,n)}bbox(){return new dt}targets(){return wt("svg [fill*="+this.id()+"]")}toString(){return this.url()}update(t){return this.clear(),"function"==typeof t&&t.call(this,this),this}url(){return"url(#"+this.id()+")"}}X(Gradient,Wt),n({Container:{gradient(...t){return this.defs().gradient(...t)}},Defs:{gradient:Y((function(t,e){return this.put(new Gradient(t)).update(e)}))}}),P(Gradient,"Gradient");class Pattern extends Container{constructor(t,e=t){super(D("pattern",t),e)}attr(t,e,n){return"transform"===t&&(t="patternTransform"),super.attr(t,e,n)}bbox(){return new dt}targets(){return wt("svg [fill*="+this.id()+"]")}toString(){return this.url()}update(t){return this.clear(),"function"==typeof t&&t.call(this,this),this}url(){return"url(#"+this.id()+")"}}n({Container:{pattern(...t){return this.defs().pattern(...t)}},Defs:{pattern:Y((function(t,e,n){return this.put(new Pattern).update(n).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}))}}),P(Pattern,"Pattern");class Image extends Shape{constructor(t,e=t){super(D("image",t),e)}load(t,e){if(!t)return this;const n=new b.window.Image;return Mt(n,"load",(function(t){const i=this.parent(Pattern);0===this.width()&&0===this.height()&&this.size(n.width,n.height),i instanceof Pattern&&0===i.width()&&0===i.height()&&i.size(this.width(),this.height()),"function"==typeof e&&e.call(this,t)}),this),Mt(n,"load error",(function(){At(n)})),this.attr("href",n.src=t,_)}}Ut=function(t,e,n){return"fill"!==t&&"stroke"!==t||tt.test(e)&&(e=n.root().defs().image(e)),e instanceof Image&&(e=n.root().defs().pattern(0,0,(t=>{t.add(e)}))),e},It.push(Ut),n({Container:{image:Y((function(t,e){return this.put(new Image).size(0,0).load(t,e)}))}}),P(Image,"Image");class Qt extends Et{bbox(){let t=-1/0,e=-1/0,n=1/0,i=1/0;return this.forEach((function(r){t=Math.max(r[0],t),e=Math.max(r[1],e),n=Math.min(r[0],n),i=Math.min(r[1],i)})),new dt(n,i,t-n,e-i)}move(t,e){const n=this.bbox();if(t-=n.x,e-=n.y,!isNaN(t)&&!isNaN(e))for(let n=this.length-1;n>=0;n--)this[n]=[this[n][0]+t,this[n][1]+e];return this}parse(t=[0,0]){const e=[];(t=t instanceof Array?Array.prototype.concat.apply([],t):t.trim().split(et).map(parseFloat)).length%2!=0&&t.pop();for(let n=0,i=t.length;n=0;n--)i.width&&(this[n][0]=(this[n][0]-i.x)*t/i.width+i.x),i.height&&(this[n][1]=(this[n][1]-i.y)*e/i.height+i.y);return this}toLine(){return{x1:this[0][0],y1:this[0][1],x2:this[1][0],y2:this[1][1]}}toString(){const t=[];for(let e=0,n=this.length;e":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return 1-Math.cos(t*Math.PI/2)},bezier:function(t,e,n,i){return function(r){return r<0?t>0?e/t*r:n>0?i/n*r:0:r>1?n<1?(1-i)/(1-n)*r+(i-n)/(1-n):t<1?(1-e)/(1-t)*r+(e-t)/(1-t):1:3*r*(1-r)**2*e+3*r**2*(1-r)*i+r**3}},steps:function(t,e="end"){e=e.split("-").reverse()[0];let n=t;return"none"===e?--n:"both"===e&&++n,(i,r=!1)=>{let s=Math.floor(i*t);const o=i*s%1==0;return"start"!==e&&"both"!==e||++s,r&&o&&--s,i>=0&&s<0&&(s=0),i<=1&&s>n&&(s=n),s/n}}};class te{done(){return!1}}class ee extends te{constructor(t=Ct.ease){super(),this.ease=Kt[t]||t}step(t,e,n){return"number"!=typeof t?n<1?t:e:t+(e-t)*this.ease(n)}}class ne extends te{constructor(t){super(),this.stepper=t}done(t){return t.done}step(t,e,n,i){return this.stepper(t,e,n,i)}}function ie(){const t=(this._duration||500)/1e3,e=this._overshoot||0,n=Math.PI,i=Math.log(e/100+1e-10),r=-i/Math.sqrt(n*n+i*i),s=3.9/(r*t);this.d=2*r*s,this.k=s*s}class re extends ne{constructor(t=500,e=0){super(),this.duration(t).overshoot(e)}step(t,e,n,i){if("string"==typeof t)return t;if(i.done=n===1/0,n===1/0)return e;if(0===n)return t;n>100&&(n=16),n/=1e3;const r=i.velocity||0,s=-this.d*r-this.k*(t-e),o=t+r*n+s*n*n/2;return i.velocity=r+s*n,i.done=Math.abs(e-o)+Math.abs(r)<.002,i.done?e:o}}X(re,{duration:Zt("_duration",ie),overshoot:Zt("_overshoot",ie)});class se extends ne{constructor(t=.1,e=.01,n=0,i=1e3){super(),this.p(t).i(e).d(n).windup(i)}step(t,e,n,i){if("string"==typeof t)return t;if(i.done=n===1/0,n===1/0)return e;if(0===n)return t;const r=e-t;let s=(i.integral||0)+r*n;const o=(r-(i.error||0))/n,h=this._windup;return!1!==h&&(s=Math.max(-h,Math.min(s,h))),i.error=r,i.integral=s,i.done=Math.abs(r)<.001,i.done?e:t+(this.P*r+this.I*s+this.D*o)}}X(se,{windup:Zt("_windup"),p:Zt("P"),i:Zt("I"),d:Zt("D")});const oe={M:2,L:2,H:1,V:1,C:6,S:4,Q:4,T:2,A:7,Z:0},he={M:function(t,e,n){return e.x=n.x=t[0],e.y=n.y=t[1],["M",e.x,e.y]},L:function(t,e){return e.x=t[0],e.y=t[1],["L",t[0],t[1]]},H:function(t,e){return e.x=t[0],["H",t[0]]},V:function(t,e){return e.y=t[0],["V",t[0]]},C:function(t,e){return e.x=t[4],e.y=t[5],["C",t[0],t[1],t[2],t[3],t[4],t[5]]},S:function(t,e){return e.x=t[2],e.y=t[3],["S",t[0],t[1],t[2],t[3]]},Q:function(t,e){return e.x=t[2],e.y=t[3],["Q",t[0],t[1],t[2],t[3]]},T:function(t,e){return e.x=t[0],e.y=t[1],["T",t[0],t[1]]},Z:function(t,e,n){return e.x=n.x,e.y=n.y,["Z"]},A:function(t,e){return e.x=t[5],e.y=t[6],["A",t[0],t[1],t[2],t[3],t[4],t[5],t[6]]}},ue="mlhvqtcsaz".split("");for(let t=0,e=ue.length;t=0;i--)n=this[i][0],"M"===n||"L"===n||"T"===n?(this[i][1]+=t,this[i][2]+=e):"H"===n?this[i][1]+=t:"V"===n?this[i][1]+=e:"C"===n||"S"===n||"Q"===n?(this[i][1]+=t,this[i][2]+=e,this[i][3]+=t,this[i][4]+=e,"C"===n&&(this[i][5]+=t,this[i][6]+=e)):"A"===n&&(this[i][6]+=t,this[i][7]+=e);return this}parse(t="M0 0"){return Array.isArray(t)&&(t=Array.prototype.concat.apply([],t).toString()),function(t,e=!0){let n=0,i="";const r={segment:[],inNumber:!1,number:"",lastToken:"",inSegment:!1,segments:[],pointSeen:!1,hasExponent:!1,absolute:e,p0:new ut,p:new ut};for(;r.lastToken=i,i=t.charAt(n++);)if(r.inSegment||!le(r,i))if("."!==i)if(isNaN(parseInt(i)))if(pe.has(i))r.inNumber&&ce(r,!1);else if("-"!==i&&"+"!==i)if("E"!==i.toUpperCase()){if(nt.test(i)){if(r.inNumber)ce(r,!1);else{if(!ae(r))throw new Error("parser Error");fe(r)}--n}}else r.number+=i,r.hasExponent=!0;else{if(r.inNumber&&!me(r)){ce(r,!1),--n;continue}r.number+=i,r.inNumber=!0}else{if("0"===r.number||de(r)){r.inNumber=!0,r.number=i,ce(r,!0);continue}r.inNumber=!0,r.number+=i}else{if(r.pointSeen||r.hasExponent){ce(r,!1),--n;continue}r.inNumber=!0,r.pointSeen=!0,r.number+=i}return r.inNumber&&ce(r,!1),r.inSegment&&ae(r)&&fe(r),r.segments}(t)}size(t,e){const n=this.bbox();let i,r;for(n.width=0===n.width?1:n.width,n.height=0===n.height?1:n.height,i=this.length-1;i>=0;i--)r=this[i][0],"M"===r||"L"===r||"T"===r?(this[i][1]=(this[i][1]-n.x)*t/n.width+n.x,this[i][2]=(this[i][2]-n.y)*e/n.height+n.y):"H"===r?this[i][1]=(this[i][1]-n.x)*t/n.width+n.x:"V"===r?this[i][1]=(this[i][1]-n.y)*e/n.height+n.y:"C"===r||"S"===r||"Q"===r?(this[i][1]=(this[i][1]-n.x)*t/n.width+n.x,this[i][2]=(this[i][2]-n.y)*e/n.height+n.y,this[i][3]=(this[i][3]-n.x)*t/n.width+n.x,this[i][4]=(this[i][4]-n.y)*e/n.height+n.y,"C"===r&&(this[i][5]=(this[i][5]-n.x)*t/n.width+n.x,this[i][6]=(this[i][6]-n.y)*e/n.height+n.y)):"A"===r&&(this[i][1]=this[i][1]*t/n.width,this[i][2]=this[i][2]*e/n.height,this[i][6]=(this[i][6]-n.x)*t/n.width+n.x,this[i][7]=(this[i][7]-n.y)*e/n.height+n.y);return this}toString(){return function(t){let e="";for(let n=0,i=t.length;n{const e=typeof t;return"number"===e?jt:"string"===e?ht.isColor(t)?ht:et.test(t)?nt.test(t)?ye:Et:B.test(t)?jt:_e:Me.indexOf(t.constructor)>-1?t.constructor:Array.isArray(t)?Et:"object"===e?ve:_e};class ge{constructor(t){this._stepper=t||new ee("-"),this._from=null,this._to=null,this._type=null,this._context=null,this._morphObj=null}at(t){return this._morphObj.morph(this._from,this._to,t,this._stepper,this._context)}done(){return this._context.map(this._stepper.done).reduce((function(t,e){return t&&e}),!0)}from(t){return null==t?this._from:(this._from=this._set(t),this)}stepper(t){return null==t?this._stepper:(this._stepper=t,this)}to(t){return null==t?this._to:(this._to=this._set(t),this)}type(t){return null==t?this._type:(this._type=t,this)}_set(t){this._type||this.type(we(t));let e=new this._type(t);return this._type===ht&&(e=this._to?e[this._to[4]]():this._from?e[this._from[4]]():e),this._type===ve&&(e=this._to?e.align(this._to):this._from?e.align(this._from):e),e=e.toConsumable(),this._morphObj=this._morphObj||new this._type,this._context=this._context||Array.apply(null,Array(e.length)).map(Object).map((function(t){return t.done=!0,t})),e}}class _e{constructor(...t){this.init(...t)}init(t){return t=Array.isArray(t)?t[0]:t,this.value=t,this}toArray(){return[this.value]}valueOf(){return this.value}}class xe{constructor(...t){this.init(...t)}init(t){return Array.isArray(t)&&(t={scaleX:t[0],scaleY:t[1],shear:t[2],rotate:t[3],translateX:t[4],translateY:t[5],originX:t[6],originY:t[7]}),Object.assign(this,xe.defaults,t),this}toArray(){const t=this;return[t.scaleX,t.scaleY,t.shear,t.rotate,t.translateX,t.translateY,t.originX,t.originY]}}xe.defaults={scaleX:1,scaleY:1,shear:0,rotate:0,translateX:0,translateY:0,originX:0,originY:0};const be=(t,e)=>t[0]e[0]?1:0;class ve{constructor(...t){this.init(...t)}align(t){const e=this.values;for(let n=0,i=e.length;nt.concat(e)),[]),this}toArray(){return this.values}valueOf(){const t={},e=this.values;for(;e.length;){const n=e.shift(),i=e.shift(),r=e.shift(),s=e.splice(0,r);t[n]=new i(s)}return t}}const Me=[_e,xe,ve];function Ae(t=[]){Me.push(...[].concat(t))}function Oe(){X(Me,{to(t){return(new ge).type(this.constructor).from(this.toArray()).to(t)},fromArray(t){return this.init(t),this},toConsumable(){return this.toArray()},morph(t,e,n,i,r){return this.fromArray(t.map((function(t,s){return i.step(t,e[s],n,r[s],r)})))}})}class Path extends Shape{constructor(t,e=t){super(D("path",t),e)}array(){return this._array||(this._array=new ye(this.attr("d")))}clear(){return delete this._array,this}height(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}move(t,e){return this.attr("d",this.array().move(t,e))}plot(t){return null==t?this.array():this.clear().attr("d","string"==typeof t?t:this._array=new ye(t))}size(t,e){const n=l(this,t,e);return this.attr("d",this.array().size(n.width,n.height))}width(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)}x(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)}y(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)}}Path.prototype.MorphArray=ye,n({Container:{path:Y((function(t){return this.put(new Path).plot(t||new ye)}))}}),P(Path,"Path");var ke={__proto__:null,array:function(){return this._array||(this._array=new Qt(this.attr("points")))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("points",this.array().move(t,e))},plot:function(t){return null==t?this.array():this.clear().attr("points","string"==typeof t?t:this._array=new Qt(t))},size:function(t,e){const n=l(this,t,e);return this.attr("points",this.array().size(n.width,n.height))}};class Polygon extends Shape{constructor(t,e=t){super(D("polygon",t),e)}}n({Container:{polygon:Y((function(t){return this.put(new Polygon).plot(t||new Qt)}))}}),X(Polygon,Jt),X(Polygon,ke),P(Polygon,"Polygon");class Polyline extends Shape{constructor(t,e=t){super(D("polyline",t),e)}}n({Container:{polyline:Y((function(t){return this.put(new Polyline).plot(t||new Qt)}))}}),X(Polyline,Jt),X(Polyline,ke),P(Polyline,"Polyline");class Rect extends Shape{constructor(t,e=t){super(D("rect",t),e)}}X(Rect,{rx:Pt,ry:Rt}),n({Container:{rect:Y((function(t,e){return this.put(new Rect).size(t,e)}))}}),P(Rect,"Rect");class Te{constructor(){this._first=null,this._last=null}first(){return this._first&&this._first.value}last(){return this._last&&this._last.value}push(t){const e=void 0!==t.next?t:{value:t,next:null,prev:null};return this._last?(e.prev=this._last,this._last.next=e,this._last=e):(this._last=e,this._first=e),e}remove(t){t.prev&&(t.prev.next=t.next),t.next&&(t.next.prev=t.prev),t===this._last&&(this._last=t.prev),t===this._first&&(this._first=t.next),t.prev=null,t.next=null}shift(){const t=this._first;return t?(this._first=t.next,this._first&&(this._first.prev=null),this._last=this._first?this._last:null,t.value):null}}const Ce={nextDraw:null,frames:new Te,timeouts:new Te,immediates:new Te,timer:()=>b.window.performance||b.window.Date,transforms:[],frame(t){const e=Ce.frames.push({run:t});return null===Ce.nextDraw&&(Ce.nextDraw=b.window.requestAnimationFrame(Ce._draw)),e},timeout(t,e){e=e||0;const n=Ce.timer().now()+e,i=Ce.timeouts.push({run:t,time:n});return null===Ce.nextDraw&&(Ce.nextDraw=b.window.requestAnimationFrame(Ce._draw)),i},immediate(t){const e=Ce.immediates.push(t);return null===Ce.nextDraw&&(Ce.nextDraw=b.window.requestAnimationFrame(Ce._draw)),e},cancelFrame(t){null!=t&&Ce.frames.remove(t)},clearTimeout(t){null!=t&&Ce.timeouts.remove(t)},cancelImmediate(t){null!=t&&Ce.immediates.remove(t)},_draw(t){let e=null;const n=Ce.timeouts.last();for(;(e=Ce.timeouts.shift())&&(t>=e.time?e.run():Ce.timeouts.push(e),e!==n););let i=null;const r=Ce.frames.last();for(;i!==r&&(i=Ce.frames.shift());)i.run(t);let s=null;for(;s=Ce.immediates.shift();)s();Ce.nextDraw=Ce.timeouts.first()||Ce.frames.first()?b.window.requestAnimationFrame(Ce._draw):null}},Ne=function(t){const e=t.start,n=t.runner.duration();return{start:e,duration:n,end:e+n,runner:t.runner}},Se=function(){const t=b.window;return(t.performance||t.Date).now()};class Ee extends kt{constructor(t=Se){super(),this._timeSource=t,this.terminate()}active(){return!!this._nextFrame}finish(){return this.time(this.getEndTimeOfTimeline()+1),this.pause()}getEndTime(){const t=this.getLastRunnerInfo(),e=t?t.runner.duration():0;return(t?t.start:this._time)+e}getEndTimeOfTimeline(){const t=this._runners.map((t=>t.start+t.runner.duration()));return Math.max(0,...t)}getLastRunnerInfo(){return this.getRunnerInfoById(this._lastRunnerId)}getRunnerInfoById(t){return this._runners[this._runnerIds.indexOf(t)]||null}pause(){return this._paused=!0,this._continue()}persist(t){return null==t?this._persist:(this._persist=t,this)}play(){return this._paused=!1,this.updateTime()._continue()}reverse(t){const e=this.speed();if(null==t)return this.speed(-e);const n=Math.abs(e);return this.speed(t?-n:n)}schedule(t,e,n){if(null==t)return this._runners.map(Ne);let i=0;const r=this.getEndTime();if(e=e||0,null==n||"last"===n||"after"===n)i=r;else if("absolute"===n||"start"===n)i=e,e=0;else if("now"===n)i=this._time;else if("relative"===n){const n=this.getRunnerInfoById(t.id);n&&(i=n.start+e,e=0)}else{if("with-last"!==n)throw new Error('Invalid value for the "when" parameter');{const t=this.getLastRunnerInfo();i=t?t.start:this._time}}t.unschedule(),t.timeline(this);const s=t.persist(),o={persist:null===s?this._persist:s,start:i+e,runner:t};return this._lastRunnerId=t.id,this._runners.push(o),this._runners.sort(((t,e)=>t.start-e.start)),this._runnerIds=this._runners.map((t=>t.runner.id)),this.updateTime()._continue(),this}seek(t){return this.time(this._time+t)}source(t){return null==t?this._timeSource:(this._timeSource=t,this)}speed(t){return null==t?this._speed:(this._speed=t,this)}stop(){return this.time(0),this.pause()}time(t){return null==t?this._time:(this._time=t,this._continue(!0))}unschedule(t){const e=this._runnerIds.indexOf(t.id);return e<0||(this._runners.splice(e,1),this._runnerIds.splice(e,1),t.timeline(null)),this}updateTime(){return this.active()||(this._lastSourceTime=this._timeSource()),this}_continue(t=!1){return Ce.cancelFrame(this._nextFrame),this._nextFrame=null,t?this._stepImmediate():(this._paused||(this._nextFrame=Ce.frame(this._step)),this)}_stepFn(t=!1){const e=this._timeSource();let n=e-this._lastSourceTime;t&&(n=0);const i=this._speed*n+(this._time-this._lastStepTime);this._lastSourceTime=e,t||(this._time+=i,this._time=this._time<0?0:this._time),this._lastStepTime=this._time,this.fire("time",this._time);for(let t=this._runners.length;t--;){const e=this._runners[t],n=e.runner;this._time-e.start<=0&&n.reset()}let r=!1;for(let t=0,e=this._runners.length;t0?this._continue():(this.pause(),this.fire("finished")),this}terminate(){this._startTime=0,this._speed=1,this._persist=0,this._nextFrame=null,this._paused=!0,this._runners=[],this._runnerIds=[],this._lastRunnerId=-1,this._time=0,this._lastSourceTime=0,this._lastStepTime=0,this._step=this._stepFn.bind(this,!1),this._stepImmediate=this._stepFn.bind(this,!0)}}n({Element:{timeline:function(t){return null==t?(this._timeline=this._timeline||new Ee,this._timeline):(this._timeline=t,this)}}});class je extends kt{constructor(t){super(),this.id=je.id++,t="function"==typeof(t=null==t?Ct.duration:t)?new ne(t):t,this._element=null,this._timeline=null,this.done=!1,this._queue=[],this._duration="number"==typeof t&&t,this._isDeclarative=t instanceof ne,this._stepper=this._isDeclarative?t:new ee,this._history={},this.enabled=!0,this._time=0,this._lastTime=0,this._reseted=!0,this.transforms=new lt,this.transformId=1,this._haveReversed=!1,this._reverse=!1,this._loopsDone=0,this._swing=!1,this._wait=0,this._times=1,this._frameId=null,this._persist=!!this._isDeclarative||null}static sanitise(t,e,n){let i=1,r=!1,s=0;return e=e??Ct.delay,n=n||"last","object"!=typeof(t=t??Ct.duration)||t instanceof te||(e=t.delay??e,n=t.when??n,r=t.swing||r,i=t.times??i,s=t.wait??s,t=t.duration??Ct.duration),{duration:t,delay:e,swing:r,times:i,wait:s,when:n}}active(t){return null==t?this.enabled:(this.enabled=t,this)}addTransform(t){return this.transforms.lmultiplyO(t),this}after(t){return this.on("finished",t)}animate(t,e,n){const i=je.sanitise(t,e,n),r=new je(i.duration);return this._timeline&&r.timeline(this._timeline),this._element&&r.element(this._element),r.loop(i).schedule(i.delay,i.when)}clearTransform(){return this.transforms=new lt,this}clearTransformsFromQueue(){this.done&&this._timeline&&this._timeline._runnerIds.includes(this.id)||(this._queue=this._queue.filter((t=>!t.isTransform)))}delay(t){return this.animate(0,t)}duration(){return this._times*(this._wait+this._duration)-this._wait}during(t){return this.queue(null,t)}ease(t){return this._stepper=new ee(t),this}element(t){return null==t?this._element:(this._element=t,t._prepareRunner(),this)}finish(){return this.step(1/0)}loop(t,e,n){return"object"==typeof t&&(e=t.swing,n=t.wait,t=t.times),this._times=t||1/0,this._swing=e||!1,this._wait=n||0,!0===this._times&&(this._times=1/0),this}loops(t){const e=this._duration+this._wait;if(null==t){const t=Math.floor(this._time/e),n=(this._time-t*e)/this._duration;return Math.min(t+n,this._times)}const n=t%1,i=e*Math.floor(t)+this._duration*n;return this.time(i)}persist(t){return null==t?this._persist:(this._persist=t,this)}position(t){const e=this._time,n=this._duration,i=this._wait,r=this._times,s=this._swing,o=this._reverse;let h;if(null==t){const t=function(t){const e=s*Math.floor(t%(2*(i+n))/(i+n)),r=e&&!o||!e&&o,h=Math.pow(-1,r)*(t%(i+n))/n+r;return Math.max(Math.min(h,1),0)},u=r*(i+n)-i;return h=e<=0?Math.round(t(1e-5)):e=0;this._lastPosition=e;const i=this.duration(),r=this._lastTime<=0&&this._time>0,s=this._lastTime=i;this._lastTime=this._time,r&&this.fire("start",this);const o=this._isDeclarative;this.done=!o&&!s&&this._time>=i,this._reseted=!1;let h=!1;return(n||o)&&(this._initialise(n),this.transforms=new lt,h=this._run(o?t:e),this.fire("step",this)),this.done=this.done||h&&o,s&&this.fire("finished",this),this}time(t){if(null==t)return this._time;const e=t-this._time;return this.step(e),this}timeline(t){return void 0===t?this._timeline:(this._timeline=t,this)}unschedule(){const t=this.timeline();return t&&t.unschedule(this),this}_initialise(t){if(t||this._isDeclarative)for(let e=0,n=this._queue.length;et.lmultiplyO(e),ze=t=>t.transforms;function Pe(){const t=this._transformationRunners.runners.map(ze).reduce(Ie,new lt);this.transform(t),this._transformationRunners.merge(),1===this._transformationRunners.length()&&(this._frameId=null)}class Re{constructor(){this.runners=[],this.ids=[]}add(t){if(this.runners.includes(t))return;const e=t.id+1;return this.runners.push(t),this.ids.push(e),this}clearBefore(t){const e=this.ids.indexOf(t+1)||1;return this.ids.splice(0,e,0),this.runners.splice(0,e,new De).forEach((t=>t.clearTransformsFromQueue())),this}edit(t,e){const n=this.ids.indexOf(t+1);return this.ids.splice(n,1,t+1),this.runners.splice(n,1,e),this}getByID(t){return this.runners[this.ids.indexOf(t+1)]}length(){return this.ids.length}merge(){let t=null;for(let e=0;ee.id<=t.id)).map(ze).reduce(Ie,new lt)},_addRunner(t){this._transformationRunners.add(t),Ce.cancelImmediate(this._frameId),this._frameId=Ce.immediate(Pe.bind(this))},_prepareRunner(){null==this._frameId&&(this._transformationRunners=(new Re).add(new De(new lt(this))))}}});X(je,{attr(t,e){return this.styleAttr("attr",t,e)},css(t,e){return this.styleAttr("css",t,e)},styleAttr(t,e,n){if("string"==typeof e)return this.styleAttr(t,{[e]:n});let i=e;if(this._tryRetarget(t,i))return this;let r=new ge(this._stepper).to(i),s=Object.keys(i);return this.queue((function(){r=r.from(this.element()[t](s))}),(function(e){return this.element()[t](r.at(e).valueOf()),r.done()}),(function(e){const n=Object.keys(e),o=(h=s,n.filter((t=>!h.includes(t))));var h;if(o.length){const e=this.element()[t](o),n=new ve(r.from()).valueOf();Object.assign(n,e),r.from(n)}const u=new ve(r.to()).valueOf();Object.assign(u,e),r.to(u),s=n,i=e})),this._rememberMorpher(t,r),this},zoom(t,e){if(this._tryRetarget("zoom",t,e))return this;let n=new ge(this._stepper).to(new jt(t));return this.queue((function(){n=n.from(this.element().zoom())}),(function(t){return this.element().zoom(n.at(t),e),n.done()}),(function(t,i){e=i,n.to(t)})),this._rememberMorpher("zoom",n),this},transform(t,e,n){if(e=t.relative||e,this._isDeclarative&&!e&&this._tryRetarget("transform",t))return this;const i=lt.isMatrixLike(t);n=null!=t.affine?t.affine:null!=n?n:!i;const r=new ge(this._stepper).type(n?xe:lt);let s,o,h,u,a;return this.queue((function(){o=o||this.element(),s=s||c(t,o),a=new lt(e?void 0:o),o._addRunner(this),e||o._clearTransformRunnersBefore(this)}),(function(l){e||this.clearTransform();const{x:c,y:f}=new ut(s).transform(o._currentTransform(this));let d=new lt({...t,origin:[c,f]}),m=this._isDeclarative&&h?h:a;if(n){d=d.decompose(c,f),m=m.decompose(c,f);const t=d.rotate,e=m.rotate,n=[t-360,t,t+360],i=n.map((t=>Math.abs(t-e))),r=Math.min(...i),s=i.indexOf(r);d.rotate=n[s]}e&&(i||(d.rotate=t.rotate||0),this._isDeclarative&&u&&(m.rotate=u)),r.from(m),r.to(d);const p=r.at(l);return u=p.rotate,h=new lt(p),this.addTransform(h),o._addRunner(this),r.done()}),(function(e){(e.origin||"center").toString()!==(t.origin||"center").toString()&&(s=c(e,o)),t={...e,origin:s}}),!0),this._isDeclarative&&this._rememberMorpher("transform",r),this},x(t){return this._queueNumber("x",t)},y(t){return this._queueNumber("y",t)},ax(t){return this._queueNumber("ax",t)},ay(t){return this._queueNumber("ay",t)},dx(t=0){return this._queueNumberDelta("x",t)},dy(t=0){return this._queueNumberDelta("y",t)},dmove(t,e){return this.dx(t).dy(e)},_queueNumberDelta(t,e){if(e=new jt(e),this._tryRetarget(t,e))return this;const n=new ge(this._stepper).to(e);let i=null;return this.queue((function(){i=this.element()[t](),n.from(i),n.to(i+e)}),(function(e){return this.element()[t](n.at(e)),n.done()}),(function(t){n.to(i+new jt(t))})),this._rememberMorpher(t,n),this},_queueObject(t,e){if(this._tryRetarget(t,e))return this;const n=new ge(this._stepper).to(e);return this.queue((function(){n.from(this.element()[t]())}),(function(e){return this.element()[t](n.at(e)),n.done()})),this._rememberMorpher(t,n),this},_queueNumber(t,e){return this._queueObject(t,new jt(e))},cx(t){return this._queueNumber("cx",t)},cy(t){return this._queueNumber("cy",t)},move(t,e){return this.x(t).y(e)},amove(t,e){return this.ax(t).ay(e)},center(t,e){return this.cx(t).cy(e)},size(t,e){let n;return t&&e||(n=this._element.bbox()),t||(t=n.width/n.height*e),e||(e=n.height/n.width*t),this.width(t).height(e)},width(t){return this._queueNumber("width",t)},height(t){return this._queueNumber("height",t)},plot(t,e,n,i){if(4===arguments.length)return this.plot([t,e,n,i]);if(this._tryRetarget("plot",t))return this;const r=new ge(this._stepper).type(this._element.MorphArray).to(t);return this.queue((function(){r.from(this._element.array())}),(function(t){return this._element.plot(r.at(t)),r.done()})),this._rememberMorpher("plot",r),this},leading(t){return this._queueNumber("leading",t)},viewbox(t,e,n,i){return this._queueObject("viewbox",new dt(t,e,n,i))},update(t){return"object"!=typeof t?this.update({offset:arguments[0],color:arguments[1],opacity:arguments[2]}):(null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",t.offset),this)}}),X(je,{rx:Pt,ry:Rt,from:Vt,to:$t}),P(je,"Runner");class Svg extends Container{constructor(t,e=t){super(D("svg",t),e),this.namespace()}defs(){return this.isRoot()?I(this.node.querySelector("defs"))||this.put(new Defs):this.root().defs()}isRoot(){return!this.node.parentNode||!(this.node.parentNode instanceof b.window.SVGElement)&&"#document-fragment"!==this.node.parentNode.nodeName}namespace(){return this.isRoot()?this.attr({xmlns:y,version:"1.1"}).attr("xmlns:xlink",_,g):this.root().namespace()}removeNamespace(){return this.attr({xmlns:null,version:null}).attr("xmlns:xlink",null,g).attr("xmlns:svgjs",null,g)}root(){return this.isRoot()?this:super.root()}}n({Container:{nested:Y((function(){return this.put(new Svg)}))}}),P(Svg,"Svg",!0);class Symbol extends Container{constructor(t,e=t){super(D("symbol",t),e)}}n({Container:{symbol:Y((function(){return this.put(new Symbol)}))}}),P(Symbol,"Symbol");var Le={__proto__:null,amove:function(t,e){return this.ax(t).ay(e)},ax:function(t){return this.attr("x",t)},ay:function(t){return this.attr("y",t)},build:function(t){return this._build=!!t,this},center:function(t,e,n=this.bbox()){return this.cx(t,n).cy(e,n)},cx:function(t,e=this.bbox()){return null==t?e.cx:this.attr("x",this.attr("x")+t-e.cx)},cy:function(t,e=this.bbox()){return null==t?e.cy:this.attr("y",this.attr("y")+t-e.cy)},length:function(){return this.node.getComputedTextLength()},move:function(t,e,n=this.bbox()){return this.x(t,n).y(e,n)},plain:function(t){return!1===this._build&&this.clear(),this.node.appendChild(b.document.createTextNode(t)),this},x:function(t,e=this.bbox()){return null==t?e.x:this.attr("x",this.attr("x")+t-e.x)},y:function(t,e=this.bbox()){return null==t?e.y:this.attr("y",this.attr("y")+t-e.y)}};class Text extends Shape{constructor(t,e=t){super(D("text",t),e),this.dom.leading=this.dom.leading??new jt(1.3),this._rebuild=!0,this._build=!1}leading(t){return null==t?this.dom.leading:(this.dom.leading=new jt(t),this.rebuild())}rebuild(t){if("boolean"==typeof t&&(this._rebuild=t),this._rebuild){const t=this;let e=0;const n=this.dom.leading;this.each((function(i){if(d(this.node))return;const r=b.window.getComputedStyle(this.node).getPropertyValue("font-size"),s=n*new jt(r);this.dom.newLined&&(this.attr("x",t.attr("x")),"\n"===this.text()?e+=s:(this.attr("dy",i?s+e:0),e=0))})),this.fire("rebuild")}return this}setData(t){return this.dom=t,this.dom.leading=new jt(t.leading||1.3),this}writeDataToDom(){return m(this,this.dom,{leading:1.3}),this}text(t){if(void 0===t){const e=this.node.childNodes;let n=0;t="";for(let i=0,r=e.length;i{let i;try{i=n.node instanceof T().SVGSVGElement?new dt(n.attr(["x","y","width","height"])):n.bbox()}catch(t){return}const r=new lt(n),s=r.translate(t,e).transform(r.inverse()),o=new ut(i.x,i.y).transform(s);n.move(o.x,o.y)})),this},dx:function(t){return this.dmove(t,0)},dy:function(t){return this.dmove(0,t)},height:function(t,e=this.bbox()){return null==t?e.height:this.size(e.width,t,e)},move:function(t=0,e=0,n=this.bbox()){const i=t-n.x,r=e-n.y;return this.dmove(i,r)},size:function(t,e,n=this.bbox()){const i=l(this,t,e,n),r=i.width/n.width,s=i.height/n.height;return this.children().forEach((t=>{const e=new ut(n).transform(new lt(t).inverse());t.scale(r,s,e.x,e.y)})),this},width:function(t,e=this.bbox()){return null==t?e.width:this.size(t,e.height,e)},x:function(t,e=this.bbox()){return null==t?e.x:this.move(t,e.y,e)},y:function(t,e=this.bbox()){return null==t?e.y:this.move(e.x,t,e)}};class G extends Container{constructor(t,e=t){super(D("g",t),e)}}X(G,Fe),n({Container:{group:Y((function(){return this.put(new G)}))}}),P(G,"G");class A extends Container{constructor(t,e=t){super(D("a",t),e)}target(t){return this.attr("target",t)}to(t){return this.attr("href",t,_)}}X(A,Fe),n({Container:{link:Y((function(t){return this.put(new A).to(t)}))},Element:{unlink(){const t=this.linker();if(!t)return this;const e=t.parent();if(!e)return this.remove();const n=e.index(t);return e.add(this,n),t.remove(),this},linkTo(t){let e=this.linker();return e||(e=new A,this.wrap(e)),"function"==typeof t?t.call(e,e):e.to(t),this},linker(){const t=this.parent();return t&&"a"===t.node.nodeName.toLowerCase()?t:null}}}),P(A,"A");class Mask extends Container{constructor(t,e=t){super(D("mask",t),e)}remove(){return this.targets().forEach((function(t){t.unmask()})),super.remove()}targets(){return wt("svg [mask*="+this.id()+"]")}}n({Container:{mask:Y((function(){return this.defs().put(new Mask)}))},Element:{masker(){return this.reference("mask")},maskWith(t){const e=t instanceof Mask?t:this.parent().mask().add(t);return this.attr("mask","url(#"+e.id()+")")},unmask(){return this.attr("mask",null)}}}),P(Mask,"Mask");class Stop extends Element{constructor(t,e=t){super(D("stop",t),e)}update(t){return("number"==typeof t||t instanceof jt)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new jt(t.offset)),this}}n({Gradient:{stop:function(t,e,n){return this.put(new Stop).update(t,e,n)}}}),P(Stop,"Stop");class Style extends Element{constructor(t,e=t){super(D("style",t),e)}addText(t=""){return this.node.textContent+=t,this}font(t,e,n={}){return this.rule("@font-face",{fontFamily:t,src:e,...n})}rule(t,e){return this.addText(function(t,e){if(!t)return"";if(!e)return t;let n=t+"{";for(const t in e)n+=u(t)+":"+e[t]+";";return n+="}",n}(t,e))}}n("Dom",{style(t,e){return this.put(new Style).rule(t,e)},fontface(t,e,n){return this.put(new Style).font(t,e,n)}}),P(Style,"Style");class TextPath extends Text{constructor(t,e=t){super(D("textPath",t),e)}array(){const t=this.track();return t?t.array():null}plot(t){const e=this.track();let n=null;return e&&(n=e.plot(t)),null==t?n:this}track(){return this.reference("href")}}n({Container:{textPath:Y((function(t,e){return t instanceof Text||(t=this.text(t)),t.path(e)}))},Text:{path:Y((function(t,e=!0){const n=new TextPath;let i;if(t instanceof Path||(t=this.defs().path(t)),n.attr("href","#"+t,_),e)for(;i=this.node.firstChild;)n.node.appendChild(i);return this.put(n)})),textPath(){return this.findOne("textPath")}},Path:{text:Y((function(t){return t instanceof Text||(t=(new Text).addTo(this.parent()).text(t)),t.path(this)})),targets(){return wt("svg textPath").filter((t=>(t.attr("href")||"").includes(this.id())))}}}),TextPath.prototype.MorphArray=ye,P(TextPath,"TextPath");class Use extends Shape{constructor(t,e=t){super(D("use",t),e)}use(t,e){return this.attr("href",(e||"")+"#"+t,_)}}n({Container:{use:Y((function(t,e){return this.put(new Use).use(t,e)}))}}),P(Use,"Use");const Xe=j;X([Svg,Symbol,Image,Pattern,Marker],i("viewbox")),X([Line,Polyline,Polygon,Path],i("marker")),X(Text,i("Text")),X(Path,i("Path")),X(Defs,i("Defs")),X([Text,Tspan],i("Tspan")),X([Rect,Ellipse,Gradient,je],i("radius")),X(kt,i("EventTarget")),X(Dom,i("Dom")),X(Element,i("Element")),X(Shape,i("Shape")),X([Container,Ht],i("Container")),X(Gradient,i("Gradient")),X(je,i("Runner")),pt.extend([...new Set(e)]),Ae([jt,ht,dt,lt,Et,Qt,ye,ut]),Oe();var Ye={__proto__:null,A:A,Animator:Ce,Array:Et,Box:dt,Circle:Circle,ClipPath:ClipPath,Color:ht,Container:Container,Controller:ne,Defs:Defs,Dom:Dom,Ease:ee,Element:Element,Ellipse:Ellipse,EventTarget:kt,ForeignObject:qe,Fragment:Ht,G:G,Gradient:Gradient,Image:Image,Line:Line,List:pt,Marker:Marker,Mask:Mask,Matrix:lt,Morphable:ge,NonMorphable:_e,Number:jt,ObjectBag:ve,PID:se,Path:Path,PathArray:ye,Pattern:Pattern,Point:ut,PointArray:Qt,Polygon:Polygon,Polyline:Polyline,Queue:Te,Rect:Rect,Runner:je,SVG:Xe,Shape:Shape,Spring:re,Stop:Stop,Style:Style,Svg:Svg,Symbol:Symbol,Text:Text,TextPath:TextPath,Timeline:Ee,TransformBag:xe,Tspan:Tspan,Use:Use,adopt:I,assignNewId:F,clearEvents:vt,create:E,defaults:St,dispatch:Ot,easing:Kt,eid:q,extend:X,find:wt,getClass:R,getEventTarget:bt,getEvents:xt,getWindow:T,makeInstance:j,makeMorphable:Oe,mockAdopt:function(t=I){z=t},namespaces:x,nodeOrNew:D,off:At,on:Mt,parser:ct,regex:it,register:P,registerMorphableType:Ae,registerWindow:v,restoreWindow:k,root:S,saveWindow:O,utils:p,windowEvents:_t,withWindow:function(t,e){O(),v(t,t.document),e(t,t.document),k()},wrapWithAttrCheck:Y};function Be(t,e){return j(t,e)}return Object.assign(Be,Ye),Be}(); +//# sourceMappingURL=svg.min.js.map diff --git a/symbols/symbols.js b/symbols/symbols.js new file mode 100644 index 0000000..090d92e --- /dev/null +++ b/symbols/symbols.js @@ -0,0 +1,912 @@ +// ============================================ +// SYMBOL-DEFINITIONEN +// Gutachter Symbolbibliothek v2.0 +// ============================================ + +const SYMBOLS = { + // ========== SCHADENSARTEN ========== + schaeden: { + name: "Schadensarten", + icon: "🔥", + items: [ + { + id: "wasserschaden", + name: "Wasserschaden", + filename: "wasserschaden_symbol.svg", + tags: ["wasser", "feuchtigkeit", "nass"], + svg: ``, + dxfSvg: `W` + }, + { + id: "brandschaden", + name: "Brandschaden", + filename: "brandschaden_symbol.svg", + tags: ["feuer", "brand", "flamme"], + svg: ``, + dxfSvg: `B` + }, + { + id: "rauchschaden", + name: "Rauchschaden", + filename: "rauchschaden_symbol.svg", + tags: ["rauch", "russ", "qualm"], + svg: ``, + dxfSvg: `R` + }, + { + id: "leitungswasser", + name: "Leitungswasser / Rohrbruch", + filename: "leitungswasserschaden_symbol.svg", + tags: ["rohr", "leitung", "bruch", "wasser"], + svg: ``, + dxfSvg: `LW` + }, + { + id: "schimmel", + name: "Schimmelschaden", + filename: "schimmelschaden_symbol.svg", + tags: ["schimmel", "pilz", "feucht", "sporen"], + svg: `!`, + dxfSvg: `S` + }, + { + id: "sturm", + name: "Sturmschaden", + filename: "sturmschaden_symbol.svg", + tags: ["sturm", "wind", "dach", "unwetter"], + svg: ``, + dxfSvg: `ST` + }, + { + id: "einbruch", + name: "Einbruchschaden", + filename: "einbruchschaden_symbol.svg", + tags: ["einbruch", "diebstahl", "fenster", "tür"], + svg: ``, + dxfSvg: `EB` + }, + { + id: "elektro", + name: "Elektroschaden", + filename: "elektroschaden_symbol.svg", + tags: ["elektro", "strom", "blitz", "kurzschluss"], + svg: ``, + dxfSvg: `E` + }, + { + id: "hagel", + name: "Hagelschaden", + filename: "hagelschaden_symbol.svg", + tags: ["hagel", "eis", "dellen", "unwetter"], + svg: ``, + dxfSvg: `H` + }, + { + id: "vandalismus", + name: "Vandalismus", + filename: "vandalismus_symbol.svg", + tags: ["vandalismus", "graffiti", "zerstörung", "sachbeschädigung"], + svg: `TAG`, + dxfSvg: `V` + } + ] + }, + + // ========== WERKZEUGE & MARKIERUNGEN ========== + werkzeuge: { + name: "Werkzeuge & Markierungen", + icon: "🔧", + items: [ + { + id: "massstab", + name: "Maßstab 1m", + filename: "massstab_1m.svg", + tags: ["maßstab", "meter", "lineal", "messen"], + svg: `0501001 Meter`, + dxfSvg: `` + }, + { + id: "messpunkt", + name: "Messpunkt", + filename: "messpunkt.svg", + tags: ["messpunkt", "markierung", "punkt", "messen"], + svg: ``, + dxfSvg: `` + }, + { + id: "kamera", + name: "Fotostandpunkt", + filename: "fotostandpunkt.svg", + tags: ["foto", "kamera", "standpunkt", "aufnahme"], + svg: ``, + dxfSvg: `FO` + }, + { + id: "lupe", + name: "Detailbereich", + filename: "detailbereich.svg", + tags: ["detail", "lupe", "vergrößerung", "zoom"], + svg: `+`, + dxfSvg: `` + }, + { + id: "notiz", + name: "Notiz / Hinweis", + filename: "notiz_hinweis.svg", + tags: ["notiz", "hinweis", "anmerkung", "text"], + svg: ``, + dxfSvg: `` + }, + { + id: "warnung", + name: "Warnung / Achtung", + filename: "warnung_achtung.svg", + tags: ["warnung", "achtung", "gefahr", "vorsicht"], + svg: `!`, + dxfSvg: `!` + }, + { + id: "info", + name: "Information", + filename: "information.svg", + tags: ["info", "information", "hinweis", "details"], + svg: ``, + dxfSvg: `i` + }, + { + id: "haken", + name: "Erledigt / OK", + filename: "erledigt_ok.svg", + tags: ["ok", "erledigt", "fertig", "haken", "check"], + svg: ``, + dxfSvg: `` + }, + { + id: "kreuz", + name: "Fehler / Mangel", + filename: "fehler_mangel.svg", + tags: ["fehler", "mangel", "falsch", "kreuz"], + svg: ``, + dxfSvg: `` + }, + { + id: "fragezeichen", + name: "Unklar / Prüfen", + filename: "unklar_pruefen.svg", + tags: ["unklar", "prüfen", "frage", "unbekannt"], + svg: `?`, + dxfSvg: `?` + } + ] + }, + + // ========== BAUTEILE ========== + bauteile: { + name: "Bauteile", + icon: "🏗️", + items: [ + { + id: "fenster", + name: "Fenster", + filename: "bauteil_fenster.svg", + tags: ["fenster", "verglasung", "rahmen"], + svg: ``, + dxfSvg: `` + }, + { + id: "tuer", + name: "Tür", + filename: "bauteil_tuer.svg", + tags: ["tür", "türblatt", "eingang"], + svg: ``, + dxfSvg: `` + }, + { + id: "wand", + name: "Wand (Mauerwerk)", + filename: "bauteil_wand.svg", + tags: ["wand", "mauer", "mauerwerk", "ziegel"], + svg: ``, + dxfSvg: `` + }, + { + id: "wand_beton", + name: "Wand (Beton)", + filename: "bauteil_wand_beton.svg", + tags: ["wand", "beton", "stahlbeton", "massiv"], + svg: ``, + dxfSvg: `` + }, + { + id: "boden_fliesen", + name: "Fliesen", + filename: "bauteil_fliesen.svg", + tags: ["fliesen", "boden", "wand", "keramik", "kacheln"], + svg: ``, + dxfSvg: `` + }, + { + id: "boden_parkett", + name: "Parkett / Holzboden", + filename: "bauteil_parkett.svg", + tags: ["parkett", "holz", "boden", "laminat", "dielen"], + svg: ``, + dxfSvg: `` + }, + { + id: "dach", + name: "Dach", + filename: "bauteil_dach.svg", + tags: ["dach", "dachstuhl", "ziegel", "bedachung"], + svg: ``, + dxfSvg: `` + }, + { + id: "treppe", + name: "Treppe", + filename: "bauteil_treppe.svg", + tags: ["treppe", "stufen", "aufgang", "treppenhaus"], + svg: ``, + dxfSvg: `` + }, + { + id: "daemmung", + name: "Dämmung / Isolierung", + filename: "bauteil_daemmung.svg", + tags: ["dämmung", "isolierung", "wärme", "kälte"], + svg: ``, + dxfSvg: `` + }, + { + id: "rohr", + name: "Rohrleitung", + filename: "bauteil_rohr.svg", + tags: ["rohr", "leitung", "rohrleitung", "installation"], + svg: ``, + dxfSvg: `` + } + ] + }, + + // ========== MÖBEL ========== + moebel: { + name: "Möbel", + icon: "🛋️", + items: [ + { + id: "sofa", + name: "Sofa / Couch", + filename: "moebel_sofa.svg", + tags: ["sofa", "couch", "sitzmoebel", "wohnzimmer"], + svg: ``, + dxfSvg: `` + }, + { + id: "tisch", + name: "Tisch", + filename: "moebel_tisch.svg", + tags: ["tisch", "esstisch", "schreibtisch", "möbel"], + svg: ``, + dxfSvg: `` + }, + { + id: "stuhl", + name: "Stuhl", + filename: "moebel_stuhl.svg", + tags: ["stuhl", "sitz", "möbel", "esszimmer"], + svg: ``, + dxfSvg: `` + }, + { + id: "schrank", + name: "Schrank", + filename: "moebel_schrank.svg", + tags: ["schrank", "kleiderschrank", "möbel", "stauraum"], + svg: ``, + dxfSvg: `` + }, + { + id: "bett", + name: "Bett", + filename: "moebel_bett.svg", + tags: ["bett", "schlafzimmer", "möbel", "schlafen"], + svg: ``, + dxfSvg: `` + }, + { + id: "regal", + name: "Regal", + filename: "moebel_regal.svg", + tags: ["regal", "bücherregal", "möbel", "stauraum"], + svg: ``, + dxfSvg: `` + } + ] + }, + + // ========== BAD / SANITÄR ========== + bad: { + name: "Bad & Sanitär", + icon: "🚿", + items: [ + { + id: "wc", + name: "WC / Toilette", + filename: "wc_draufsicht.svg", + tags: ["wc", "toilette", "klo", "bad", "sanitär"], + svg: ``, + dxfSvg: `` + }, + { + id: "waschbecken", + name: "Waschbecken", + filename: "waschbecken_draufsicht.svg", + tags: ["waschbecken", "waschtisch", "bad", "sanitär", "lavabo"], + svg: ``, + dxfSvg: `` + }, + { + id: "badewanne", + name: "Badewanne", + filename: "badewanne_draufsicht.svg", + tags: ["badewanne", "wanne", "bad", "sanitär", "baden"], + svg: ``, + dxfSvg: `` + }, + { + id: "dusche", + name: "Dusche", + filename: "dusche_draufsicht.svg", + tags: ["dusche", "duschwanne", "bad", "sanitär", "brause"], + svg: ``, + dxfSvg: `` + }, + { + id: "bidet", + name: "Bidet", + filename: "bidet_draufsicht.svg", + tags: ["bidet", "bad", "sanitär"], + svg: ``, + dxfSvg: `` + }, + { + id: "doppelwaschbecken", + name: "Doppelwaschbecken", + filename: "doppelwaschbecken_draufsicht.svg", + tags: ["doppelwaschbecken", "waschtisch", "bad", "sanitär", "doppel"], + svg: ``, + dxfSvg: `` + } + ] + }, + // ========== KÜCHE ========== + kueche: { + name: "Küche", + icon: "🍳", + items: [ + { + id: "herd", + name: "Herd / Kochfeld", + filename: "kueche_herd.svg", + tags: ["herd", "kochfeld", "küche", "kochen"], + svg: ``, + dxfSvg: `` + }, + { + id: "spuele", + name: "Spüle", + filename: "kueche_spuele.svg", + tags: ["spüle", "waschbecken", "küche", "abwasch"], + svg: ``, + dxfSvg: `` + }, + { + id: "kuehlschrank", + name: "Kühlschrank", + filename: "kueche_kuehlschrank.svg", + tags: ["kühlschrank", "kühlen", "küche", "elektrogerät"], + svg: ``, + dxfSvg: `` + }, + { + id: "backofen", + name: "Backofen", + filename: "kueche_backofen.svg", + tags: ["backofen", "ofen", "küche", "backen"], + svg: ``, + dxfSvg: `` + }, + { + id: "spuelmaschine", + name: "Spülmaschine", + filename: "kueche_spuelmaschine.svg", + tags: ["spülmaschine", "geschirrspüler", "küche", "elektrogerät"], + svg: ``, + dxfSvg: `` + }, + { + id: "dunstabzug", + name: "Dunstabzugshaube", + filename: "kueche_dunstabzug.svg", + tags: ["dunstabzug", "dunstabzugshaube", "küche", "abzug"], + svg: ``, + dxfSvg: `` + } + ] + }, + + // ========== PFEILE (dynamisch) ========== + pfeile: { + name: "Richtungspfeile (Rot)", + icon: "➡️", + items: [] + }, + + // ========== KOMPASS (dynamisch) ========== + kompass: { + name: "Nordpfeile / Kompass", + icon: "🧭", + items: [] + }, + + // ========== VERMESSUNG - STATUS ========== + vermessung_status: { + name: "Vermessung - Status", + icon: "📋", + items: [ + { + id: "vm_reparatur", + name: "Reparatur", + filename: "vermessung_reparatur.svg", + tags: ["reparatur", "instandsetzung", "vermessung"], + svg: `R` + }, + { + id: "vm_neu", + name: "Neu", + filename: "vermessung_neu.svg", + tags: ["neu", "neubau", "vermessung"], + svg: `N` + }, + { + id: "vm_bestand", + name: "Bestand", + filename: "vermessung_bestand.svg", + tags: ["bestand", "bestehend", "vermessung"], + svg: `B` + }, + { + id: "vm_abriss", + name: "Abriss", + filename: "vermessung_abriss.svg", + tags: ["abriss", "rückbau", "vermessung"], + svg: `` + }, + { + id: "vm_geplant", + name: "Geplant", + filename: "vermessung_geplant.svg", + tags: ["geplant", "planung", "vermessung"], + svg: `P` + } + ] + }, + + // ========== VERMESSUNG - GRENZEN ========== + vermessung_grenzen: { + name: "Vermessung - Grenzen", + icon: "📍", + items: [ + { + id: "vm_grundstuecksgrenze", + name: "Grundstücksgrenze", + filename: "vermessung_grundstuecksgrenze.svg", + tags: ["grundstück", "grenze", "flurstück", "vermessung"], + svg: `` + }, + { + id: "vm_grenzpunkt_vermarkt", + name: "Grenzpunkt (vermarkt)", + filename: "vermessung_grenzpunkt_vermarkt.svg", + tags: ["grenzpunkt", "grenzstein", "vermarkt", "vermessung"], + svg: `` + }, + { + id: "vm_grenzpunkt_unvermarkt", + name: "Grenzpunkt (unvermarkt)", + filename: "vermessung_grenzpunkt_unvermarkt.svg", + tags: ["grenzpunkt", "unvermarkt", "vermessung"], + svg: `` + }, + { + id: "vm_flurstucksgrenze", + name: "Flurstücksgrenze", + filename: "vermessung_flurstucksgrenze.svg", + tags: ["flurstück", "grenze", "kataster", "vermessung"], + svg: `` + }, + { + id: "vm_zaun", + name: "Zaun", + filename: "vermessung_zaun.svg", + tags: ["zaun", "einfriedung", "grenze", "vermessung"], + svg: `` + }, + { + id: "vm_mauer", + name: "Mauer", + filename: "vermessung_mauer.svg", + tags: ["mauer", "wand", "einfriedung", "vermessung"], + svg: `` + }, + { + id: "vm_hecke", + name: "Hecke", + filename: "vermessung_hecke.svg", + tags: ["hecke", "grün", "bepflanzung", "vermessung"], + svg: `` + } + ] + }, + + // ========== VERMESSUNG - WASSER ========== + vermessung_wasser: { + name: "Vermessung - Wasser", + icon: "💧", + items: [ + { + id: "vm_hydrant_unterflur", + name: "Hydrant (Unterflur)", + filename: "vermessung_hydrant_unterflur.svg", + tags: ["hydrant", "unterflur", "wasser", "feuerwehr", "vermessung"], + svg: `` + }, + { + id: "vm_hydrant_ueberflur", + name: "Hydrant (Überflur)", + filename: "vermessung_hydrant_ueberflur.svg", + tags: ["hydrant", "überflur", "wasser", "feuerwehr", "vermessung"], + svg: `` + }, + { + id: "vm_wasserschacht", + name: "Trinkwasserschacht", + filename: "vermessung_wasserschacht.svg", + tags: ["schacht", "wasser", "trinkwasser", "vermessung"], + svg: `W` + }, + { + id: "vm_wasserschieber", + name: "Wasserschieber", + filename: "vermessung_wasserschieber.svg", + tags: ["schieber", "absperrer", "wasser", "vermessung"], + svg: `` + }, + { + id: "vm_brunnen", + name: "Brunnen", + filename: "vermessung_brunnen.svg", + tags: ["brunnen", "wasser", "quelle", "vermessung"], + svg: `` + }, + { + id: "vm_wasserleitung", + name: "Wasserleitung", + filename: "vermessung_wasserleitung.svg", + tags: ["leitung", "wasser", "rohr", "vermessung"], + svg: `W` + } + ] + }, + + // ========== VERMESSUNG - ABWASSER ========== + vermessung_abwasser: { + name: "Vermessung - Abwasser", + icon: "🚰", + items: [ + { + id: "vm_abwasserschacht", + name: "Abwasserschacht", + filename: "vermessung_abwasserschacht.svg", + tags: ["schacht", "abwasser", "kanal", "vermessung"], + svg: `S` + }, + { + id: "vm_schacht_rund", + name: "Schacht (rund)", + filename: "vermessung_schacht_rund.svg", + tags: ["schacht", "rund", "kanal", "vermessung"], + svg: `` + }, + { + id: "vm_schacht_eckig", + name: "Schacht (eckig)", + filename: "vermessung_schacht_eckig.svg", + tags: ["schacht", "eckig", "kanal", "vermessung"], + svg: `` + }, + { + id: "vm_einlauf", + name: "Einlauf / Gully", + filename: "vermessung_einlauf.svg", + tags: ["einlauf", "gully", "straßenablauf", "vermessung"], + svg: `` + }, + { + id: "vm_abwasserleitung", + name: "Abwasserleitung", + filename: "vermessung_abwasserleitung.svg", + tags: ["leitung", "abwasser", "kanal", "vermessung"], + svg: `` + } + ] + }, + + // ========== VERMESSUNG - STROM ========== + vermessung_strom: { + name: "Vermessung - Strom", + icon: "⚡", + items: [ + { + id: "vm_hausanschluss_elektro", + name: "Hausanschluss Elektro", + filename: "vermessung_hausanschluss_elektro.svg", + tags: ["hausanschluss", "elektro", "strom", "vermessung"], + svg: `` + }, + { + id: "vm_laterne", + name: "Laterne / Mast", + filename: "vermessung_laterne.svg", + tags: ["laterne", "mast", "beleuchtung", "vermessung"], + svg: `` + }, + { + id: "vm_stromkabel", + name: "Stromkabel", + filename: "vermessung_stromkabel.svg", + tags: ["kabel", "strom", "leitung", "vermessung"], + svg: `E` + }, + { + id: "vm_schaltkasten", + name: "Schaltkasten", + filename: "vermessung_schaltkasten.svg", + tags: ["schaltkasten", "verteiler", "strom", "vermessung"], + svg: `E` + }, + { + id: "vm_trafostation", + name: "Trafostation", + filename: "vermessung_trafostation.svg", + tags: ["trafo", "station", "umspanner", "vermessung"], + svg: `` + }, + { + id: "vm_mast_holz", + name: "Mast (Holz)", + filename: "vermessung_mast_holz.svg", + tags: ["mast", "holz", "freileitung", "vermessung"], + svg: `H` + }, + { + id: "vm_mast_beton", + name: "Mast (Beton)", + filename: "vermessung_mast_beton.svg", + tags: ["mast", "beton", "freileitung", "vermessung"], + svg: `` + }, + { + id: "vm_mast_stahl", + name: "Mast (Stahl)", + filename: "vermessung_mast_stahl.svg", + tags: ["mast", "stahl", "freileitung", "vermessung"], + svg: `` + } + ] + }, + + // ========== VERMESSUNG - GAS ========== + vermessung_gas: { + name: "Vermessung - Gas", + icon: "🔥", + items: [ + { + id: "vm_gasschieber", + name: "Gasschieber", + filename: "vermessung_gasschieber.svg", + tags: ["schieber", "absperrer", "gas", "vermessung"], + svg: `G` + }, + { + id: "vm_gasleitung", + name: "Gasleitung", + filename: "vermessung_gasleitung.svg", + tags: ["leitung", "gas", "rohr", "vermessung"], + svg: `G` + }, + { + id: "vm_hausanschluss_gas", + name: "Hausanschluss Gas", + filename: "vermessung_hausanschluss_gas.svg", + tags: ["hausanschluss", "gas", "anschluss", "vermessung"], + svg: `G` + } + ] + }, + + // ========== VERMESSUNG - VERKEHR ========== + vermessung_verkehr: { + name: "Vermessung - Verkehr", + icon: "🚗", + items: [ + { + id: "vm_gleise", + name: "Gleise / Schienen", + filename: "vermessung_gleise.svg", + tags: ["gleise", "schienen", "bahn", "vermessung"], + svg: `` + }, + { + id: "vm_prellbock", + name: "Prellbock", + filename: "vermessung_prellbock.svg", + tags: ["prellbock", "gleisende", "bahn", "vermessung"], + svg: `` + }, + { + id: "vm_verkehrsschild", + name: "Verkehrsschild", + filename: "vermessung_verkehrsschild.svg", + tags: ["schild", "verkehr", "straße", "vermessung"], + svg: `` + }, + { + id: "vm_ampel", + name: "Ampel", + filename: "vermessung_ampel.svg", + tags: ["ampel", "signal", "verkehr", "vermessung"], + svg: `` + }, + { + id: "vm_haltestelle", + name: "Haltestelle", + filename: "vermessung_haltestelle.svg", + tags: ["haltestelle", "bus", "bahn", "vermessung"], + svg: `H` + }, + { + id: "vm_parkplatz", + name: "Parkplatz", + filename: "vermessung_parkplatz.svg", + tags: ["parkplatz", "parken", "stellplatz", "vermessung"], + svg: `P` + }, + { + id: "vm_schranke", + name: "Schranke", + filename: "vermessung_schranke.svg", + tags: ["schranke", "bahnübergang", "absperrung", "vermessung"], + svg: `` + } + ] + }, + + // ========== VERMESSUNG - TOPOGRAFIE ========== + vermessung_topografie: { + name: "Vermessung - Topografie", + icon: "🌳", + items: [ + { + id: "vm_laubbaum", + name: "Laubbaum", + filename: "vermessung_laubbaum.svg", + tags: ["baum", "laubbaum", "vegetation", "vermessung"], + svg: `` + }, + { + id: "vm_nadelbaum", + name: "Nadelbaum", + filename: "vermessung_nadelbaum.svg", + tags: ["baum", "nadelbaum", "tanne", "vermessung"], + svg: `` + }, + { + id: "vm_gebaeude", + name: "Gebäude", + filename: "vermessung_gebaeude.svg", + tags: ["gebäude", "haus", "bauwerk", "vermessung"], + svg: `` + }, + { + id: "vm_hoehenpunkt", + name: "Höhenpunkt", + filename: "vermessung_hoehenpunkt.svg", + tags: ["höhe", "nivellement", "punkt", "vermessung"], + svg: `HP` + }, + { + id: "vm_boeschung", + name: "Böschung", + filename: "vermessung_boeschung.svg", + tags: ["böschung", "hang", "gelände", "vermessung"], + svg: `` + }, + { + id: "vm_fliessrichtung", + name: "Fließrichtung", + filename: "vermessung_fliessrichtung.svg", + tags: ["fließrichtung", "gewässer", "bach", "vermessung"], + svg: `` + }, + { + id: "vm_quelle", + name: "Quelle", + filename: "vermessung_quelle.svg", + tags: ["quelle", "wasser", "ursprung", "vermessung"], + svg: `` + }, + { + id: "vm_durchlass", + name: "Durchlass", + filename: "vermessung_durchlass.svg", + tags: ["durchlass", "rohr", "kanal", "vermessung"], + svg: `` + }, + { + id: "vm_kilometerstein", + name: "Kilometerstein", + filename: "vermessung_kilometerstein.svg", + tags: ["kilometer", "stein", "markierung", "vermessung"], + svg: `km` + }, + { + id: "vm_poller", + name: "Poller", + filename: "vermessung_poller.svg", + tags: ["poller", "absperrung", "pfosten", "vermessung"], + svg: `` + } + ] + } +}; + +// ========== DYNAMISCHE PFEILE GENERIEREN ========== +function generateArrowSVG(angle) { + return ``; +} + +// DXF-taugliche Pfeile (nur Linien, keine Füllung) +function generateArrowDxfSVG(angle) { + return ``; +} + +function generateNorthArrowSVG(angle) { + return `N`; +} + +// DXF-taugliche Nordpfeile (nur Linien) +function generateNorthArrowDxfSVG(angle) { + return `N`; +} + +// Pfeile und Kompass generieren +for (let angle = 0; angle < 360; angle += 15) { + SYMBOLS.pfeile.items.push({ + id: `pfeil_${angle}`, + name: `${angle}°`, + filename: `richtungspfeil_rot_${angle}grad.svg`, + tags: ["pfeil", "richtung", "rot", angle.toString()], + svg: generateArrowSVG(angle), + dxfSvg: generateArrowDxfSVG(angle) + }); + + SYMBOLS.kompass.items.push({ + id: `nord_${angle}`, + name: `${angle}°`, + filename: `kompass_nord_${angle}grad.svg`, + tags: ["nord", "kompass", "himmelsrichtung", angle.toString()], + svg: generateNorthArrowSVG(angle), + dxfSvg: generateNorthArrowDxfSVG(angle) + }); +}