{"id":2076,"date":"2026-05-22T07:02:17","date_gmt":"2026-05-22T05:02:17","guid":{"rendered":"https:\/\/cad-kompass.de\/?p=2076"},"modified":"2026-06-26T17:55:07","modified_gmt":"2026-06-26T15:55:07","slug":"elementkanten-explainer","status":"publish","type":"post","link":"https:\/\/cad-kompass.de\/?p=2076","title":{"rendered":"Elementkanten-Explainer"},"content":{"rendered":"\n<style>\n    .ek-box {\n        --ek-primary: #3f5f75;\n        --ek-primary-dark: #2c4a5e;\n        --ek-primary-deep: #1e3548;\n        --ek-ink: #1e293b;\n        --ek-text: #334155;\n        --ek-muted: #64748b;\n        --ek-border: #c8d6e0;\n        --ek-surface: #f7f9fb;\n        --ek-good: #059669;\n        --ek-good-deep: #15803d;\n        --ek-warn: #d97706;\n        --ek-warn-deep: #92700c;\n        --ek-warn-border: #f4cf81;\n        --ek-warn-bg: #fffbeb;\n        --ek-error: #dc2626;\n        --ek-accent: #e8913a;\n        --ek-line-a: #3f5f75;\n        --ek-line-b: #0e7490;\n        --ek-dgm: #2f7d33;\n        --ek-pi: #3f5f75;\n        --ek-hp: #43a047;\n        background: var(--ek-surface);\n        border: 3px solid var(--ek-primary);\n        border-radius: 8px;\n        margin: 28px 0;\n        overflow: hidden;\n        position: relative;\n        font-family: inherit;\n        color: var(--ek-text);\n        line-height: 1.55;\n    }\n    .ek-box, .ek-box *, .ek-box *::before, .ek-box *::after { box-sizing: border-box; }\n\n    .ek-header {\n        background: linear-gradient(135deg, var(--ek-primary-deep) 0%, var(--ek-primary-dark) 100%);\n        padding: 20px 28px 18px;\n        position: relative;\n    }\n    .ek-header::after {\n        content: '';\n        position: absolute;\n        left: 0;\n        right: 0;\n        bottom: 0;\n        height: 3px;\n        background: repeating-linear-gradient(90deg, var(--ek-primary) 0px, var(--ek-primary) 8px, transparent 8px, transparent 12px);\n    }\n    .ek-header-top { display: flex; align-items: center; gap: 14px; }\n    .ek-header-icon { width: 36px; height: 36px; flex-shrink: 0; }\n    .ek-box-title { font-size: 1.15rem; color: #fff; font-weight: 700; margin: 0; letter-spacing: -0.01em; }\n    .ek-box-desc { font-size: 0.85rem; color: rgba(255,255,255,0.68); margin: 6px 0 0; line-height: 1.55; }\n\n    .ek-body { padding: 22px 28px 24px; }\n    .ek-privacy {\n        background: #f0fdf4;\n        border: 1px solid #bbf7d0;\n        border-radius: 3px;\n        padding: 9px 14px;\n        margin-bottom: 16px;\n        font-size: 0.8rem;\n        color: var(--ek-good-deep);\n        line-height: 1.5;\n        display: flex;\n        align-items: center;\n        gap: 8px;\n    }\n    .ek-privacy::before { content: '\\1F512'; flex-shrink: 0; font-size: 0.9rem; }\n    .ek-intro {\n        background: #f0f5fa;\n        border-left: 3px solid var(--ek-primary);\n        border-radius: 0 4px 4px 0;\n        padding: 11px 15px;\n        margin-bottom: 16px;\n        font-size: 0.86rem;\n        color: var(--ek-text);\n    }\n    .ek-intro strong { color: var(--ek-ink); }\n\n    .ek-focus-label {\n        font-size: 0.66rem;\n        font-weight: 800;\n        text-transform: uppercase;\n        letter-spacing: 0.1em;\n        color: var(--ek-muted);\n        margin-bottom: 6px;\n    }\n    .ek-focus {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 6px;\n        margin-bottom: 14px;\n    }\n    .ek-box .ek-focus-btn {\n        -webkit-appearance: none !important;\n        appearance: none !important;\n        border: 1px solid var(--ek-border) !important;\n        border-radius: 3px !important;\n        background: #fff !important;\n        color: var(--ek-text) !important;\n        min-height: 36px !important;\n        padding: 7px 12px !important;\n        font: inherit !important;\n        font-size: 0.78rem !important;\n        font-weight: 800 !important;\n        cursor: pointer !important;\n        box-shadow: none !important;\n        text-transform: none !important;\n        transition: background-color 0.15s, border-color 0.15s, color 0.15s, transform 0.12s;\n    }\n    .ek-box .ek-focus-btn:hover { border-color: var(--ek-primary) !important; color: var(--ek-primary) !important; background: #fff !important; transform: translateY(-1px); }\n    .ek-box .ek-focus-btn:active { transform: translateY(1px); }\n    .ek-box .ek-focus-btn:focus-visible { outline: 2px solid var(--ek-primary) !important; outline-offset: 2px !important; border-color: var(--ek-primary) !important; color: var(--ek-primary) !important; }\n    .ek-box .ek-focus-btn.is-active { background: var(--ek-primary) !important; border-color: var(--ek-primary) !important; color: #fff !important; }\n\n    .ek-topic { display: none; }\n    .ek-topic.is-active { display: block; }\n    .ek-topic-title {\n        font-size: 0.72rem;\n        font-weight: 800;\n        color: var(--ek-primary);\n        text-transform: uppercase;\n        letter-spacing: 0.08em;\n        margin: 0 0 9px;\n    }\n    .ek-stage-panel {\n        display: grid;\n        grid-template-columns: minmax(0, 1fr);\n        gap: 12px;\n        align-items: start;\n        margin-bottom: 14px;\n    }\n    .ek-svg-wrap, .ek-controls, .ek-verdict {\n        border: 1px solid var(--ek-border);\n        border-radius: 4px;\n        background: #fff;\n    }\n    .ek-svg-wrap { order: 2; padding: 12px 14px 10px; }\n    .ek-svg-wrap svg { width: 100%; height: auto; display: block; font-family: inherit; touch-action: none; user-select: none; }\n    .ek-controls {\n        order: 1;\n        background: var(--ek-surface);\n        padding: 14px 16px 16px;\n        display: grid;\n        grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));\n        gap: 14px 16px;\n        align-items: start;\n    }\n    .ek-section-label {\n        font-size: 0.66rem;\n        font-weight: 800;\n        text-transform: uppercase;\n        letter-spacing: 0.1em;\n        color: var(--ek-muted);\n        margin: 0 0 8px;\n    }\n    .ek-control-block { min-width: 0; padding-bottom: 0; margin-bottom: 0; border-bottom: 0; }\n    .ek-field { margin-bottom: 10px; padding-left: 9px; border-left: 3px solid var(--ek-border); }\n    .ek-field-label { display: flex; justify-content: space-between; gap: 8px; font-size: 0.76rem; font-weight: 800; color: var(--ek-ink); margin-bottom: 5px; }\n    .ek-field-unit { color: var(--ek-muted); font-size: 0.72rem; font-weight: 600; }\n    .ek-inrow { display: flex; gap: 6px; align-items: stretch; }\n    .ek-num, .ek-select {\n        width: 100%;\n        min-width: 0;\n        font-family: inherit;\n        font-size: 0.84rem;\n        color: var(--ek-text);\n        background: #fff;\n        border: 1px solid var(--ek-border);\n        border-radius: 3px;\n        padding: 7px 9px;\n        font-variant-numeric: tabular-nums;\n    }\n    .ek-num:disabled { background: #eceff1; color: var(--ek-muted); cursor: not-allowed; }\n    .ek-num:focus-visible, .ek-select:focus-visible { outline: 2px solid var(--ek-primary); outline-offset: 1px; }\n    .ek-box .ek-range {\n        -webkit-appearance: none !important;\n        -moz-appearance: none !important;\n        appearance: none !important;\n        width: 100% !important;\n        height: 6px !important;\n        padding: 0 !important;\n        margin: 10px 0 !important;\n        background: #dbe4ec !important;\n        border: 0 !important;\n        border-radius: 3px !important;\n        outline: none !important;\n        box-shadow: none !important;\n        box-sizing: border-box !important;\n        accent-color: var(--ek-primary);\n    }\n    .ek-box .ek-range::-webkit-slider-runnable-track {\n        -webkit-appearance: none !important;\n        appearance: none !important;\n        height: 6px !important;\n        background: transparent !important;\n        border: 0 !important;\n        box-shadow: none !important;\n    }\n    .ek-box .ek-range::-webkit-slider-thumb {\n        -webkit-appearance: none !important;\n        appearance: none !important;\n        width: 18px !important;\n        height: 18px !important;\n        border-radius: 50% !important;\n        background: var(--ek-primary) !important;\n        border: 2px solid #fff !important;\n        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2) !important;\n        margin-top: -6px !important;\n        cursor: pointer !important;\n    }\n    .ek-box .ek-range::-moz-range-track {\n        height: 6px !important;\n        background: transparent !important;\n        border: 0 !important;\n        box-shadow: none !important;\n    }\n    .ek-box .ek-range::-moz-range-thumb {\n        -moz-appearance: none !important;\n        appearance: none !important;\n        width: 18px !important;\n        height: 18px !important;\n        border-radius: 50% !important;\n        background: var(--ek-primary) !important;\n        border: 2px solid #fff !important;\n        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2) !important;\n        cursor: pointer !important;\n    }\n    .ek-box .ek-range::-moz-focus-outer { border: 0 !important; }\n    .ek-box .ek-range:focus-visible { outline: 2px solid var(--ek-primary) !important; outline-offset: 4px !important; border-radius: 3px !important; }\n    .ek-box .ek-range:disabled::-webkit-slider-thumb { background: #aab8c2 !important; }\n    .ek-box .ek-range:disabled::-moz-range-thumb { background: #aab8c2 !important; }\n    .ek-box .ek-btn {\n        -webkit-appearance: none !important;\n        appearance: none !important;\n        font-family: inherit !important;\n        font-size: 0.78rem !important;\n        font-weight: 800 !important;\n        line-height: 1.2 !important;\n        color: #fff !important;\n        background: var(--ek-primary) !important;\n        border: 1px solid var(--ek-primary) !important;\n        border-radius: 3px !important;\n        min-height: 36px !important;\n        padding: 7px 12px !important;\n        cursor: pointer !important;\n        box-shadow: none !important;\n        text-transform: none !important;\n        white-space: nowrap !important;\n        transition: background-color 0.15s, border-color 0.15s, color 0.15s;\n    }\n    .ek-box .ek-btn:hover { background: var(--ek-primary-dark) !important; border-color: var(--ek-primary-dark) !important; color: #fff !important; }\n    .ek-box .ek-btn:focus-visible { outline: 2px solid var(--ek-primary) !important; outline-offset: 2px !important; }\n    .ek-box .ek-btn:disabled { background: #aab8c2 !important; border-color: #aab8c2 !important; cursor: not-allowed !important; }\n    .ek-box .ek-btn.is-ghost { background: #fff !important; color: var(--ek-primary) !important; }\n    .ek-box .ek-btn.is-ghost:hover { background: var(--ek-primary) !important; color: #fff !important; }\n    .ek-box .ek-btn.is-ghost:focus-visible { background: var(--ek-primary) !important; color: #fff !important; outline: 2px solid var(--ek-primary) !important; outline-offset: 2px !important; }\n    .ek-btnrow { display: flex; flex-wrap: wrap; gap: 6px; }\n\n    .ek-seg { display: inline-flex; flex-wrap: wrap; border: 1px solid var(--ek-border); border-radius: 3px; overflow: hidden; background: #fff; }\n    .ek-box .ek-seg button {\n        -webkit-appearance: none !important;\n        appearance: none !important;\n        font-family: inherit !important;\n        font-size: 0.73rem !important;\n        font-weight: 800 !important;\n        line-height: 1.2 !important;\n        color: var(--ek-text) !important;\n        background: #fff !important;\n        border: none !important;\n        border-radius: 0 !important;\n        min-height: 34px !important;\n        padding: 6px 12px !important;\n        cursor: pointer !important;\n        box-shadow: none !important;\n        text-transform: none !important;\n        transition: background-color 0.15s, color 0.15s;\n    }\n    .ek-box .ek-seg button + button { border-left: 1px solid var(--ek-border) !important; }\n    .ek-box .ek-seg button.is-active { background: var(--ek-primary) !important; color: #fff !important; }\n    .ek-box .ek-seg button:hover:not(.is-active) { background: #edf4f8 !important; color: var(--ek-primary) !important; }\n    .ek-box .ek-seg button:focus-visible { outline: 2px solid var(--ek-primary) !important; outline-offset: -2px !important; }\n\n    .ek-check {\n        display: flex;\n        align-items: flex-start;\n        gap: 8px;\n        margin: 8px 0 0;\n        font-size: 0.78rem;\n        font-weight: 700;\n        color: var(--ek-text);\n    }\n    .ek-check input { margin-top: 3px; accent-color: var(--ek-primary); }\n    .ek-mini-hint { font-size: 0.74rem; color: var(--ek-muted); line-height: 1.45; margin-top: 6px; }\n    .ek-box .ek-range:disabled { opacity: 0.42 !important; cursor: not-allowed !important; }\n    .ek-step-flow { display: grid; gap: 8px; }\n    .ek-step-check {\n        display: grid;\n        grid-template-columns: 18px 24px minmax(0, 1fr);\n        gap: 8px;\n        align-items: start;\n        border: 1px solid var(--ek-border);\n        border-radius: 4px;\n        background: #fff;\n        padding: 9px 10px;\n        cursor: pointer;\n        transition: border-color 0.15s, background-color 0.15s;\n    }\n    .ek-step-check input { margin: 3px 0 0; accent-color: var(--ek-primary); }\n    .ek-step-number {\n        width: 22px;\n        height: 22px;\n        border-radius: 999px;\n        display: inline-flex;\n        align-items: center;\n        justify-content: center;\n        background: #edf4f8;\n        color: var(--ek-primary);\n        font-size: 0.72rem;\n        font-weight: 900;\n    }\n    .ek-step-title { display: block; font-size: 0.78rem; font-weight: 900; color: var(--ek-ink); }\n    .ek-step-copy { display: block; margin-top: 2px; font-size: 0.72rem; line-height: 1.35; color: var(--ek-muted); }\n    .ek-step-check.is-next { border-color: #9fbad0; background: #f8fbfd; box-shadow: inset 3px 0 0 var(--ek-primary); }\n    .ek-step-check.is-next .ek-step-number { background: #edf4f8; color: var(--ek-primary); }\n    .ek-step-check.is-done { border-color: #bbf7d0; background: #f0fdf4; }\n    .ek-step-check.is-done .ek-step-number { background: #dcfce7; color: var(--ek-good-deep); }\n    .ek-step-check.is-locked { cursor: default; }\n\n    .ek-point-table { display: grid; gap: 5px; margin-bottom: 12px; }\n    .ek-box .ek-point-row {\n        -webkit-appearance: none !important;\n        appearance: none !important;\n        width: 100% !important;\n        display: grid !important;\n        grid-template-columns: 16px minmax(76px, 1fr) auto auto;\n        gap: 7px;\n        align-items: center;\n        border: 1px solid var(--ek-border) !important;\n        background: #fff !important;\n        color: var(--ek-text) !important;\n        border-radius: 3px !important;\n        min-height: 36px !important;\n        padding: 5px 7px !important;\n        font: inherit !important;\n        font-size: 0.74rem !important;\n        cursor: pointer !important;\n        box-shadow: none !important;\n        text-align: left !important;\n        text-transform: none !important;\n    }\n    .ek-box .ek-point-row:hover { border-color: var(--ek-primary) !important; background: #fff !important; }\n    .ek-box .ek-point-row:focus-visible { outline: 2px solid var(--ek-primary) !important; outline-offset: 2px !important; border-color: var(--ek-primary) !important; }\n    .ek-box .ek-point-row.is-active { border-color: var(--ek-primary) !important; background: #edf4f8 !important; }\n    .ek-dot { width: 10px; height: 10px; display: inline-block; flex-shrink: 0; }\n    .ek-dot.is-pi { background: var(--ek-pi); }\n    .ek-dot.is-hp { border: 2px solid var(--ek-hp); border-radius: 50%; background: #fff; }\n    .ek-point-kind { font-weight: 800; color: var(--ek-ink); }\n    .ek-point-meta, .ek-point-z { color: var(--ek-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }\n\n    .ek-legend {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 10px 14px;\n        align-items: center;\n        margin-top: 9px;\n        padding-top: 9px;\n        border-top: 1px solid #edf2f7;\n        font-size: 0.74rem;\n        color: var(--ek-muted);\n    }\n    .ek-legend-item { display: inline-flex; align-items: center; gap: 5px; }\n    .ek-line-swatch { width: 22px; height: 0; border-top: 3px solid currentColor; border-radius: 999px; }\n    .ek-line-swatch.is-dash {\n        height: 2px;\n        border-top: 0;\n        background: repeating-linear-gradient(90deg, currentColor 0 6px, transparent 6px 10px);\n    }\n    .ek-tile-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; margin-top: 8px; }\n    .ek-controls > .ek-tile-grid { margin-top: 0; align-self: stretch; }\n    .ek-tile {\n        background: #fff;\n        border: 1px solid #dde7ee;\n        border-radius: 3px;\n        padding: 8px 9px;\n        min-width: 0;\n    }\n    .ek-tile-label {\n        display: block;\n        color: var(--ek-muted);\n        font-size: 0.64rem;\n        font-weight: 800;\n        text-transform: uppercase;\n        letter-spacing: 0.05em;\n    }\n    .ek-tile-value {\n        display: block;\n        color: var(--ek-ink);\n        font-size: 0.9rem;\n        font-weight: 800;\n        font-variant-numeric: tabular-nums;\n        margin-top: 1px;\n    }\n\n    .ek-verdict { padding: 12px 16px; margin-bottom: 14px; }\n    .ek-verdict.is-live { background: #fff; border-left: 3px solid var(--ek-primary); border-radius: 0 4px 4px 0; }\n    .ek-verdict-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }\n    .ek-verdict-title {\n        font-size: 0.66rem;\n        font-weight: 800;\n        text-transform: uppercase;\n        letter-spacing: 0.1em;\n        color: var(--ek-muted);\n    }\n    .ek-chip {\n        display: inline-flex;\n        align-items: center;\n        min-height: 22px;\n        padding: 2px 10px;\n        border-radius: 999px;\n        font-size: 0.72rem;\n        font-weight: 800;\n        background: #edf4f8;\n        border: 1px solid #cfe0ea;\n        color: var(--ek-primary-deep);\n    }\n    .ek-chip.is-good { background: #f0fdf4; border-color: #bbf7d0; color: var(--ek-good-deep); }\n    .ek-chip.is-warn { background: #fffbeb; border-color: #f4cf81; color: var(--ek-warn-deep); }\n    .ek-chip.is-error { background: #fef2f2; border-color: #fac5c5; color: var(--ek-error); }\n    .ek-chip.is-lock { background: #fffbeb; border-color: #f4cf81; color: var(--ek-warn-deep); }\n    .ek-verdict-text { font-size: 0.85rem; line-height: 1.6; color: var(--ek-text); margin: 0; }\n    .ek-verdict-text strong { color: var(--ek-ink); }\n\n    .ek-merksatz {\n        background: #f0fdf4;\n        border: 1px solid #bbf7d0;\n        border-left: 3px solid var(--ek-good);\n        border-radius: 0 4px 4px 0;\n        padding: 12px 16px;\n        margin-top: 14px;\n        font-size: 0.9rem;\n        color: var(--ek-text);\n    }\n    .ek-merksatz strong { color: var(--ek-good-deep); }\n\n    .ek-svg-label { fill: var(--ek-muted); font-size: 10px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }\n    .ek-svg-note { fill: var(--ek-muted); font-size: 10px; }\n    .ek-svg-axis { stroke: #d9e4ec; stroke-width: 1.2; }\n    .ek-svg-panel { fill: #fff; stroke: #d9e4ec; stroke-width: 1.2; }\n    .ek-svg-profile { fill: none; stroke: var(--ek-primary); stroke-width: 2.8; stroke-linejoin: round; stroke-linecap: round; }\n    .ek-svg-ghost { fill: none; stroke: #94a3b8; stroke-width: 2; stroke-dasharray: 5 4; stroke-linejoin: round; stroke-linecap: round; }\n    .ek-svg-surface { fill: none; stroke: var(--ek-dgm); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }\n    .ek-svg-surface-old { fill: none; stroke: #cbd5e1; stroke-width: 1.8; stroke-dasharray: 5 4; stroke-linecap: round; stroke-linejoin: round; }\n    .ek-svg-feature { fill: none; stroke: var(--ek-primary); stroke-width: 2.8; stroke-linecap: round; stroke-linejoin: round; }\n    .ek-svg-warnline { fill: none; stroke: var(--ek-accent); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }\n    .ek-svg-tin { stroke: #dbe4ec; stroke-width: 1; fill: none; }\n    .ek-svg-tin-new { stroke: #bdd7bf; stroke-width: 1; fill: none; }\n    .ek-svg-break { stroke: var(--ek-accent); stroke-width: 3; fill: none; stroke-linecap: round; }\n    .ek-drag-hit { fill: transparent; pointer-events: all; cursor: grab; }\n    .ek-drag-hit.is-locked { cursor: not-allowed; }\n    .ek-drag-hit:active { cursor: grabbing; }\n\n    @media (max-width: 940px) {\n        .ek-stage-panel { grid-template-columns: 1fr; }\n    }\n    @media (max-width: 700px) {\n        .ek-header { padding: 16px 20px 14px; }\n        .ek-body { padding: 18px 18px 20px; }\n        .ek-tile-grid { grid-template-columns: 1fr; }\n        .ek-box .ek-point-row { grid-template-columns: 16px minmax(0, 1fr) auto !important; }\n        .ek-point-z { display: none; }\n    }\n    @media (max-width: 480px) {\n        .ek-focus { flex-direction: column; align-items: stretch; }\n        .ek-box .ek-focus-btn { text-align: left !important; }\n        .ek-inrow { flex-direction: column; }\n        .ek-seg { display: flex; flex-direction: column; }\n        .ek-box .ek-seg button + button { border-left: 0 !important; border-top: 1px solid var(--ek-border) !important; }\n    }\n    @media (prefers-reduced-motion: reduce) {\n        \/* !important n\u00f6tig: Accessibility-Override muss beliebige Spezifit\u00e4ten der CSS-Box schlagen. *\/\n        .ek-box * { transition: none !important; animation: none !important; }\n    }\n<\/style>\n\n<div class=\"ek-box\" data-ek-root>\n    <div class=\"ek-header\">\n        <div class=\"ek-header-top\">\n            <svg class=\"ek-header-icon\" viewBox=\"0 0 36 36\" fill=\"none\" aria-hidden=\"true\">\n                <polyline points=\"5,27 13,17 21,22 31,10\" stroke=\"rgba(255,255,255,0.9)\" stroke-width=\"2\" fill=\"none\" stroke-linejoin=\"round\"\/>\n                <rect x=\"3.5\" y=\"25\" width=\"3\" height=\"3\" fill=\"rgba(255,255,255,0.9)\"\/>\n                <rect x=\"29.5\" y=\"8.5\" width=\"3\" height=\"3\" fill=\"rgba(255,255,255,0.9)\"\/>\n                <circle cx=\"13\" cy=\"17\" r=\"2.25\" fill=\"none\" stroke=\"rgba(255,255,255,0.75)\" stroke-width=\"1.4\"\/>\n                <circle cx=\"21\" cy=\"22\" r=\"2.25\" fill=\"none\" stroke=\"rgba(255,255,255,0.75)\" stroke-width=\"1.4\"\/>\n                <path d=\"M6 7.5 H30\" stroke=\"rgba(255,255,255,0.28)\" stroke-width=\"1.4\" stroke-linecap=\"round\"\/>\n            <\/svg>\n            <div>\n                <h2 class=\"ek-box-title\">Elementkanten-Explainer<\/h2>\n                <div class=\"ek-box-desc\">Punktarten, Neigung, DGM-Bezug, Gebietskreuzungen und Bruchkantenwirkung in einem interaktiven Modell.<\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n\n    <div class=\"ek-body\">\n        <div class=\"ek-privacy\">L\u00e4uft komplett im Browser. Keine Daten verlassen deinen Rechner.<\/div>\n\n        <div class=\"ek-intro\">\n            Eine <strong>Elementkante<\/strong> ist nicht nur eine 3D-Linie. Sie hat Punktarten, H\u00f6henregeln, Gebietswirkung und kann ein DGM als Bruchkante steuern. W\u00e4hl ein Thema und ver\u00e4ndere das Modell: Der Befund direkt darunter \u00fcbersetzt die Reaktion in Civil-3D-Logik.\n        <\/div>\n\n        <div class=\"ek-focus-label\">Thema<\/div>\n        <div class=\"ek-focus\" id=\"ekFocus\" role=\"group\" aria-label=\"Thema w\u00e4hlen\">\n            <button type=\"button\" class=\"ek-focus-btn is-active\" data-ek-topic=\"height\">H\u00f6henlogik<\/button>\n            <button type=\"button\" class=\"ek-focus-btn\" data-ek-topic=\"dgm\">DGM-Bezug<\/button>\n            <button type=\"button\" class=\"ek-focus-btn\" data-ek-topic=\"crossing\">Gebiet &amp; Kreuzung<\/button>\n            <button type=\"button\" class=\"ek-focus-btn\" data-ek-topic=\"surface\">DGM-Wirkung<\/button>\n        <\/div>\n\n        <div class=\"ek-topic is-active\" id=\"ekTopicHeight\" data-topic=\"height\">\n            <div class=\"ek-topic-title\">H\u00f6henlogik<\/div>\n            <div class=\"ek-verdict is-live\">\n                <div class=\"ek-verdict-head\">\n                    <span class=\"ek-verdict-title\">Befund<\/span>\n                    <span class=\"ek-chip\" id=\"ekChipHeight\" aria-hidden=\"true\">l\u00e4dt<\/span>\n                <\/div>\n                <p class=\"ek-verdict-text\" id=\"ekTextHeight\" aria-live=\"polite\"><\/p>\n            <\/div>\n            <div class=\"ek-stage-panel\">\n                <div class=\"ek-svg-wrap\">\n                    <svg id=\"ekSvgHeight\" viewBox=\"0 0 620 390\" preserveAspectRatio=\"xMidYMid meet\" role=\"img\" aria-labelledby=\"ekHeightTitle\">\n                        <title id=\"ekHeightTitle\">Elementkante mit Draufsicht und H\u00f6henprofil<\/title>\n                        <g id=\"ekHeightDraw\"><\/g>\n                    <\/svg>\n                    <div class=\"ek-legend\">\n                        <span class=\"ek-legend-item\"><span class=\"ek-dot is-pi\"><\/span> St\u00fctzpunkt<\/span>\n                        <span class=\"ek-legend-item\"><span class=\"ek-dot is-hp\"><\/span> H\u00f6henpunkt<\/span>\n                        <span class=\"ek-legend-item\" style=\"color:var(--ek-warn-deep);\"><span class=\"ek-line-swatch\"><\/span> Gef\u00e4lle<\/span>\n                        <span class=\"ek-legend-item\" style=\"color:#94a3b8;\"><span class=\"ek-line-swatch is-dash\"><\/span> vorheriger Verlauf<\/span>\n                    <\/div>\n                <\/div>\n\n                <div class=\"ek-controls\">\n                    <div class=\"ek-control-block\">\n                        <div class=\"ek-section-label\">Punkteditor<\/div>\n                        <div class=\"ek-point-table\" id=\"ekPointTable\" role=\"group\" aria-label=\"Punkt w\u00e4hlen\"><\/div>\n                    <\/div>\n\n                    <div class=\"ek-control-block\" id=\"ekPiMoveBlock\">\n                        <div class=\"ek-section-label\">Lage und Profil<\/div>\n                        <div class=\"ek-field\">\n                            <label class=\"ek-field-label\" for=\"ekPiStation\">Station verschieben <span class=\"ek-field-unit\" id=\"ekPiStationText\">&#8211;<\/span><\/label>\n                            <input class=\"ek-range\" id=\"ekPiStation\" type=\"range\" min=\"0\" max=\"60\" step=\"1\" value=\"30\">\n                        <\/div>\n                        <div class=\"ek-field\">\n                            <label class=\"ek-field-label\" for=\"ekPiLane\">Seitenversatz <span class=\"ek-field-unit\" id=\"ekPiLaneText\">&#8211;<\/span><\/label>\n                            <input class=\"ek-range\" id=\"ekPiLane\" type=\"range\" min=\"-1.2\" max=\"1.2\" step=\"0.1\" value=\"0\">\n                        <\/div>\n                        <div class=\"ek-mini-hint\" id=\"ekPiMoveHint\">W\u00e4hle einen inneren St\u00fctzpunkt, um die Lage zu verschieben. H\u00f6henpunkte bleiben auf der Elementkante.<\/div>\n                    <\/div>\n\n                    <div class=\"ek-control-block\">\n                        <div class=\"ek-field\">\n                            <label class=\"ek-field-label\" for=\"ekHeightValue\">H\u00f6he des gew\u00e4hlten Punkts <span class=\"ek-field-unit\">m<\/span><\/label>\n                            <div class=\"ek-inrow\">\n                                <input class=\"ek-num\" id=\"ekHeightValue\" type=\"number\" step=\"0.05\" inputmode=\"decimal\">\n                                <button class=\"ek-btn\" id=\"ekSetHeight\" type=\"button\">Setzen<\/button>\n                            <\/div>\n                        <\/div>\n\n                        <div class=\"ek-field\">\n                            <label class=\"ek-field-label\" for=\"ekGradeValue\">Neigung zum n\u00e4chsten Punkt <span class=\"ek-field-unit\">%<\/span><\/label>\n                            <div class=\"ek-inrow\">\n                                <input class=\"ek-num\" id=\"ekGradeValue\" type=\"number\" step=\"0.1\" inputmode=\"decimal\">\n                                <button class=\"ek-btn\" id=\"ekSetGrade\" type=\"button\">Anwenden<\/button>\n                            <\/div>\n                        <\/div>\n                    <\/div>\n\n                    <div class=\"ek-control-block\">\n                        <div class=\"ek-section-label\">Punktaktionen<\/div>\n                        <div class=\"ek-btnrow\">\n                            <button class=\"ek-btn is-ghost\" id=\"ekAddHp\" type=\"button\">H\u00f6henpunkt einf\u00fcgen<\/button>\n                            <button class=\"ek-btn is-ghost\" id=\"ekAddPi\" type=\"button\">St\u00fctzpunkt einf\u00fcgen<\/button>\n                            <button class=\"ek-btn is-ghost\" id=\"ekRemovePoint\" type=\"button\">Entfernen<\/button>\n                            <button class=\"ek-btn is-ghost\" id=\"ekResetHeight\" type=\"button\">Zur\u00fccksetzen<\/button>\n                        <\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div class=\"ek-topic\" id=\"ekTopicDgm\" data-topic=\"dgm\">\n            <div class=\"ek-topic-title\">DGM-Bezug<\/div>\n            <div class=\"ek-verdict is-live\">\n                <div class=\"ek-verdict-head\">\n                    <span class=\"ek-verdict-title\">Befund<\/span>\n                    <span class=\"ek-chip\" id=\"ekChipDgm\" aria-hidden=\"true\">l\u00e4dt<\/span>\n                <\/div>\n                <p class=\"ek-verdict-text\" id=\"ekTextDgm\" aria-live=\"polite\"><\/p>\n            <\/div>\n            <div class=\"ek-stage-panel\">\n                <div class=\"ek-svg-wrap\">\n                    <svg id=\"ekSvgDgm\" viewBox=\"0 0 620 390\" preserveAspectRatio=\"xMidYMid meet\" role=\"img\" aria-labelledby=\"ekDgmTitle\">\n                        <title id=\"ekDgmTitle\">Elementkante absolut, statisch aus DGM oder relativ zum DGM<\/title>\n                        <g id=\"ekDgmDraw\"><\/g>\n                    <\/svg>\n                    <div class=\"ek-legend\">\n                        <span class=\"ek-legend-item\" style=\"color:var(--ek-dgm);\"><span class=\"ek-line-swatch\"><\/span> aktuelles Bezugs-DGM<\/span>\n                        <span class=\"ek-legend-item\" style=\"color:#cbd5e1;\"><span class=\"ek-line-swatch is-dash\"><\/span> altes Bezugs-DGM<\/span>\n                        <span class=\"ek-legend-item\" style=\"color:#3f5f75;\"><span class=\"ek-line-swatch\"><\/span> Elementkante<\/span>\n                    <\/div>\n                <\/div>\n\n                <div class=\"ek-controls\">\n                    <div class=\"ek-control-block\">\n                        <div class=\"ek-section-label\">H\u00f6henquelle<\/div>\n                        <span class=\"ek-seg\" id=\"ekDgmMode\" role=\"group\" aria-label=\"H\u00f6henquelle w\u00e4hlen\">\n                            <button type=\"button\" data-dgm-mode=\"free\">frei<\/button>\n                            <button type=\"button\" data-dgm-mode=\"static\">statisch aus DGM<\/button>\n                            <button type=\"button\" data-dgm-mode=\"relative\" class=\"is-active\">relativ zum DGM<\/button>\n                        <\/span>\n                    <\/div>\n\n                    <div class=\"ek-control-block\">\n                        <div class=\"ek-field\">\n                            <label class=\"ek-field-label\" for=\"ekDgmShift\">DGM-\u00c4nderung <span class=\"ek-field-unit\" id=\"ekDgmShiftText\">+0,00 m<\/span><\/label>\n                            <input class=\"ek-range\" id=\"ekDgmShift\" type=\"range\" min=\"-0.80\" max=\"0.80\" step=\"0.05\" value=\"0\">\n                        <\/div>\n                        <div class=\"ek-field\">\n                            <label class=\"ek-field-label\" for=\"ekDgmOffset\">relativer Versatz <span class=\"ek-field-unit\">m<\/span><\/label>\n                            <input class=\"ek-num\" id=\"ekDgmOffset\" type=\"number\" step=\"0.05\" value=\"0.00\" inputmode=\"decimal\">\n                        <\/div>\n                    <\/div>\n\n                    <div class=\"ek-tile-grid\">\n                        <div class=\"ek-tile\">\n                            <span class=\"ek-tile-label\">Startpunkt<\/span>\n                            <span class=\"ek-tile-value\" id=\"ekDgmStartOut\">&#8211;<\/span>\n                        <\/div>\n                        <div class=\"ek-tile\">\n                            <span class=\"ek-tile-label\">Endpunkt<\/span>\n                            <span class=\"ek-tile-value\" id=\"ekDgmEndOut\">&#8211;<\/span>\n                        <\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div class=\"ek-topic\" id=\"ekTopicCrossing\" data-topic=\"crossing\">\n            <div class=\"ek-topic-title\">Gebiet &amp; Kreuzung<\/div>\n            <div class=\"ek-verdict is-live\">\n                <div class=\"ek-verdict-head\">\n                    <span class=\"ek-verdict-title\">Befund<\/span>\n                    <span class=\"ek-chip\" id=\"ekChipCrossing\" aria-hidden=\"true\">l\u00e4dt<\/span>\n                <\/div>\n                <p class=\"ek-verdict-text\" id=\"ekTextCrossing\" aria-live=\"polite\"><\/p>\n            <\/div>\n            <div class=\"ek-stage-panel\">\n                <div class=\"ek-svg-wrap\">\n                    <svg id=\"ekSvgCrossing\" viewBox=\"0 0 620 390\" preserveAspectRatio=\"xMidYMid meet\" role=\"img\" aria-labelledby=\"ekCrossingTitle\">\n                        <title id=\"ekCrossingTitle\">Zwei Elementkanten kreuzen sich im selben oder getrennten Gebiet<\/title>\n                        <g id=\"ekCrossDraw\"><\/g>\n                    <\/svg>\n                    <div class=\"ek-legend\">\n                        <span class=\"ek-legend-item\" style=\"color:var(--ek-line-a);\"><span class=\"ek-line-swatch\"><\/span> Kante A<\/span>\n                        <span class=\"ek-legend-item\" style=\"color:var(--ek-line-b);\"><span class=\"ek-line-swatch\"><\/span> Kante B<\/span>\n                        <span class=\"ek-legend-item\" style=\"color:var(--ek-accent);\"><span class=\"ek-line-swatch\"><\/span> Teilungspunkt<\/span>\n                    <\/div>\n                <\/div>\n\n                <div class=\"ek-controls\">\n                    <div class=\"ek-control-block\">\n                        <div class=\"ek-section-label\">Gebietszuordnung<\/div>\n                        <div class=\"ek-field\">\n                            <label class=\"ek-field-label\" for=\"ekSiteA\">Kante A<\/label>\n                            <select class=\"ek-select\" id=\"ekSiteA\">\n                                <option value=\"bau\">Baugebiet<\/option>\n                                <option value=\"wall\">Wall<\/option>\n                                <option value=\"none\">&lt;Keine&gt;<\/option>\n                            <\/select>\n                        <\/div>\n                        <div class=\"ek-field\">\n                            <label class=\"ek-field-label\" for=\"ekSiteB\">Kante B<\/label>\n                            <select class=\"ek-select\" id=\"ekSiteB\">\n                                <option value=\"bau\">Baugebiet<\/option>\n                                <option value=\"wall\">Wall<\/option>\n                                <option value=\"none\">&lt;Keine&gt;<\/option>\n                            <\/select>\n                        <\/div>\n                    <\/div>\n\n                    <div class=\"ek-control-block\">\n                        <div class=\"ek-section-label\">H\u00f6he am Schnittpunkt<\/div>\n                        <span class=\"ek-seg\" id=\"ekCrossRule\" role=\"group\" aria-label=\"Regel f\u00fcr Schnittpunkt\">\n                            <button type=\"button\" data-cross-rule=\"prioA\" class=\"is-active\">Stil A gewinnt<\/button>\n                            <button type=\"button\" data-cross-rule=\"prioB\">Stil B gewinnt<\/button>\n                        <\/span>\n                        <label class=\"ek-check\">\n                            <input type=\"checkbox\" id=\"ekCrossPi\">\n                            <span>PI am Schnittpunkt einf\u00fcgen<\/span>\n                        <\/label>\n                        <div class=\"ek-mini-hint\">Im selben Gebiet braucht der Schnittpunkt eine gemeinsame H\u00f6he. Mit PI wird der Punkt bewusst kontrollierbar.<\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div class=\"ek-topic\" id=\"ekTopicSurface\" data-topic=\"surface\">\n            <div class=\"ek-topic-title\">DGM-Wirkung<\/div>\n            <div class=\"ek-verdict is-live\">\n                <div class=\"ek-verdict-head\">\n                    <span class=\"ek-verdict-title\">Befund<\/span>\n                    <span class=\"ek-chip\" id=\"ekChipSurface\" aria-hidden=\"true\">l\u00e4dt<\/span>\n                <\/div>\n                <p class=\"ek-verdict-text\" id=\"ekTextSurface\" aria-live=\"polite\"><\/p>\n            <\/div>\n            <div class=\"ek-stage-panel\">\n                <div class=\"ek-svg-wrap\">\n                    <svg id=\"ekSvgSurface\" viewBox=\"0 0 620 390\" preserveAspectRatio=\"xMidYMid meet\" role=\"img\" aria-labelledby=\"ekSurfaceTitle\">\n                        <title id=\"ekSurfaceTitle\">Elementkante als Bruchkante in einer TIN-Oberfl\u00e4che<\/title>\n                        <g id=\"ekSurfaceDraw\"><\/g>\n                    <\/svg>\n                    <div class=\"ek-legend\">\n                        <span class=\"ek-legend-item\" style=\"color:#94a3b8;\"><span class=\"ek-line-swatch is-dash\"><\/span> altes TIN vor Neuaufbau<\/span>\n                        <span class=\"ek-legend-item\" style=\"color:var(--ek-accent);\"><span class=\"ek-line-swatch\"><\/span> Bruchkante<\/span>\n                        <span class=\"ek-legend-item\" style=\"color:var(--ek-dgm);\"><span class=\"ek-line-swatch\"><\/span> neues TIN\/DGM nach Neuaufbau<\/span>\n                    <\/div>\n                <\/div>\n\n                <div class=\"ek-controls\">\n                    <div class=\"ek-control-block\">\n                        <div class=\"ek-section-label\">DGM-Definition<\/div>\n                        <div class=\"ek-step-flow\">\n                            <label class=\"ek-step-check\" data-surface-step=\"object\">\n                                <input type=\"checkbox\" id=\"ekSurfaceObject\" checked disabled>\n                                <span class=\"ek-step-number\">1<\/span>\n                                <span>\n                                    <span class=\"ek-step-title\">Elementkante existiert<\/span>\n                                    <span class=\"ek-step-copy\">Sie ist zuerst nur ein Objekt mit eigener H\u00f6he.<\/span>\n                                <\/span>\n                            <\/label>\n                            <label class=\"ek-step-check\" data-surface-step=\"breakline\">\n                                <input type=\"checkbox\" id=\"ekAsBreakline\">\n                                <span class=\"ek-step-number\">2<\/span>\n                                <span>\n                                    <span class=\"ek-step-title\">Als Bruchkante hinzuf\u00fcgen<\/span>\n                                    <span class=\"ek-step-copy\">Die Kante wird Teil der DGM-Definition.<\/span>\n                                <\/span>\n                            <\/label>\n                            <label class=\"ek-step-check\" data-surface-step=\"rebuild\">\n                                <input type=\"checkbox\" id=\"ekRebuildSurface\">\n                                <span class=\"ek-step-number\">3<\/span>\n                                <span>\n                                    <span class=\"ek-step-title\">DGM neu aufbauen<\/span>\n                                    <span class=\"ek-step-copy\">Die Triangulation \u00fcbernimmt die Definition.<\/span>\n                                <\/span>\n                            <\/label>\n                            <label class=\"ek-step-check\" data-surface-step=\"follow\">\n                                <input type=\"checkbox\" id=\"ekDgmFollows\" disabled>\n                                <span class=\"ek-step-number\">4<\/span>\n                                <span>\n                                    <span class=\"ek-step-title\">DGM folgt der Kante<\/span>\n                                    <span class=\"ek-step-copy\">Der Knick ist im neu aufgebauten TIN sichtbar.<\/span>\n                                <\/span>\n                            <\/label>\n                        <\/div>\n                    <\/div>\n\n                    <div class=\"ek-control-block\">\n                        <div class=\"ek-section-label\">Abh\u00e4ngigkeit<\/div>\n                        <label class=\"ek-check\">\n                            <input type=\"checkbox\" id=\"ekCircular\">\n                            <span>Kante ist relativ zu diesem Ziel-DGM<\/span>\n                        <\/label>\n                        <div class=\"ek-mini-hint\">Sauber ist meist: Kante bezieht ihre H\u00f6hen aus einem Referenz-DGM und steuert ein anderes Plan-DGM.<\/div>\n                    <\/div>\n\n                    <div class=\"ek-tile-grid\">\n                        <div class=\"ek-tile\">\n                            <span class=\"ek-tile-label\">Status<\/span>\n                            <span class=\"ek-tile-value\" id=\"ekSurfaceStatus\">&#8211;<\/span>\n                        <\/div>\n                        <div class=\"ek-tile\">\n                            <span class=\"ek-tile-label\">DGM-Reaktion<\/span>\n                            <span class=\"ek-tile-value\" id=\"ekSurfaceReaction\">&#8211;<\/span>\n                        <\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <div class=\"ek-merksatz\" id=\"ekRule\">\n            <strong>Grundregel:<\/strong> \u00c4ndert sich die Richtung im Lageplan, brauchst du einen St\u00fctzpunkt. \u00c4ndert sich nur der H\u00f6henverlauf, reicht ein H\u00f6henpunkt.\n        <\/div>\n    <\/div>\n<\/div>\n\n<script>\n    (function () {\n        \"use strict\";\n        var ROOT = document.querySelector(\"[data-ek-root]\");\n        if (!ROOT) { return; }\n\n        \/\/ Farbpalette als JS-Konstanten \u2014 Safari < 16 versteht keine CSS-Variablen\n        \/\/ in SVG-Attributwerten, deshalb hier hartcodierte Hex-Werte parallel zur CSS-Palette.\n        var COLORS = {\n            primary: \"#3f5f75\",\n            primaryDeep: \"#1e3548\",\n            ink: \"#1e293b\",\n            muted: \"#64748b\",\n            border: \"#d9e4ec\",\n            accent: \"#e8913a\",\n            lineA: \"#3f5f75\",\n            lineB: \"#0e7490\",\n            dgm: \"#2f7d33\",\n            goodDeep: \"#15803d\",\n            warnDeep: \"#92700c\",\n            warnBorder: \"#f4cf81\",\n            warnBg: \"#fffbeb\"\n        };\n\n        var SVGNS = \"http:\/\/www.w3.org\/2000\/svg\";\n        function $(id) { return ROOT.querySelector(\"#\" + id); }\n        function el(tag, attrs) {\n            var n = document.createElementNS(SVGNS, tag);\n            Object.keys(attrs || {}).forEach(function (k) { n.setAttribute(k, attrs[k]); });\n            return n;\n        }\n        function clear(node) { while (node.firstChild) { node.removeChild(node.firstChild); } }\n        function round2(v) { return Math.round(v * 100) \/ 100; }\n        function num(id) {\n            var v = parseFloat(String($(id).value).replace(\",\", \".\"));\n            return isNaN(v) ? 0 : v;\n        }\n        function fmt(v, d) {\n            var digits = typeof d === \"number\" ? d : 2;\n            return (isFinite(v) ? v : 0).toLocaleString(\"de-DE\", { minimumFractionDigits: digits, maximumFractionDigits: digits });\n        }\n        function fmtSigned(v, d) {\n            var x = Math.abs(v) < 0.0005 ? 0 : v;\n            return (x > 0 ? \"+\" : \"\") + fmt(x, d);\n        }\n        function pointString(points) {\n            return points.map(function (p) { return p.x.toFixed(1) + \",\" + p.y.toFixed(1); }).join(\" \");\n        }\n        function poly(group, pts, attrs) {\n            var p = el(\"polyline\", Object.assign({ points: pointString(pts) }, attrs || {}));\n            group.appendChild(p);\n            return p;\n        }\n        function line(group, x1, y1, x2, y2, attrs) {\n            group.appendChild(el(\"line\", Object.assign({ x1: x1, y1: y1, x2: x2, y2: y2 }, attrs || {})));\n        }\n        function text(group, x, y, content, attrs) {\n            \/\/ Safari erbt font-family im SVG nicht zuverl\u00e4ssig vom Container \u2014\n            \/\/ deshalb hier explizit setzen, falls nicht \u00fcberschrieben.\n            var base = { x: x, y: y, \"font-family\": \"inherit\" };\n            var t = el(\"text\", Object.assign(base, attrs || {}));\n            t.textContent = content;\n            group.appendChild(t);\n            return t;\n        }\n        function rect(group, x, y, w, h, attrs) {\n            group.appendChild(el(\"rect\", Object.assign({ x: x, y: y, width: w, height: h }, attrs || {})));\n        }\n        function circle(group, x, y, r, attrs) {\n            group.appendChild(el(\"circle\", Object.assign({ cx: x, cy: y, r: r }, attrs || {})));\n        }\n        function card(group, x, y, w, h, title) {\n            rect(group, x, y, w, h, { fill: \"#fff\", stroke: \"#d9e4ec\", \"stroke-width\": 1.2, rx: 4 });\n            if (title) {\n                text(group, x + 14, y + 24, title, { class: \"ek-svg-label\" });\n            }\n        }\n        function labelBox(group, x, y, label, opts) {\n            opts = opts || {};\n            var fs = opts.size || 9.5;\n            var pad = opts.pad || 6;\n            var w = opts.width || Math.max(34, label.length * fs * 0.55 + pad * 2);\n            var h = opts.height || 17;\n            var fill = opts.fill || \"#fff\";\n            var stroke = opts.stroke || \"#d9e4ec\";\n            rect(group, x - w \/ 2, y - h + 3, w, h, { fill: fill, stroke: stroke, \"stroke-width\": 1, rx: 3 });\n            text(group, x, y - 3, label, {\n                \"font-size\": fs,\n                fill: opts.color || \"#1e293b\",\n                \"text-anchor\": \"middle\",\n                \"font-weight\": opts.weight || 800\n            });\n        }\n        function swatchLabel(group, x, y, color, label) {\n            line(group, x, y - 4, x + 22, y - 4, { stroke: color, \"stroke-width\": 3, \"stroke-linecap\": \"round\" });\n            text(group, x + 29, y, label, { \"font-size\": 9.5, fill: \"#475569\", \"font-weight\": 700 });\n        }\n        var VERDICT_TARGETS = {\n            height: { chip: \"ekChipHeight\", text: \"ekTextHeight\" },\n            dgm: { chip: \"ekChipDgm\", text: \"ekTextDgm\" },\n            crossing: { chip: \"ekChipCrossing\", text: \"ekTextCrossing\" },\n            surface: { chip: \"ekChipSurface\", text: \"ekTextSurface\" }\n        };\n        function setVerdict(kind, chip, html, rule) {\n            var t = VERDICT_TARGETS[state.topic];\n            if (!t) { return; }\n            var c = $(t.chip);\n            c.className = \"ek-chip\" + (kind ? \" \" + kind : \"\");\n            c.textContent = chip;\n            $(t.text).innerHTML = html;\n            $(\"ekRule\").innerHTML = rule;\n        }\n        function bounds(values, padRatio) {\n            var lo = Math.min.apply(null, values);\n            var hi = Math.max.apply(null, values);\n            if (hi - lo < 0.6) {\n                var mid = (hi + lo) \/ 2;\n                lo = mid - 0.3;\n                hi = mid + 0.3;\n            }\n            var pad = (hi - lo) * (padRatio || 0.16);\n            return { lo: lo - pad, hi: hi + pad };\n        }\n\n        var nextId = 0;\n        function mkPoint(type, st, z, lane) {\n            nextId += 1;\n            return { id: \"p\" + nextId, type: type, st: st, z: z, lane: lane };\n        }\n        function baseHeightPoints() {\n            nextId = 0;\n            return [\n                mkPoint(\"pi\", 0, 124.00, 0),\n                mkPoint(\"hp\", 15, 124.45, 0),\n                mkPoint(\"pi\", 30, 124.90, 1),\n                mkPoint(\"hp\", 45, 124.60, 0),\n                mkPoint(\"pi\", 60, 125.40, 0)\n            ];\n        }\n        function clonePoints(points) {\n            return points.map(function (p) { return { id: p.id, type: p.type, st: p.st, z: p.z, lane: p.lane }; });\n        }\n\n        var state = {\n            topic: \"height\",\n            points: baseHeightPoints(),\n            selected: null,\n            ghost: null,\n            lastAction: \"init\",\n            dgmMode: \"relative\",\n            dgmShift: 0,\n            dgmOffset: 0,\n            crossRule: \"prioA\",\n            asBreakline: false,\n            rebuildSurface: false,\n            circular: false,\n            drag: null\n        };\n        state.selected = state.points[1].id;\n\n        function idx(id) {\n            for (var i = 0; i < state.points.length; i += 1) {\n                if (state.points[i].id === id) { return i; }\n            }\n            return -1;\n        }\n        function selectedPoint() {\n            var i = idx(state.selected);\n            return i < 0 ? null : state.points[i];\n        }\n        function piList() { return state.points.filter(function (p) { return p.type === \"pi\"; }); }\n\n        var ST = { min: 0, max: 60, x0: 58, x1: 570 };\n        function xFromSt(st) { return ST.x0 + (st - ST.min) \/ (ST.max - ST.min) * (ST.x1 - ST.x0); }\n        function piPlanXY(p) { return { x: xFromSt(p.st), y: 88 - p.lane * 38 }; }\n        function pointPlanXY(p) {\n            if (p.type === \"pi\") { return piPlanXY(p); }\n            var pis = piList(), a = pis[0], b = pis[pis.length - 1];\n            for (var i = 0; i < pis.length; i += 1) {\n                if (pis[i].st <= p.st) { a = pis[i]; }\n                if (pis[i].st >= p.st) { b = pis[i]; break; }\n            }\n            if (a === b) { return piPlanXY(a); }\n            var pa = piPlanXY(a), pb = piPlanXY(b);\n            var t = (p.st - a.st) \/ (b.st - a.st);\n            return { x: pa.x + t * (pb.x - pa.x), y: pa.y + t * (pb.y - pa.y) };\n        }\n        function drawPointMark(group, p, x, y, active) {\n            if (p.type === \"pi\") {\n                rect(group, x - 5.6, y - 5.6, 11.2, 11.2, {\n                    fill: active ? \"#1e3548\" : \"#3f5f75\",\n                    stroke: \"#fff\",\n                    \"stroke-width\": 1.5\n                });\n            } else {\n                circle(group, x, y, 5.4, {\n                    fill: \"#fff\",\n                    stroke: active ? \"#15803d\" : \"#43a047\",\n                    \"stroke-width\": 2.4\n                });\n            }\n        }\n        function profileY(z, b, y0, y1) {\n            return y1 - (z - b.lo) \/ (b.hi - b.lo) * (y1 - y0);\n        }\n        function clamp(v, min, max) {\n            return Math.max(min, Math.min(max, v));\n        }\n        function canMovePi(p) {\n            if (!p) { return false; }\n            var i = idx(p.id);\n            if (p.type !== \"pi\") { return false; }\n            if (i <= 0) { return false; }\n            return i < state.points.length - 1;\n        }\n        function piMoveBounds(i) {\n            return {\n                minSt: Math.ceil(state.points[i - 1].st + 1),\n                maxSt: Math.floor(state.points[i + 1].st - 1)\n            };\n        }\n        function svgPoint(svg, evt) {\n            var pt = svg.createSVGPoint();\n            pt.x = evt.clientX;\n            pt.y = evt.clientY;\n            return pt.matrixTransform(svg.getScreenCTM().inverse());\n        }\n        function applyPiPlanPosition(p, svgPt) {\n            var i = idx(p.id);\n            if (i <= 0 || i >= state.points.length - 1) { return; }\n            var lim = piMoveBounds(i);\n            var rawSt = ST.min + (svgPt.x - ST.x0) \/ (ST.x1 - ST.x0) * (ST.max - ST.min);\n            p.st = clamp(Math.round(rawSt), lim.minSt, lim.maxSt);\n            p.lane = round2(clamp((88 - svgPt.y) \/ 38, -1.2, 1.2));\n        }\n        function stationFromProfileX(x) {\n            return ST.min + (x - ST.x0) \/ (ST.x1 - ST.x0) * (ST.max - ST.min);\n        }\n        function zFromProfileY(y, fixedBounds) {\n            var b = fixedBounds || state.profileBounds || bounds(state.points.map(function (p) { return p.z; }), 0.18);\n            var yy = clamp(y, 214, 304);\n            return round2(b.lo + (304 - yy) \/ (304 - 214) * (b.hi - b.lo));\n        }\n        function profileMoveAction(p) {\n            return p.type === \"hp\" ? \"moveHpProfile\" : \"movePiProfile\";\n        }\n        function applyProfilePosition(p, svgPt) {\n            var i = idx(p.id);\n            if (i < 0) { return; }\n            var fixedBounds = (state.drag ? state.drag.mode === \"profile\" : false) ? state.drag.bounds : null;\n            p.z = zFromProfileY(svgPt.y, fixedBounds);\n            if (!(p.type !== \"hp\" || i <= 0 || i >= state.points.length - 1)) {\n                var rawSt = stationFromProfileX(svgPt.x);\n                p.st = clamp(Math.round(rawSt), Math.ceil(state.points[i - 1].st + 1), Math.floor(state.points[i + 1].st - 1));\n            }\n        }\n\n        function renderHeight() {\n            var g = $(\"ekHeightDraw\");\n            clear(g);\n            card(g, 24, 16, 572, 142, \"Draufsicht\");\n            card(g, 24, 166, 572, 196, \"L\u00e4ngsprofil\");\n\n            var pis = piList();\n            if (state.ghost) {\n                poly(g, state.ghost.filter(function (p) { return p.type === \"pi\"; }).map(piPlanXY), { class: \"ek-svg-ghost\" });\n            }\n            poly(g, pis.map(piPlanXY), { fill: \"none\", stroke: \"#3f5f75\", \"stroke-width\": 2.7, \"stroke-linejoin\": \"round\", \"stroke-linecap\": \"round\" });\n            line(g, ST.x0, 132, ST.x1, 132, { class: \"ek-svg-axis\" });\n            state.points.forEach(function (p, i) {\n                var xy = pointPlanXY(p);\n                drawPointMark(g, p, xy.x, xy.y, p.id === state.selected);\n                labelBox(g, xy.x, xy.y - 12, \"P\" + (i + 1), {\n                    size: 9,\n                    width: 27,\n                    fill: p.id === state.selected ? \"#edf4f8\" : \"#fff\",\n                    stroke: p.id === state.selected ? \"#c5d5e0\" : \"#d9e4ec\",\n                    color: p.id === state.selected ? \"#1e293b\" : \"#64748b\"\n                });\n                if (p.type === \"pi\") {\n                    var hit = el(\"circle\", {\n                        cx: xy.x,\n                        cy: xy.y,\n                        r: 15,\n                        class: \"ek-drag-hit\" + (canMovePi(p) ? \"\" : \" is-locked\"),\n                        \"data-drag-pi\": p.id\n                    });\n                    g.appendChild(hit);\n                }\n            });\n\n            var allZ = state.points.map(function (p) { return p.z; });\n            if (state.ghost) { allZ = allZ.concat(state.ghost.map(function (p) { return p.z; })); }\n            var b = bounds(allZ, 0.18);\n            state.profileBounds = b;\n            line(g, ST.x0, 320, ST.x1, 320, { class: \"ek-svg-axis\" });\n            if (state.ghost) {\n                poly(g, state.ghost.map(function (p) {\n                    return { x: xFromSt(p.st), y: profileY(p.z, b, 214, 304) };\n                }), { class: \"ek-svg-ghost\" });\n            }\n            poly(g, state.points.map(function (p) {\n                return { x: xFromSt(p.st), y: profileY(p.z, b, 214, 304) };\n            }), { class: \"ek-svg-profile\" });\n\n            for (var i = 0; i < state.points.length - 1; i += 1) {\n                var a = state.points[i], c = state.points[i + 1];\n                var grade = (c.z - a.z) \/ (c.st - a.st) * 100;\n                var gx = (xFromSt(a.st) + xFromSt(c.st)) \/ 2;\n                var gy = (profileY(a.z, b, 214, 304) + profileY(c.z, b, 214, 304)) \/ 2 - 9;\n                labelBox(g, gx, gy, fmtSigned(grade, 1) + \" %\", {\n                    size: 9,\n                    fill: COLORS.warnBg,\n                    stroke: COLORS.warnBorder,\n                    color: COLORS.warnDeep,\n                    width: 48\n                });\n            }\n            state.points.forEach(function (p, i) {\n                var x = xFromSt(p.st), y = profileY(p.z, b, 214, 304);\n                drawPointMark(g, p, x, y, p.id === state.selected);\n                labelBox(g, x, y - 13, fmt(p.z, 2), {\n                    size: 8.5,\n                    width: 46,\n                    fill: p.id === state.selected ? \"#edf4f8\" : \"#fff\",\n                    stroke: p.id === state.selected ? \"#c5d5e0\" : \"#d9e4ec\",\n                    color: p.id === state.selected ? \"#1e293b\" : \"#64748b\",\n                    weight: p.id === state.selected ? 800 : 650\n                });\n                text(g, x, 338, p.st + \" m\", {\n                    \"font-size\": 9,\n                    fill: \"#94a3b8\",\n                    \"text-anchor\": \"middle\"\n                });\n                if (i === 0 || i === state.points.length - 1) {\n                    line(g, x, 316, x, 324, { stroke: \"#cbd5e1\", \"stroke-width\": 1 });\n                }\n                var profileHit = el(\"circle\", {\n                    cx: x,\n                    cy: y,\n                    r: 14,\n                    class: \"ek-drag-hit\",\n                    \"data-drag-profile\": p.id\n                });\n                g.appendChild(profileHit);\n            });\n\n            renderPointTable();\n            syncHeightInputs();\n            renderHeightVerdict();\n        }\n\n        function renderPointTable() {\n            var box = $(\"ekPointTable\");\n            clear(box);\n            state.points.forEach(function (p, i) {\n                var btn = document.createElement(\"button\");\n                btn.type = \"button\";\n                btn.className = \"ek-point-row\" + (p.id === state.selected ? \" is-active\" : \"\");\n                btn.setAttribute(\"data-point-id\", p.id);\n                var dot = document.createElement(\"span\");\n                dot.className = \"ek-dot \" + (p.type === \"pi\" ? \"is-pi\" : \"is-hp\");\n                var kind = document.createElement(\"span\");\n                kind.className = \"ek-point-kind\";\n                kind.textContent = \"P\" + (i + 1) + \" \u00b7 \" + (p.type === \"pi\" ? \"St\u00fctzpunkt\" : \"H\u00f6henpunkt\");\n                var meta = document.createElement(\"span\");\n                meta.className = \"ek-point-meta\";\n                meta.textContent = p.st + \" m\";\n                var z = document.createElement(\"span\");\n                z.className = \"ek-point-z\";\n                z.textContent = fmt(p.z, 2);\n                btn.appendChild(dot);\n                btn.appendChild(kind);\n                btn.appendChild(meta);\n                btn.appendChild(z);\n                box.appendChild(btn);\n            });\n        }\n        function syncHeightInputs() {\n            var p = selectedPoint();\n            var h = $(\"ekHeightValue\"), gr = $(\"ekGradeValue\");\n            var setGrade = $(\"ekSetGrade\"), addHp = $(\"ekAddHp\"), addPi = $(\"ekAddPi\"), rm = $(\"ekRemovePoint\");\n            var piStation = $(\"ekPiStation\"), piLane = $(\"ekPiLane\");\n            var piStationText = $(\"ekPiStationText\"), piLaneText = $(\"ekPiLaneText\"), piMoveHint = $(\"ekPiMoveHint\");\n            if (!p) { return; }\n            h.value = p.z.toFixed(2);\n            var i = idx(p.id);\n            var movablePi = canMovePi(p);\n            piStation.disabled = !movablePi;\n            piLane.disabled = !movablePi;\n            if (movablePi) {\n                var lim = piMoveBounds(i);\n                piStation.min = lim.minSt;\n                piStation.max = lim.maxSt;\n                piStation.value = p.st;\n                piLane.value = p.lane;\n                piStationText.textContent = fmt(p.st, 0) + \" m\";\n                piLaneText.textContent = fmt(p.lane, 1);\n                piMoveHint.textContent = \"Draufsicht: St\u00fctzpunkt frei ziehen. L\u00e4ngsprofil: denselben Punkt vertikal in der H\u00f6he ziehen.\";\n            } else {\n                piStationText.textContent = \"-\";\n                piLaneText.textContent = \"-\";\n                piMoveHint.textContent = p.type === \"pi\"\n                    ? \"Im L\u00e4ngsprofil kannst du die H\u00f6he ziehen. Die Endpunkte bleiben in der Draufsicht als Modellgrenzen fixiert.\"\n                    : \"H\u00f6henpunkte bleiben in der Draufsicht auf der Elementkante. Im L\u00e4ngsprofil kannst du H\u00f6he und Station entlang der Kante ziehen.\";\n            }\n            if (i < state.points.length - 1) {\n                var n = state.points[i + 1];\n                gr.value = (((n.z - p.z) \/ (n.st - p.st)) * 100).toFixed(1);\n                setGrade.disabled = false;\n            } else {\n                gr.value = \"\";\n                setGrade.disabled = true;\n            }\n            addHp.disabled = i >= state.points.length - 1 || state.points.length >= 9;\n            addPi.disabled = i >= state.points.length - 1 || state.points.length >= 9;\n            rm.disabled = i === 0 || i === state.points.length - 1 || state.points.length <= 3;\n        }\n        function renderHeightVerdict() {\n            var p = selectedPoint();\n            if (!p) { return; }\n            var kind = \"is-good\";\n            var chip = p.type === \"pi\" ? \"St\u00fctzpunkt\" : \"H\u00f6henpunkt\";\n            var base = p.type === \"pi\"\n                ? \"<strong>St\u00fctzpunkt gew\u00e4hlt.<\/strong> Er ist ein echter Geometriepunkt. In der Draufsicht kann hier ein Knick entstehen, im H\u00f6henprofil ebenfalls.\"\n                : \"<strong>H\u00f6henpunkt gew\u00e4hlt.<\/strong> Er liegt im Grundriss auf der vorhandenen Elementkante. Er erzeugt nur im H\u00f6henprofil einen Knick.\";\n            var extra = \"\";\n            if (state.lastAction === \"height\") {\n                extra = \" Die Einzelh\u00f6he ver\u00e4ndert nur diesen Punkt; die angrenzenden Neigungen \u00e4ndern sich dadurch sichtbar.\";\n            } else if (state.lastAction === \"grade\") {\n                extra = \" Die Neigung wurde ab dem gew\u00e4hlten Punkt gesetzt. Der n\u00e4chste Punkt und die Folgeh\u00f6hen verschieben sich als Block, damit der nachfolgende Verlauf erhalten bleibt.\";\n            } else if (state.lastAction === \"addHp\") {\n                extra = \" Der neue H\u00f6henpunkt ver\u00e4ndert den Grundriss nicht, aber er gibt dem Profil einen zus\u00e4tzlichen Griff.\";\n            } else if (state.lastAction === \"addPi\") {\n                extra = \" Der neue St\u00fctzpunkt ist auch im Lageplan sichtbar; genau darum ist er mehr als nur eine H\u00f6he.\";\n            } else if (state.lastAction === \"remove\") {\n                extra = \" Der Punkt wurde entfernt; Civil 3D interpoliert den H\u00f6henverlauf zwischen den Nachbarn neu.\";\n            } else if (state.lastAction === \"movePi\") {\n                extra = \" Der St\u00fctzpunkt wurde im Lageplan verschoben. H\u00f6henpunkte bleiben auf der Elementkante und wandern in der Draufsicht mit der neuen Geometrie mit.\";\n            } else if (state.lastAction === \"movePiProfile\") {\n                extra = \" Die H\u00f6he dieses St\u00fctzpunkts wurde im L\u00e4ngsprofil ge\u00e4ndert; seine Lage in der Draufsicht bleibt dabei erhalten.\";\n            } else if (state.lastAction === \"moveHpProfile\") {\n                extra = \" Der H\u00f6henpunkt wurde im L\u00e4ngsprofil verschoben. Seine H\u00f6he und Station \u00e4ndern sich; in der Draufsicht bleibt er auf der Elementkante.\";\n            }\n            setVerdict(kind, chip, base + extra, \"<strong>Grundregel:<\/strong> \u00c4ndert sich die Richtung im Lageplan, brauchst du einen St\u00fctzpunkt. \u00c4ndert sich nur der H\u00f6henverlauf, reicht ein H\u00f6henpunkt.\");\n        }\n\n        function setHeight() {\n            var p = selectedPoint();\n            if (!p) { return; }\n            var v = num(\"ekHeightValue\");\n            state.ghost = clonePoints(state.points);\n            p.z = round2(v);\n            state.lastAction = \"height\";\n            renderHeight();\n        }\n        function setGrade() {\n            var p = selectedPoint();\n            if (!p) { return; }\n            var i = idx(p.id);\n            if (i >= state.points.length - 1) { return; }\n            var n = state.points[i + 1];\n            var g = num(\"ekGradeValue\");\n            state.ghost = clonePoints(state.points);\n            var newZ = round2(p.z + g \/ 100 * (n.st - p.st));\n            var delta = round2(newZ - n.z);\n            for (var j = i + 1; j < state.points.length; j += 1) {\n                state.points[j].z = round2(state.points[j].z + delta);\n            }\n            state.lastAction = \"grade\";\n            renderHeight();\n        }\n        function insertAfter(type) {\n            var p = selectedPoint();\n            if (!p) { return; }\n            var i = idx(p.id);\n            if (i >= state.points.length - 1 || state.points.length >= 9) { return; }\n            var n = state.points[i + 1];\n            var st = Math.round((p.st + n.st) \/ 2);\n            var z = round2(p.z + (n.z - p.z) * (st - p.st) \/ (n.st - p.st));\n            var np = mkPoint(type, st, z, type === \"pi\" ? (p.lane === 0 ? 1 : 0) : 0);\n            state.ghost = clonePoints(state.points);\n            state.points.splice(i + 1, 0, np);\n            state.selected = np.id;\n            state.lastAction = type === \"pi\" ? \"addPi\" : \"addHp\";\n            renderHeight();\n        }\n        function removePoint() {\n            var i = idx(state.selected);\n            if (i <= 0 || i >= state.points.length - 1 || state.points.length <= 3) { return; }\n            state.ghost = clonePoints(state.points);\n            state.points.splice(i, 1);\n            state.selected = state.points[Math.max(0, i - 1)].id;\n            state.lastAction = \"remove\";\n            renderHeight();\n        }\n        function resetHeight() {\n            state.points = baseHeightPoints();\n            state.selected = state.points[1].id;\n            state.ghost = null;\n            state.lastAction = \"init\";\n            renderHeight();\n        }\n        function moveSelectedPi() {\n            var p = selectedPoint();\n            if (!canMovePi(p)) { return; }\n            var i = idx(p.id);\n            if (state.lastAction !== \"movePi\") {\n                state.ghost = clonePoints(state.points);\n            }\n            var lim = piMoveBounds(i);\n            p.st = clamp(Math.round(num(\"ekPiStation\")), lim.minSt, lim.maxSt);\n            p.lane = round2(parseFloat($(\"ekPiLane\").value) || 0);\n            state.lastAction = \"movePi\";\n            renderHeight();\n        }\n\n        function dgmBase(st, shift) {\n            return 123.65 + shift + 0.38 * Math.sin((st \/ 60) * Math.PI) + 0.18 * Math.sin((st \/ 60) * Math.PI * 2.2);\n        }\n        function dgmFeatureZ(st) {\n            if (state.dgmMode === \"static\") { return dgmBase(st, 0) + state.dgmOffset; }\n            if (state.dgmMode === \"relative\") { return dgmBase(st, state.dgmShift) + state.dgmOffset; }\n            return 124.05 + 0.018 * st + (st === 30 ? 0.28 : 0);\n        }\n        function renderDgm() {\n            var g = $(\"ekDgmDraw\");\n            clear(g);\n            card(g, 24, 28, 572, 320, \"Bezugs-DGM und Elementkante\");\n            var stations = [0, 10, 20, 30, 40, 50, 60];\n            var featureStations = stations.slice();\n            var values = [];\n            var shiftInput = $(\"ekDgmShift\");\n            var shiftMin = parseFloat(shiftInput.min);\n            var shiftMax = parseFloat(shiftInput.max);\n            stations.forEach(function (st) {\n                values.push(dgmBase(st, shiftMin));\n                values.push(dgmBase(st, 0));\n                values.push(dgmBase(st, shiftMax));\n            });\n            featureStations.forEach(function (st) {\n                values.push(dgmFeatureZ(st));\n                values.push(124.05 + 0.018 * st + (st === 30 ? 0.28 : 0));\n                values.push(dgmBase(st, 0) + state.dgmOffset);\n                values.push(dgmBase(st, shiftMin) + state.dgmOffset);\n                values.push(dgmBase(st, shiftMax) + state.dgmOffset);\n            });\n            var b = bounds(values, 0.20);\n            function y(z) { return profileY(z, b, 106, 278); }\n            line(g, ST.x0, 292, ST.x1, 292, { class: \"ek-svg-axis\" });\n            for (var k = 0; k <= 6; k += 1) {\n                var sx = xFromSt(k * 10);\n                line(g, sx, 288, sx, 296, { stroke: \"#d9e4ec\", \"stroke-width\": 1 });\n                text(g, sx, 312, (k * 10) + \" m\", { \"font-size\": 9, fill: \"#94a3b8\", \"text-anchor\": \"middle\" });\n            }\n            var oldPts = stations.map(function (st) { return { x: xFromSt(st), y: y(dgmBase(st, 0)) }; });\n            var curPts = stations.map(function (st) { return { x: xFromSt(st), y: y(dgmBase(st, state.dgmShift)) }; });\n            var flPts = featureStations.map(function (st) { return { x: xFromSt(st), y: y(dgmFeatureZ(st)) }; });\n            if (Math.abs(state.dgmShift) > 0.001) {\n                poly(g, oldPts, { class: \"ek-svg-surface-old\" });\n            }\n            poly(g, curPts, { fill: \"none\", stroke: COLORS.dgm, \"stroke-width\": 8, opacity: 0.62, \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\" });\n            poly(g, flPts, { fill: \"none\", stroke: COLORS.primary, \"stroke-width\": 2.5, \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\" });\n            featureStations.forEach(function (st) {\n                var x = xFromSt(st), z = dgmFeatureZ(st);\n                circle(g, x, y(z), 4.8, { fill: \"#fff\", stroke: COLORS.primary, \"stroke-width\": 2 });\n                if (st === 0 || st === 20 || st === 40 || st === 60) {\n                    labelBox(g, x, y(z) - 12, fmt(z, 2), { size: 8.5, width: 47, fill: \"#fff\", stroke: \"#d9e4ec\", color: \"#1e293b\" });\n                }\n            });\n            rect(g, 404, 56, 154, state.dgmMode === \"relative\" ? 66 : 50, { fill: \"#f8fbfd\", stroke: \"#d9e4ec\", \"stroke-width\": 1, rx: 4 });\n            swatchLabel(g, 418, 75, COLORS.dgm, \"aktuelles DGM\");\n            swatchLabel(g, 418, 94, COLORS.primary, \"Elementkante\");\n            if (state.dgmMode === \"relative\") {\n                text(g, 418, 113, \"Z = DGM \" + (state.dgmOffset >= 0 ? \"+\" : \"\") + fmt(state.dgmOffset, 2) + \" m\", { \"font-size\": 9.5, fill: \"#1e3548\", \"font-weight\": 800 });\n            }\n            $(\"ekDgmShiftText\").textContent = fmtSigned(state.dgmShift, 2) + \" m\";\n            $(\"ekDgmStartOut\").textContent = fmt(dgmFeatureZ(0), 2) + \" m\";\n            $(\"ekDgmEndOut\").textContent = fmt(dgmFeatureZ(60), 2) + \" m\";\n            ROOT.querySelectorAll(\"#ekDgmMode button\").forEach(function (btn) {\n                btn.classList.toggle(\"is-active\", btn.getAttribute(\"data-dgm-mode\") === state.dgmMode);\n            });\n            $(\"ekDgmOffset\").disabled = state.dgmMode === \"free\";\n            renderDgmVerdict();\n        }\n        function renderDgmVerdict() {\n            if (state.dgmMode === \"free\") {\n                setVerdict(\"\", \"frei\", \"<strong>Keine DGM-Kopplung.<\/strong> Die Elementkante beh\u00e4lt ihre eigenen H\u00f6hen. Wenn sich das Bezugs-DGM \u00e4ndert, bleibt die Kante stehen.\", \"<strong>Merksatz:<\/strong> Eine freie Elementkante ist editierbar, aber sie folgt keiner Oberfl\u00e4che.\");\n            } else if (state.dgmMode === \"static\") {\n                setVerdict(\"is-warn\", \"statisch\", \"<strong>Einmal aus dem DGM \u00fcbernommen.<\/strong> Die H\u00f6hen wurden aus dem alten DGM abgelesen. Verschiebst du das DGM, bleibt die Elementkante auf der Momentaufnahme.\", \"<strong>Merksatz:<\/strong> \u201eH\u00f6hen aus DGM\u201c ist nicht automatisch dynamisch. Dynamisch wird es erst mit relativem DGM-Bezug.\");\n            } else {\n                setVerdict(\"is-lock\", \"relativ\", \"<strong>Relativ zum DGM.<\/strong> Die Elementkante folgt dem aktuellen DGM mit dem eingestellten Versatz. Darum verhalten sich solche H\u00f6hen im Editor oft wie gesteuerte Werte.\", \"<strong>Merksatz:<\/strong> Relativ bedeutet: DGM-H\u00f6he plus Versatz. Es bedeutet nicht automatisch, dass diese Kante das DGM auch als Bruchkante steuert.\");\n            }\n        }\n\n        function siteName(v) {\n            return v === \"bau\" ? \"Baugebiet\" : (v === \"wall\" ? \"Wall\" : \"<Keine>\");\n        }\n        function crossSameSite() {\n            var a = $(\"ekSiteA\").value, b = $(\"ekSiteB\").value;\n            if (a === \"none\") { return false; }\n            return a === b;\n        }\n        function crossWinner() {\n            if ($(\"ekCrossPi\").checked) { return \"A\"; }\n            return state.crossRule === \"prioB\" ? \"B\" : \"A\";\n        }\n        function renderCrossing() {\n            var g = $(\"ekCrossDraw\");\n            clear(g);\n            var same = crossSameSite();\n            var winner = crossWinner();\n            var zA = 124.80, zB = 123.95;\n            var common = winner === \"A\" ? zA : zB;\n            card(g, 24, 28, 572, 122, \"1 \u00b7 Lageplan\");\n            card(g, 24, 174, 270, 142, \"2 \u00b7 Wunschh\u00f6hen\");\n            card(g, 326, 174, 270, 142, \"3 \u00b7 Ergebnis in Civil 3D\");\n\n            line(g, 88, 108, 532, 82, { stroke: COLORS.lineA, \"stroke-width\": 3.2, \"stroke-linecap\": \"round\" });\n            line(g, 150, 133, 470, 58, { stroke: COLORS.lineB, \"stroke-width\": 3.2, \"stroke-linecap\": \"round\" });\n            circle(g, 310, 95, 8, { fill: same ? \"#fff7ed\" : \"#fff\", stroke: same ? COLORS.accent : \"#94a3b8\", \"stroke-width\": same ? 3 : 1.8 });\n            if ($(\"ekCrossPi\").checked) {\n                rect(g, 303.5, 88.5, 13, 13, { fill: \"#e8913a\", stroke: \"#fff\", \"stroke-width\": 1.5 });\n            }\n            labelBox(g, 178, 74, \"Kante A \u00b7 \" + siteName($(\"ekSiteA\").value), { width: 132, size: 9, color: COLORS.primaryDeep });\n            labelBox(g, 444, 56, \"Kante B \u00b7 \" + siteName($(\"ekSiteB\").value), { width: 132, size: 9, color: COLORS.lineB });\n            labelBox(g, 310, 138, same ? \"gemeinsames Gebiet\" : \"getrennte Gebiete\", {\n                width: same ? 142 : 140,\n                size: 9.5,\n                fill: same ? \"#fff7ed\" : \"#f8fbfd\",\n                stroke: same ? \"#fed7aa\" : \"#d9e4ec\",\n                color: same ? \"#b45309\" : \"#64748b\"\n            });\n\n            text(g, 310, 263, \"\u2192\", { \"font-size\": 26, fill: \"#94a3b8\", \"text-anchor\": \"middle\", \"font-weight\": 800 });\n            function iso(cx, cy, u, v, z) {\n                return { x: cx + u * 42 + v * 24, y: cy + v * 14 - z * 46 };\n            }\n            function drawIsoBase(cx, cy) {\n                var plane = [\n                    iso(cx, cy, -1.55, -1.15, 0),\n                    iso(cx, cy, 1.55, -1.15, 0),\n                    iso(cx, cy, 1.55, 1.15, 0),\n                    iso(cx, cy, -1.55, 1.15, 0),\n                    iso(cx, cy, -1.55, -1.15, 0)\n                ];\n                poly(g, plane, { fill: \"#f8fbfd\", stroke: \"#e5edf3\", \"stroke-width\": 1 });\n                line(g, iso(cx, cy, -1.55, 0, 0).x, iso(cx, cy, -1.55, 0, 0).y, iso(cx, cy, 1.55, 0, 0).x, iso(cx, cy, 1.55, 0, 0).y, { stroke: \"#e5edf3\", \"stroke-width\": 1 });\n                line(g, iso(cx, cy, 0, -1.15, 0).x, iso(cx, cy, 0, -1.15, 0).y, iso(cx, cy, 0, 1.15, 0).x, iso(cx, cy, 0, 1.15, 0).y, { stroke: \"#e5edf3\", \"stroke-width\": 1 });\n            }\n            function drawIsoPolyline(points, color) {\n                poly(g, points, { fill: \"none\", stroke: color, \"stroke-width\": 3.2, \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\" });\n            }\n            function drawIsoPoint(p, color, active) {\n                circle(g, p.x, p.y, active ? 6.5 : 5.2, { fill: active ? \"#fff7ed\" : \"#fff\", stroke: active ? COLORS.accent : color, \"stroke-width\": active ? 3 : 2.2 });\n            }\n            function drawIsoSupport(p, color) {\n                circle(g, p.x, p.y, 3.2, { fill: \"#fff\", stroke: color, \"stroke-width\": 1.5, opacity: 0.95 });\n            }\n            function drawIsoScene(cx, cy, resultMode) {\n                var high = 1, low = 0.06;\n                var a1 = iso(cx, cy, -1.35, 0, high);\n                var a2 = iso(cx, cy, 1.35, 0, high);\n                var b1 = iso(cx, cy, 0, -1.05, low);\n                var b2 = iso(cx, cy, 0, 1.05, low);\n                var pHigh = iso(cx, cy, 0, 0, high);\n                var pLow = iso(cx, cy, 0, 0, low);\n                var aPreHigh = iso(cx, cy, -0.46, 0, high);\n                var aPostHigh = iso(cx, cy, 0.46, 0, high);\n                var bPreLow = iso(cx, cy, 0, -0.42, low);\n                var bPostLow = iso(cx, cy, 0, 0.42, low);\n                drawIsoBase(cx, cy);\n                if (resultMode === \"result\" ? same : false) {\n                    if (winner === \"A\") {\n                        drawIsoPolyline([a1, aPreHigh, pHigh, aPostHigh, a2], COLORS.lineA);\n                        drawIsoPolyline([b1, bPreLow, pHigh, bPostLow, b2], COLORS.lineB);\n                        drawIsoSupport(bPreLow, COLORS.lineB);\n                        drawIsoSupport(bPostLow, COLORS.lineB);\n                    } else {\n                        drawIsoPolyline([b1, bPreLow, pLow, bPostLow, b2], COLORS.lineB);\n                        drawIsoPolyline([a1, aPreHigh, pLow, aPostHigh, a2], COLORS.lineA);\n                        drawIsoSupport(aPreHigh, COLORS.lineA);\n                        drawIsoSupport(aPostHigh, COLORS.lineA);\n                    }\n                    line(g, pLow.x, pLow.y, pHigh.x, pHigh.y, { stroke: COLORS.accent, \"stroke-width\": 1.8, \"stroke-dasharray\": \"4 3\" });\n                    var p = winner === \"A\" ? pHigh : pLow;\n                    if ($(\"ekCrossPi\").checked) {\n                        rect(g, p.x - 6.5, p.y - 6.5, 13, 13, { fill: COLORS.accent, stroke: \"#fff\", \"stroke-width\": 1.5 });\n                        labelBox(g, p.x, p.y - 10, \"PI \u00b7 \" + fmt(common, 2) + \" m\", { width: 88, size: 8.4, fill: \"#fff\", stroke: COLORS.border, color: COLORS.ink });\n                    } else {\n                        drawIsoPoint(p, COLORS.accent, true);\n                        labelBox(g, p.x, p.y - 10, \"Split \u00b7 \" + fmt(common, 2) + \" m\", { width: 94, size: 8.4, fill: \"#fff\", stroke: COLORS.border, color: COLORS.ink });\n                    }\n                    labelBox(g, cx + 56, cy - 13, \"lokaler Knick\", { width: 86, size: 8.4, fill: \"#fff7ed\", stroke: \"#fed7aa\", color: \"#b45309\" });\n                    text(g, cx, 310, winner === \"A\" ? \"Stil A gewinnt\" : \"Stil B gewinnt\", { \"font-size\": 9.5, fill: COLORS.muted, \"text-anchor\": \"middle\" });\n                } else {\n                    drawIsoPolyline([a1, aPreHigh, pHigh, aPostHigh, a2], COLORS.lineA);\n                    drawIsoPolyline([b1, bPreLow, pLow, bPostLow, b2], COLORS.lineB);\n                    line(g, pLow.x, pLow.y, pHigh.x, pHigh.y, { stroke: same ? COLORS.accent : \"#94a3b8\", \"stroke-width\": 1.6, \"stroke-dasharray\": \"4 3\" });\n                    drawIsoSupport(aPreHigh, COLORS.lineA);\n                    drawIsoSupport(aPostHigh, COLORS.lineA);\n                    drawIsoSupport(bPreLow, COLORS.lineB);\n                    drawIsoSupport(bPostLow, COLORS.lineB);\n                    drawIsoPoint(pHigh, COLORS.lineA, false);\n                    drawIsoPoint(pLow, COLORS.lineB, false);\n                    labelBox(g, pHigh.x - 34, pHigh.y - 9, \"A \" + fmt(zA, 2), { width: 70, size: 8.3, color: COLORS.primaryDeep });\n                    labelBox(g, pLow.x + 34, pLow.y + 20, \"B \" + fmt(zB, 2), { width: 70, size: 8.3, color: COLORS.lineB });\n                    text(g, cx, 310, resultMode === \"result\" ? \"keine H\u00f6henkopplung\" : \"zwei gew\u00fcnschte H\u00f6hen\", { \"font-size\": 9.5, fill: COLORS.muted, \"text-anchor\": \"middle\" });\n                }\n            }\n            drawIsoScene(158, 284, \"wish\");\n            drawIsoScene(462, 284, \"result\");\n            ROOT.querySelectorAll(\"#ekCrossRule button\").forEach(function (btn) {\n                btn.classList.toggle(\"is-active\", btn.getAttribute(\"data-cross-rule\") === state.crossRule);\n            });\n            renderCrossVerdict(same, winner, zA, zB, common);\n        }\n        function renderCrossVerdict(same, winner, zA, zB, common) {\n            if (!same) {\n                setVerdict(\"is-good\", \"getrennt\", \"<strong>Keine gemeinsame Gebietstopologie.<\/strong> Die Kanten kreuzen sich im Lageplan, behalten aber ihre eigenen H\u00f6hen. Genau so trennt man unabh\u00e4ngige Bauteile.\", \"<strong>Merksatz:<\/strong> Gebiete sind keine Ordner. Sie entscheiden, ob Elementkanten an Kreuzungen h\u00f6henm\u00e4\u00dfig miteinander reagieren.\");\n                return;\n            }\n            if ($(\"ekCrossPi\").checked) {\n                setVerdict(\"is-good\", \"PI kontrolliert\", \"<strong>Gleiches Gebiet, aber bewusster Kontrollpunkt.<\/strong> Am Schnitt entsteht eine gemeinsame H\u00f6he von \" + fmt(common, 2) + \" m. Der eingef\u00fcgte PI macht den lokalen H\u00f6henknick planbar statt nur abgeleitet.\", \"<strong>Merksatz:<\/strong> Split Points sind nicht direkt editierbar. Ein PI am Schnittpunkt gibt dir einen echten Griff.\");\n                return;\n            }\n            var source = winner === \"A\" ? \"Kante A\" : \"Kante B\";\n            var loser = winner === \"A\" ? \"Kante B\" : \"Kante A\";\n            var diff = Math.abs(zA - zB);\n            setVerdict(\"is-warn\", \"Split Point\", \"<strong>Gleiches Gebiet erzwingt eine gemeinsame H\u00f6he.<\/strong> \" + source + \" gibt \" + fmt(common, 2) + \" m vor; \" + loser + \" bleibt im Verlauf weitgehend auf seiner H\u00f6he, bekommt aber am Schnittpunkt einen lokalen H\u00f6henknick. Die freie H\u00f6hendifferenz w\u00e4re \" + fmt(diff, 2) + \" m gewesen.\", \"<strong>Merksatz:<\/strong> Bei kreuzenden Elementkanten im selben Gebiet ist Stilpriorit\u00e4t fachlich wirksam, nicht nur Darstellung.\");\n        }\n\n        function renderSurface() {\n            var g = $(\"ekSurfaceDraw\");\n            clear(g);\n            card(g, 24, 28, 572, 326, \"DGM-Wirkung: Definition und Neuaufbau\");\n\n            function stepChip(x, label, ok) {\n                rect(g, x, 62, 112, 28, {\n                    rx: 4,\n                    fill: ok ? \"#f0fdf4\" : \"#fff\",\n                    stroke: ok ? \"#86efac\" : \"#c8d6e0\",\n                    \"stroke-width\": 1.2\n                });\n                text(g, x + 56, 80, label, {\n                    \"font-size\": 9.5,\n                    fill: ok ? \"#15803d\" : \"#64748b\",\n                    \"text-anchor\": \"middle\",\n                    \"font-weight\": 800\n                });\n            }\n            stepChip(52, \"1 Objekt\", true);\n            line(g, 164, 76, 186, 76, { stroke: \"#c8d6e0\", \"stroke-width\": 1.5 });\n            stepChip(186, \"2 Definition\", state.asBreakline);\n            line(g, 298, 76, 320, 76, { stroke: \"#c8d6e0\", \"stroke-width\": 1.5 });\n            stepChip(320, \"3 Neuaufbau\", state.rebuildSurface);\n            line(g, 432, 76, 454, 76, { stroke: \"#c8d6e0\", \"stroke-width\": 1.5 });\n            stepChip(454, \"4 DGM folgt\", state.asBreakline ? state.rebuildSurface : false);\n\n            card(g, 42, 112, 250, 198, \"1 \u00b7 Nur Elementkante\");\n            card(g, 328, 112, 250, 198, state.asBreakline ? (state.rebuildSurface ? \"2 \u00b7 Nach DGM-Neuaufbau\" : \"2 \u00b7 Definition ohne Neuaufbau\") : \"2 \u00b7 Keine DGM-Definition\");\n\n            function oldTinPoints(x0, y0) {\n                return [\n                    { x: x0 + 24, y: y0 + 118 },\n                    { x: x0 + 76, y: y0 + 106 },\n                    { x: x0 + 128, y: y0 + 96 },\n                    { x: x0 + 180, y: y0 + 107 },\n                    { x: x0 + 226, y: y0 + 119 }\n                ];\n            }\n            function newTinPoints(x0, y0, breakPts) {\n                return [\n                    { x: x0 + 24, y: y0 + 118 },\n                    breakPts[0],\n                    breakPts[1],\n                    breakPts[2],\n                    { x: x0 + 226, y: y0 + 119 }\n                ];\n            }\n            function drawMesh(x0, y0, pts, rebuilt, muted) {\n                var base = y0 + 150;\n                var meshClass = rebuilt ? \"ek-svg-tin-new\" : \"ek-svg-tin\";\n                for (var i = 0; i < pts.length - 1; i += 1) {\n                    line(g, pts[i].x, pts[i].y, pts[i + 1].x, base, { class: meshClass, opacity: muted ? 0.45 : 1 });\n                    line(g, pts[i].x, base, pts[i + 1].x, pts[i + 1].y, { class: meshClass, opacity: muted ? 0.45 : 1 });\n                }\n                line(g, x0 + 24, base, x0 + 226, base, { class: \"ek-svg-axis\" });\n                poly(g, pts, {\n                    class: rebuilt ? \"ek-svg-surface\" : \"ek-svg-surface-old\",\n                    \"stroke-width\": rebuilt ? 3.2 : 2.2,\n                    opacity: muted ? 0.45 : 1\n                });\n            }\n            var brLeft = [{ x: 116, y: 171 }, { x: 166, y: 151 }, { x: 214, y: 181 }];\n            var brRight = [{ x: 402, y: 171 }, { x: 452, y: 151 }, { x: 500, y: 181 }];\n            drawMesh(48, 132, oldTinPoints(48, 132), false, false);\n            poly(g, brLeft, { class: \"ek-svg-warnline\", \"stroke-dasharray\": \"5 4\" });\n            labelBox(g, 167, 290, \"Objekt hat noch keine DGM-Wirkung\", { width: 170, size: 8.7, fill: COLORS.warnBg, stroke: COLORS.warnBorder, color: COLORS.warnDeep });\n\n            if (!state.asBreakline) {\n                drawMesh(334, 132, oldTinPoints(334, 132), false, false);\n                poly(g, brRight, { class: \"ek-svg-warnline\", \"stroke-dasharray\": \"5 4\" });\n                labelBox(g, 453, 290, \"DGM ignoriert die Kante\", { width: 140, size: 8.7, fill: COLORS.warnBg, stroke: COLORS.warnBorder, color: COLORS.warnDeep });\n            } else if (!state.rebuildSurface) {\n                drawMesh(334, 132, oldTinPoints(334, 132), false, false);\n                poly(g, brRight, { class: \"ek-svg-break\" });\n                line(g, 452, 158, 452, 225, { stroke: COLORS.accent, \"stroke-width\": 1.6, \"stroke-dasharray\": \"4 3\" });\n                labelBox(g, 453, 290, \"Oberfl\u00e4che bleibt alter Stand\", { width: 168, size: 8.7, fill: COLORS.warnBg, stroke: COLORS.warnBorder, color: COLORS.warnDeep });\n            } else {\n                drawMesh(334, 132, oldTinPoints(334, 132), false, true);\n                drawMesh(334, 132, newTinPoints(334, 132, brRight), true, false);\n                poly(g, brRight, { class: \"ek-svg-break\" });\n                brRight.forEach(function (p) { circle(g, p.x, p.y, 4.5, { fill: \"#fff\", stroke: COLORS.accent, \"stroke-width\": 2 }); });\n                labelBox(g, 453, 290, \"neues TIN liegt auf Bruchkante\", { width: 168, size: 8.7, fill: \"#f0fdf4\", stroke: \"#bbf7d0\", color: COLORS.goodDeep });\n            }\n\n            text(g, 58, 334, \"Kernaussage: Existieren, Definieren und Neuaufbauen sind drei getrennte Zust\u00e4nde.\", { \"font-size\": 10, fill: COLORS.muted, \"font-weight\": 700 });\n            $(\"ekSurfaceStatus\").textContent = state.asBreakline ? \"Breakline\" : \"Objekt\";\n            $(\"ekSurfaceReaction\").textContent = state.asBreakline ? (state.rebuildSurface ? \"wirkt\" : \"wartet\") : \"ignoriert\";\n            $(\"ekSurfaceObject\").checked = true;\n            $(\"ekAsBreakline\").checked = state.asBreakline;\n            $(\"ekRebuildSurface\").checked = state.rebuildSurface;\n            $(\"ekDgmFollows\").checked = state.asBreakline ? !!state.rebuildSurface : false;\n            var objectStep = ROOT.querySelector('[data-surface-step=\"object\"]');\n            var breakStep = ROOT.querySelector('[data-surface-step=\"breakline\"]');\n            var rebuildStep = ROOT.querySelector('[data-surface-step=\"rebuild\"]');\n            var followStep = ROOT.querySelector('[data-surface-step=\"follow\"]');\n            function syncStep(node, done, next) {\n                if (!node) { return; }\n                node.classList.toggle(\"is-done\", !!done);\n                node.classList.toggle(\"is-next\", !!next);\n                node.classList.toggle(\"is-locked\", !!node.querySelector(\"input:disabled\"));\n            }\n            syncStep(objectStep, true, false);\n            syncStep(breakStep, state.asBreakline, !state.asBreakline);\n            syncStep(rebuildStep, state.rebuildSurface, state.asBreakline ? !state.rebuildSurface : false);\n            syncStep(followStep, state.asBreakline ? state.rebuildSurface : false, false);\n            renderSurfaceVerdict();\n        }\n        function renderSurfaceVerdict() {\n            if (state.asBreakline ? state.circular : false) {\n                setVerdict(\"is-error\", \"Kreisbezug\", \"<strong>Stopp vor dem n\u00e4chsten Schritt.<\/strong> Die Kante h\u00e4ngt relativ an genau dem DGM, das sie gleichzeitig als Bruchkante steuert. Das ist eine typische Rebuild-Falle.\", \"<strong>Merksatz:<\/strong> Referenz-DGM und Ziel-DGM sauber trennen, wenn eine relative Elementkante als Bruchkante verwendet wird.\");\n            } else if (!state.asBreakline) {\n                setVerdict(\"is-warn\", \"Schritt 2\", \"<strong>Die Elementkante existiert nur als Objekt.<\/strong> Schritt 2 macht sie zur DGM-Definition: Erst als Bruchkante eingetragen wird die Linie Teil des Modells.\", \"<strong>Merksatz:<\/strong> Eine Elementkante ist nicht automatisch eine Bruchkante.\");\n            } else if (!state.rebuildSurface) {\n                setVerdict(\"is-warn\", \"Schritt 3\", \"<strong>Die Bruchkante ist definiert, aber die Oberfl\u00e4che zeigt noch den alten Stand.<\/strong> Schritt 3 macht es sichtbar: Erst der Neuaufbau \u00fcbernimmt die Kante in die Triangulation.\", \"<strong>Merksatz:<\/strong> DGM-Daten und sichtbare Triangulation sind erst nach dem Neuaufbau wieder synchron.\");\n            } else {\n                setVerdict(\"is-good\", \"wirksam\", \"<strong>Ablauf vollst\u00e4ndig.<\/strong> Die Elementkante wirkt als Bruchkante. Das TIN wird entlang der Kante gef\u00fchrt und bildet den Knick statt dar\u00fcber zu gl\u00e4tten.\", \"<strong>Merksatz:<\/strong> Als Bruchkante erzwingt die Elementkante Geometrie und H\u00f6he im DGM.\");\n            }\n        }\n\n        function renderTopic() {\n            if (state.topic === \"height\") { renderHeight(); }\n            else if (state.topic === \"dgm\") { renderDgm(); }\n            else if (state.topic === \"crossing\") { renderCrossing(); }\n            else { renderSurface(); }\n        }\n        function setTopic(topic) {\n            state.topic = topic;\n            ROOT.querySelectorAll(\".ek-focus-btn\").forEach(function (btn) {\n                btn.classList.toggle(\"is-active\", btn.getAttribute(\"data-ek-topic\") === topic);\n            });\n            ROOT.querySelectorAll(\".ek-topic\").forEach(function (panel) {\n                panel.classList.toggle(\"is-active\", panel.getAttribute(\"data-topic\") === topic);\n            });\n            renderTopic();\n        }\n\n        ROOT.addEventListener(\"click\", function (e) {\n            var topicBtn = e.target.closest ? e.target.closest(\"[data-ek-topic]\") : null;\n            if (topicBtn ? ROOT.contains(topicBtn) : false) { setTopic(topicBtn.getAttribute(\"data-ek-topic\")); return; }\n            var pointBtn = e.target.closest ? e.target.closest(\"[data-point-id]\") : null;\n            if (pointBtn ? ROOT.contains(pointBtn) : false) {\n                state.selected = pointBtn.getAttribute(\"data-point-id\");\n                state.lastAction = \"select\";\n                renderHeight();\n                return;\n            }\n            var dgmMode = e.target.closest ? e.target.closest(\"[data-dgm-mode]\") : null;\n            if (dgmMode ? ROOT.contains(dgmMode) : false) {\n                state.dgmMode = dgmMode.getAttribute(\"data-dgm-mode\");\n                renderDgm();\n                return;\n            }\n            var crossRule = e.target.closest ? e.target.closest(\"[data-cross-rule]\") : null;\n            if (crossRule ? ROOT.contains(crossRule) : false) {\n                state.crossRule = crossRule.getAttribute(\"data-cross-rule\");\n                renderCrossing();\n                return;\n            }\n            if (e.target.id === \"ekSetHeight\") { setHeight(); return; }\n            if (e.target.id === \"ekSetGrade\") { setGrade(); return; }\n            if (e.target.id === \"ekAddHp\") { insertAfter(\"hp\"); return; }\n            if (e.target.id === \"ekAddPi\") { insertAfter(\"pi\"); return; }\n            if (e.target.id === \"ekRemovePoint\") { removePoint(); return; }\n            if (e.target.id === \"ekResetHeight\") { resetHeight(); }\n        });\n\n        ROOT.addEventListener(\"pointerdown\", function (e) {\n            var profileHit = e.target.closest ? e.target.closest(\"[data-drag-profile]\") : null;\n            if (profileHit ? ROOT.contains(profileHit) : false) {\n                var pp = state.points.find(function (item) { return item.id === profileHit.getAttribute(\"data-drag-profile\"); });\n                if (!pp) { return; }\n                e.preventDefault();\n                state.selected = pp.id;\n                state.ghost = clonePoints(state.points);\n                state.drag = {\n                    id: pp.id,\n                    pointerId: e.pointerId,\n                    svg: $(\"ekSvgHeight\"),\n                    mode: \"profile\",\n                    bounds: Object.assign({}, state.profileBounds)\n                };\n                try { profileHit.setPointerCapture(e.pointerId); } catch (ignore) {}\n                applyProfilePosition(pp, svgPoint(state.drag.svg, e));\n                state.lastAction = profileMoveAction(pp);\n                renderHeight();\n                return;\n            }\n            var hit = e.target.closest ? e.target.closest(\"[data-drag-pi]\") : null;\n            if (!hit || !ROOT.contains(hit)) { return; }\n            var p = state.points.find(function (item) { return item.id === hit.getAttribute(\"data-drag-pi\"); });\n            if (!p) { return; }\n            state.selected = p.id;\n            if (!canMovePi(p)) {\n                state.lastAction = \"select\";\n                renderHeight();\n                return;\n            }\n            e.preventDefault();\n            state.ghost = clonePoints(state.points);\n            state.drag = {\n                id: p.id,\n                pointerId: e.pointerId,\n                svg: $(\"ekSvgHeight\"),\n                mode: \"plan\"\n            };\n            try { hit.setPointerCapture(e.pointerId); } catch (ignore2) {}\n            applyPiPlanPosition(p, svgPoint(state.drag.svg, e));\n            state.lastAction = \"movePi\";\n            renderHeight();\n        });\n        window.addEventListener(\"pointermove\", function (e) {\n            if (!state.drag || e.pointerId !== state.drag.pointerId || state.topic !== \"height\") { return; }\n            var p = state.points.find(function (item) { return item.id === state.drag.id; });\n            if (!p) { return; }\n            e.preventDefault();\n            if (state.drag.mode === \"profile\") {\n                applyProfilePosition(p, svgPoint(state.drag.svg, e));\n                state.lastAction = profileMoveAction(p);\n            } else {\n                if (!canMovePi(p)) { return; }\n                applyPiPlanPosition(p, svgPoint(state.drag.svg, e));\n                state.lastAction = \"movePi\";\n            }\n            renderHeight();\n        });\n        window.addEventListener(\"pointerup\", function (e) {\n            if (state.drag ? e.pointerId === state.drag.pointerId : false) {\n                state.drag = null;\n            }\n        });\n        window.addEventListener(\"pointercancel\", function (e) {\n            if (state.drag ? e.pointerId === state.drag.pointerId : false) {\n                state.drag = null;\n            }\n        });\n\n        ROOT.addEventListener(\"input\", function (e) {\n            if (e.target.id === \"ekDgmShift\") {\n                state.dgmShift = parseFloat(e.target.value) || 0;\n                renderDgm();\n            } else if (e.target.id === \"ekDgmOffset\") {\n                state.dgmOffset = num(\"ekDgmOffset\");\n                renderDgm();\n            } else if (e.target.id === \"ekPiStation\" || e.target.id === \"ekPiLane\") {\n                moveSelectedPi();\n            }\n        });\n        ROOT.addEventListener(\"change\", function (e) {\n            if (e.target.id === \"ekSiteA\" || e.target.id === \"ekSiteB\" || e.target.id === \"ekCrossPi\") {\n                renderCrossing();\n            } else if (e.target.id === \"ekAsBreakline\") {\n                state.asBreakline = e.target.checked;\n                if (!state.asBreakline) {\n                    state.rebuildSurface = false;\n                    $(\"ekRebuildSurface\").checked = false;\n                }\n                renderSurface();\n            } else if (e.target.id === \"ekRebuildSurface\") {\n                state.rebuildSurface = e.target.checked;\n                if (state.rebuildSurface ? !state.asBreakline : false) {\n                    state.asBreakline = true;\n                    $(\"ekAsBreakline\").checked = true;\n                }\n                renderSurface();\n            } else if (e.target.id === \"ekCircular\") {\n                state.circular = e.target.checked;\n                renderSurface();\n            }\n        });\n        ROOT.addEventListener(\"keydown\", function (e) {\n            if (e.key !== \"Enter\") { return; }\n            if (e.target.id === \"ekHeightValue\") { e.preventDefault(); setHeight(); }\n            if (e.target.id === \"ekGradeValue\") { e.preventDefault(); setGrade(); }\n        });\n\n        renderTopic();\n    })();\n<\/script>\n\n","protected":false},"excerpt":{"rendered":"<p>Elementkanten-Explainer Punktarten, Neigung, DGM-Bezug, Gebietskreuzungen und Bruchkantenwirkung in einem interaktiven Modell. L\u00e4uft komplett im Browser. Keine Daten verlassen deinen Rechner. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_uag_custom_page_level_css":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"disabled","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[28],"tags":[],"class_list":["post-2076","post","type-post","status-publish","format-standard","hentry","category-elementkanten"],"uagb_featured_image_src":{"full":false,"thumbnail":false,"medium":false,"medium_large":false,"large":false,"1536x1536":false,"2048x2048":false},"uagb_author_info":{"display_name":false,"author_link":"https:\/\/cad-kompass.de\/?author=1"},"uagb_comment_info":0,"uagb_excerpt":"Elementkanten-Explainer Punktarten, Neigung, DGM-Bezug, Gebietskreuzungen und Bruchkantenwirkung in einem interaktiven Modell. L\u00e4uft komplett im Browser. Keine Daten verlassen deinen Rechner. [&hellip;]","_links":{"self":[{"href":"https:\/\/cad-kompass.de\/index.php?rest_route=\/wp\/v2\/posts\/2076","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cad-kompass.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cad-kompass.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cad-kompass.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cad-kompass.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2076"}],"version-history":[{"count":6,"href":"https:\/\/cad-kompass.de\/index.php?rest_route=\/wp\/v2\/posts\/2076\/revisions"}],"predecessor-version":[{"id":2114,"href":"https:\/\/cad-kompass.de\/index.php?rest_route=\/wp\/v2\/posts\/2076\/revisions\/2114"}],"wp:attachment":[{"href":"https:\/\/cad-kompass.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2076"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cad-kompass.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2076"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cad-kompass.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2076"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}