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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 16px
+
+
+
+
+
+
+
+
+
+ #000000
+
+
+
+
+
+
+ #000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100%
+
+
+
+
+
+
+
+
+ 10px
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Vorschau:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 🔍
+
+
+
+
+ |
+ Gutachten:
+
+
+
+
+
+
+
+
+ |
+ Vermessung:
+
+
+
+
+
+
+
+
+ |
+ Eigene:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Datenschutz
+
+
+
+
+
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 16px
+
+
+
+
+
+
+
+
+
+ #000000
+
+
+
+
+
+
+ #000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100%
+
+
+
+
+
+
+
+
+ 10px
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Vorschau:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 🔍
+
+
+
+
+ |
+ Gutachten:
+
+
+
+
+
+
+
+
+ |
+ Vermessung:
+
+
+
+
+
+
+
+
+ |
+ Eigene:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Datenschutz
+
+
+
+
+
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 16px
+
+
+
+
+
+
+
+
+
+ #000000
+
+
+
+
+
+
+ #000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100%
+
+
+
+
+
+
+
+
+ 10px
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Vorschau:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 🔍
+
+
+
+
+ |
+ Gutachten:
+
+
+
+
+
+
+
+
+ |
+ Vermessung:
+
+
+
+
+
+
+
+
+ |
+ Eigene:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Datenschutz
+
+
+
+
+
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 16px
+
+
+
+
+
+
+
+
+
+ #000000
+
+
+
+
+
+
+ #000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100%
+
+
+
+
+
+
+
+
+ 10px
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Vorschau:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 🔍
+
+
+
+
+ |
+ Gutachten:
+
+
+
+
+
+
+
+
+ |
+ Vermessung:
+
+
+
+
+
+
+
+
+ |
+ Eigene:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Datenschutz
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 = `';
+
+ 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)
+ });
+}