INBOX/design-system-guide-v4 (3).html
2026-08-07 16:01:24 +02:00

1500 lines
94 KiB
HTML
Executable file
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Design System Guide</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Anton&family=Lobster+Two:ital,wght@0,400;0,700;1,400;1,700&family=Roboto+Slab:wght@400;600;700&family=Roboto+Condensed:wght@300;400;500;600&family=Roboto+Mono:wght@400;500&display=swap');
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#0f0f10;--surface:#18181b;--surface2:#1f1f24;--surface3:#26262c;
--border:#2a2a32;--border2:#353540;
--accent:#7c6dfa;--accent2:#c084fc;--gold:#f5c842;
--text:#e8e6f0;--muted:#7a7890;--muted2:#5a5870;
--success:#4ade80;--danger:#f87171;--warning:#fbbf24;--info:#60a5fa;
--radius:10px;--radius-lg:14px;
--font-body:'Roboto Condensed',sans-serif;--font-display:'Anton',serif;--font-heading:'Lobster Two',cursive;--font-slab:'Roboto Slab',serif;--font-mono:'Roboto Mono',monospace;
}
body{background:var(--bg);color:var(--text);font-family:var(--font-body);font-size:15px;line-height:1.7;min-height:100vh}
.app{display:flex;min-height:100vh}
/* ── Sidebar ── */
.sidebar{width:270px;min-width:270px;background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;position:sticky;top:0;height:100vh;overflow-y:auto}
.sidebar-header{padding:24px 20px 18px;border-bottom:1px solid var(--border)}
.sidebar-logo{font-family:var(--font-display);font-size:17px;color:var(--text)}
.sidebar-logo span{color:var(--accent)}
.project-section{padding:16px 20px;border-bottom:1px solid var(--border)}
.section-label{font-size:10.5px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-bottom:10px}
.project-list{display:flex;flex-direction:column;gap:3px}
.project-item{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:8px;cursor:pointer;border:1px solid transparent;transition:all .15s;font-size:13px;color:var(--muted)}
.project-item:hover{background:var(--surface2);color:var(--text)}
.project-item.active{background:rgba(124,109,250,.12);border-color:rgba(124,109,250,.25);color:var(--text)}
.project-dot{width:7px;height:7px;border-radius:50%;background:var(--border);flex-shrink:0}
.project-item.active .project-dot{background:var(--accent)}
.project-pct{margin-left:auto;font-size:11px;font-family:var(--font-mono);color:var(--muted)}
.project-item.active .project-pct{color:var(--accent)}
.add-btn{display:flex;align-items:center;gap:7px;padding:8px 10px;border-radius:8px;cursor:pointer;font-size:12.5px;color:var(--muted);background:none;border:1px dashed var(--border);width:100%;margin-top:7px;transition:all .15s;font-family:var(--font-body)}
.add-btn:hover{border-color:var(--accent);color:var(--accent)}
.new-proj-form{display:none;margin-top:7px}
.new-proj-form.show{display:block}
.new-proj-form input{width:100%;background:var(--surface2);border:1px solid var(--border);color:var(--text);padding:7px 11px;border-radius:8px;font-size:13px;font-family:var(--font-body);outline:none;transition:border-color .15s}
.new-proj-form input:focus{border-color:var(--accent)}
.new-proj-form input::placeholder{color:var(--muted)}
.form-btns{display:flex;gap:5px;margin-top:5px}
.btn-xs{padding:5px 11px;border-radius:6px;font-size:12px;cursor:pointer;border:none;font-family:var(--font-body);transition:opacity .15s}
.btn-xs:hover{opacity:.85}
.btn-ok{background:var(--accent);color:#fff}
.btn-no{background:var(--surface2);color:var(--muted)}
/* Nav */
.nav-wrap{padding:16px 20px;flex:1}
.nav-item{display:flex;align-items:center;gap:8px;padding:7px 10px;border-radius:8px;cursor:pointer;font-size:13px;color:var(--muted);transition:all .15s;margin-bottom:2px;text-decoration:none}
.nav-item:hover{background:var(--surface2);color:var(--text)}
.nav-badge{margin-left:auto;font-size:10px;font-family:var(--font-mono);background:var(--surface2);padding:2px 7px;border-radius:99px;color:var(--muted)}
.nav-item.all-done .nav-badge{background:rgba(74,222,128,.1);color:var(--success)}
.nav-sep{height:1px;background:var(--border);margin:10px 0}
.nav-special{color:var(--accent) !important;font-weight:500}
/* Footer */
.sidebar-footer{padding:14px 20px;border-top:1px solid var(--border);display:flex;flex-direction:column;gap:7px}
.export-btn{width:100%;padding:10px;background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;border:none;border-radius:var(--radius);font-size:13px;font-weight:500;cursor:pointer;font-family:var(--font-body);transition:opacity .15s}
.export-btn:hover{opacity:.88}
.save-hint{font-size:11px;color:var(--muted);text-align:center;line-height:1.5}
/* ── Main ── */
.main{flex:1;padding:44px 52px;max-width:900px;overflow-x:hidden}
/* Page header */
.page-header{margin-bottom:40px}
.proj-title-row{display:flex;align-items:center;gap:12px;margin-bottom:6px}
.proj-name{font-family:var(--font-display);font-size:34px;color:var(--text);letter-spacing:-.5px;line-height:1.15;cursor:pointer;border-bottom:2px solid transparent;transition:border-color .2s}
.proj-name:hover{border-color:var(--accent)}
.proj-name-input{font-family:var(--font-display);font-size:34px;background:none;border:none;border-bottom:2px solid var(--accent);color:var(--text);outline:none;width:100%;letter-spacing:-.5px}
.page-sub{color:var(--muted);font-size:13.5px;margin-top:3px}
/* Progress */
.progress-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:20px 22px;margin-bottom:36px}
.prog-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}
.prog-label{font-size:13px;color:var(--muted)}
.prog-pct{font-family:var(--font-mono);font-size:22px;font-weight:500}
.prog-track{height:5px;background:var(--surface2);border-radius:99px;overflow:hidden}
.prog-fill{height:100%;background:linear-gradient(90deg,var(--accent),var(--accent2));border-radius:99px;transition:width .4s cubic-bezier(.4,0,.2,1)}
.prog-pills{display:flex;gap:7px;margin-top:14px;flex-wrap:wrap}
.prog-pill{display:flex;align-items:center;gap:5px;padding:3px 9px;border-radius:99px;font-size:11px;background:var(--surface2);color:var(--muted);border:1px solid var(--border)}
.prog-pill.done{background:rgba(74,222,128,.07);color:var(--success);border-color:rgba(74,222,128,.18)}
.prog-pill.partial{background:rgba(124,109,250,.07);color:var(--accent);border-color:rgba(124,109,250,.18)}
.pill-dot{width:5px;height:5px;border-radius:50%;background:currentColor}
/* ── Phase sections ── */
.phase-section{margin-bottom:52px}
.phase-header{display:flex;align-items:center;gap:14px;margin-bottom:20px;padding-bottom:14px;border-bottom:1px solid var(--border)}
.phase-num{font-family:var(--font-mono);font-size:11px;color:var(--accent);background:rgba(124,109,250,.1);border:1px solid rgba(124,109,250,.2);padding:3px 10px;border-radius:99px;letter-spacing:.05em}
.phase-title{font-family:var(--font-display);font-size:22px;color:var(--text);letter-spacing:-.3px}
.phase-stat{margin-left:auto;font-size:11px;font-family:var(--font-mono);color:var(--muted)}
.phase-stat.done{color:var(--success)}
.phase-intro{background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:var(--radius);padding:14px 18px;margin-bottom:22px;font-size:13.5px;color:var(--muted);line-height:1.65}
/* Subsections */
.subsection{margin-bottom:26px}
.sub-title{font-size:12px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-bottom:10px;display:flex;align-items:center;gap:8px}
.sub-title::after{content:'';flex:1;height:1px;background:var(--border)}
/* Checklist */
.checklist{display:flex;flex-direction:column;gap:3px}
.check-item{display:flex;align-items:flex-start;gap:11px;padding:11px 13px;border-radius:8px;cursor:pointer;border:1px solid transparent;transition:all .15s;background:var(--surface)}
.check-item:hover{border-color:var(--border);background:var(--surface2)}
.check-item.checked{background:rgba(74,222,128,.04);border-color:rgba(74,222,128,.1)}
.check-item.checked .check-text{color:var(--muted);text-decoration:line-through;text-decoration-color:rgba(122,120,144,.4)}
.check-box{width:17px;height:17px;border-radius:5px;border:1.5px solid var(--border);display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:2px;transition:all .15s;background:var(--surface2)}
.check-item.checked .check-box{background:var(--success);border-color:var(--success)}
.check-tick{width:9px;height:9px;opacity:0;transition:opacity .15s}
.check-item.checked .check-tick{opacity:1}
.check-content{flex:1;min-width:0}
.check-text{font-size:13.5px;color:var(--text);line-height:1.5;transition:color .15s}
.check-hint{font-size:11.5px;color:var(--muted);margin-top:2px}
/* ── Data fields ── */
.data-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:20px 22px;margin-top:8px;margin-bottom:20px}
.data-section-title{font-size:12px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--accent);margin-bottom:16px;display:flex;align-items:center;gap:8px}
.data-section-title svg{width:14px;height:14px;opacity:.8}
.data-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.data-grid.cols1{grid-template-columns:1fr}
.data-grid.cols3{grid-template-columns:1fr 1fr 1fr}
.field-group{display:flex;flex-direction:column;gap:5px}
.field-label{font-size:11.5px;color:var(--muted);font-weight:500}
.field-input{background:var(--surface2);border:1px solid var(--border);color:var(--text);padding:8px 11px;border-radius:8px;font-size:13px;font-family:var(--font-body);outline:none;transition:border-color .15s;width:100%}
.field-input:focus{border-color:var(--accent)}
.field-input::placeholder{color:var(--muted2)}
.field-input.mono{font-family:var(--font-mono);font-size:12px}
.field-select{background:var(--surface2);border:1px solid var(--border);color:var(--text);padding:8px 11px;border-radius:8px;font-size:13px;font-family:var(--font-body);outline:none;transition:border-color .15s;width:100%;cursor:pointer}
.field-select:focus{border-color:var(--accent)}
/* Color swatch row */
.color-row{display:flex;flex-direction:column;gap:8px}
.color-entry{display:flex;align-items:center;gap:10px}
.color-swatch-wrap{position:relative;width:36px;height:36px;border-radius:8px;overflow:hidden;border:1px solid var(--border);flex-shrink:0}
.color-swatch-wrap input[type=color]{position:absolute;inset:-4px;width:calc(100%+8px);height:calc(100%+8px);border:none;cursor:pointer;padding:0}
.color-hex{font-family:var(--font-mono);font-size:12px;background:var(--surface2);border:1px solid var(--border);color:var(--text);padding:7px 10px;border-radius:8px;width:100px;outline:none;transition:border-color .15s}
.color-hex:focus{border-color:var(--accent)}
.color-name-in{font-size:13px;font-family:var(--font-body);background:var(--surface2);border:1px solid var(--border);color:var(--text);padding:7px 10px;border-radius:8px;flex:1;outline:none;transition:border-color .15s}
.color-name-in:focus{border-color:var(--accent)}
.add-color-btn{display:flex;align-items:center;gap:6px;padding:6px 12px;border-radius:8px;cursor:pointer;font-size:12px;color:var(--muted);background:none;border:1px dashed var(--border2);transition:all .15s;font-family:var(--font-body);margin-top:2px;width:fit-content}
.add-color-btn:hover{border-color:var(--accent);color:var(--accent)}
.remove-color{background:none;border:none;cursor:pointer;color:var(--muted2);font-size:16px;padding:0 4px;transition:color .15s;line-height:1}
.remove-color:hover{color:var(--danger)}
/* Type scale preview */
.type-preview{display:flex;flex-direction:column;gap:4px;margin-top:8px;padding:14px 16px;background:var(--surface2);border-radius:var(--radius);border:1px solid var(--border)}
.type-row{display:flex;align-items:baseline;gap:12px}
.type-size-label{font-size:10px;font-family:var(--font-mono);color:var(--muted);width:32px;flex-shrink:0;text-align:right}
.type-sample{color:var(--text);line-height:1.3}
/* Spacing preview */
.spacing-preview{display:flex;align-items:flex-end;gap:10px;margin-top:8px;padding:14px 16px;background:var(--surface2);border-radius:var(--radius);border:1px solid var(--border);overflow-x:auto}
.sp-item{display:flex;flex-direction:column;align-items:center;gap:6px;flex-shrink:0}
.sp-bar{background:var(--accent);border-radius:3px;width:20px;opacity:.7}
.sp-label{font-size:10px;font-family:var(--font-mono);color:var(--muted)}
/* ── Style Output View ── */
.style-view{display:none}
.style-view.active{display:block}
.guide-view{display:block}
.guide-view.hidden{display:none}
.style-doc{max-width:860px}
.style-hero{margin-bottom:48px;padding-bottom:32px;border-bottom:1px solid var(--border)}
.style-hero-name{font-family:var(--font-display);font-size:48px;letter-spacing:-.8px;line-height:1.1;margin-bottom:8px}
.style-hero-meta{font-size:13px;color:var(--muted);display:flex;gap:16px}
.style-hero-meta span{display:flex;align-items:center;gap:5px}
.style-section{margin-bottom:44px}
.style-section-title{font-family:var(--font-display);font-size:26px;letter-spacing:-.3px;margin-bottom:20px;color:var(--text)}
.style-sub{font-size:11px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:12px;margin-top:20px}
/* Color swatches output */
.swatch-grid{display:flex;flex-wrap:wrap;gap:12px}
.swatch-card{border-radius:var(--radius);overflow:hidden;border:1px solid var(--border);width:120px}
.swatch-color{height:72px}
.swatch-info{padding:8px 10px;background:var(--surface)}
.swatch-role{font-size:11px;font-weight:600;color:var(--muted);letter-spacing:.05em;margin-bottom:2px}
.swatch-hex{font-size:12px;font-family:var(--font-mono);color:var(--text)}
.swatch-nm{font-size:11px;color:var(--muted);margin-top:1px}
/* Type output */
.type-specimen{border:1px solid var(--border);border-radius:var(--radius-lg);overflow:hidden;margin-bottom:8px}
.type-spec-row{display:flex;align-items:baseline;gap:16px;padding:14px 18px;border-bottom:1px solid var(--border)}
.type-spec-row:last-child{border-bottom:none}
.type-spec-meta{width:80px;flex-shrink:0;font-size:11px;font-family:var(--font-mono);color:var(--muted);line-height:1.4}
.type-spec-sample{flex:1;color:var(--text)}
/* Token output */
.token-block{background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius);padding:16px 18px;font-family:var(--font-mono);font-size:12px;color:#c9c5e8;overflow-x:auto;white-space:pre;line-height:1.75;margin-bottom:8px}
.token-comment{color:var(--muted)}
.token-key{color:#c084fc}
.token-val{color:#7dd3fc}
.token-str{color:#86efac}
/* Spacing output */
.spacing-out-row{display:flex;align-items:center;gap:14px;padding:8px 0;border-bottom:1px solid var(--border)}
.spacing-out-row:last-child{border-bottom:none}
.sp-out-name{font-family:var(--font-mono);font-size:12px;color:var(--accent);width:80px}
.sp-out-val{font-family:var(--font-mono);font-size:12px;color:var(--muted);width:50px}
.sp-out-bar-wrap{flex:1}
.sp-out-bar{height:8px;background:var(--accent);border-radius:99px;opacity:.5}
/* Grid output */
.grid-vis{display:flex;gap:4px;margin-top:12px;padding:16px;background:var(--surface2);border-radius:var(--radius);border:1px solid var(--border)}
.grid-col{flex:1;height:48px;background:rgba(124,109,250,.18);border-radius:4px}
/* Checklist output */
.checklist-out{display:flex;flex-direction:column;gap:4px}
.check-out-item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:6px;font-size:13px}
.check-out-item.done-item{color:var(--success)}
.check-out-item.skip-item{color:var(--muted);text-decoration:line-through}
.check-icon{flex-shrink:0;font-size:14px}
/* Button row */
.action-row{display:flex;gap:10px;margin-bottom:28px}
.btn-outline{padding:9px 18px;border-radius:var(--radius);font-size:13px;cursor:pointer;border:1px solid var(--border);color:var(--muted);background:none;font-family:var(--font-body);transition:all .15s}
.btn-outline:hover{border-color:var(--accent);color:var(--accent)}
.btn-primary-sm{padding:9px 18px;border-radius:var(--radius);font-size:13px;cursor:pointer;border:none;background:var(--accent);color:#fff;font-family:var(--font-body);font-weight:500;transition:opacity .15s}
.btn-primary-sm:hover{opacity:.88}
/* Modal */
.modal-bd{display:none;position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:100;align-items:center;justify-content:center}
.modal-bd.open{display:flex}
.modal{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:30px;width:500px;max-width:95vw}
.modal h2{font-family:var(--font-display);font-size:22px;margin-bottom:6px;letter-spacing:-.3px}
.modal p{font-size:13.5px;color:var(--muted);margin-bottom:20px;line-height:1.6}
.modal-opts{display:flex;flex-direction:column;gap:8px}
.modal-opt{display:flex;align-items:flex-start;gap:12px;padding:14px;border:1px solid var(--border);border-radius:var(--radius);cursor:pointer;transition:all .15s;background:var(--surface2)}
.modal-opt:hover{border-color:var(--accent)}
.modal-icon{width:34px;height:34px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}
.modal-icon.p{background:rgba(124,109,250,.15)}
.modal-icon.g{background:rgba(245,200,66,.12)}
.modal-icon.gr{background:rgba(74,222,128,.12)}
.modal-opt h3{font-size:13.5px;font-weight:500;margin-bottom:3px}
.modal-opt p{font-size:12px;color:var(--muted);margin:0}
.modal-close{margin-top:16px;width:100%;padding:9px;background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius);color:var(--muted);font-size:13px;cursor:pointer;font-family:var(--font-body);transition:color .15s}
.modal-close:hover{color:var(--text)}
/* Toast */
.toast{position:fixed;bottom:24px;right:24px;background:var(--surface);border:1px solid var(--success);border-radius:var(--radius);padding:11px 16px;font-size:13px;color:var(--success);z-index:200;transform:translateY(70px);opacity:0;transition:all .3s cubic-bezier(.4,0,.2,1)}
.toast.show{transform:translateY(0);opacity:1}
::-webkit-scrollbar{width:5px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--border);border-radius:99px}
</style>
</head>
<body>
<div class="app">
<!-- ── Sidebar ── -->
<aside class="sidebar">
<div class="sidebar-header">
<div class="sidebar-logo">Design<span>System</span> Guide</div>
</div>
<div class="project-section">
<div class="section-label">Projekte</div>
<div class="project-list" id="projectList"></div>
<button class="add-btn" onclick="showAddForm()">+ Neues Projekt</button>
<div class="new-proj-form" id="addForm">
<input type="text" id="addInput" placeholder="Projektname..." onkeydown="if(event.key==='Enter')doAdd();if(event.key==='Escape')hideAddForm()">
<div class="form-btns">
<button class="btn-xs btn-ok" onclick="doAdd()">Erstellen</button>
<button class="btn-xs btn-no" onclick="hideAddForm()">Abbrechen</button>
</div>
</div>
</div>
<nav class="nav-wrap" id="navWrap"></nav>
<div class="sidebar-footer">
<button class="export-btn" onclick="openModal()">Speichern &amp; Exportieren</button>
<div class="save-hint">Fortschritt automatisch gespeichert</div>
</div>
</aside>
<!-- ── Main ── -->
<main class="main">
<!-- Guide View -->
<div class="guide-view" id="guideView">
<div class="page-header">
<div class="proj-title-row">
<div class="proj-name" id="projNameDisplay" onclick="editName()">Mein Projekt</div>
</div>
<div class="page-sub">Design System Guide · Hake Aufgaben ab und erfasse deine Design-Werte</div>
</div>
<div class="progress-card">
<div class="prog-top">
<span class="prog-label">Gesamtfortschritt</span>
<span class="prog-pct" id="progPct">0%</span>
</div>
<div class="prog-track"><div class="prog-fill" id="progFill" style="width:0%"></div></div>
<div class="prog-pills" id="progPills"></div>
</div>
<div id="guideContent"></div>
</div>
<!-- Style Doc View -->
<div class="style-view" id="styleView">
<div class="action-row">
<button class="btn-outline" onclick="showGuide()">← Zurück zum Guide</button>
<button class="btn-primary-sm" onclick="downloadStyleDoc()">Style Dokument herunterladen</button>
</div>
<div class="style-doc" id="styleDocContent"></div>
</div>
</main>
</div>
<!-- Modal -->
<div class="modal-bd" id="modal">
<div class="modal">
<h2>Speichern &amp; Exportieren</h2>
<p>Wähle eine Option für dein Projekt:</p>
<div class="modal-opts">
<div class="modal-opt" onclick="showStyleDoc()">
<div class="modal-icon p"></div>
<div><h3>Style Dokument generieren</h3><p>Erstellt ein vollständiges Style Guide Dokument aus deinen eingegebenen Daten.</p></div>
</div>
<div class="modal-opt" onclick="downloadGuide()">
<div class="modal-icon g"></div>
<div><h3>Guide als HTML herunterladen</h3><p>Speichert den interaktiven Guide mit deinem Fortschritt als Datei.</p></div>
</div>
<div class="modal-opt" onclick="printPage()">
<div class="modal-icon gr"></div>
<div><h3>Als PDF drucken</h3><p>Druckversion des aktuellen Dokuments.</p></div>
</div>
</div>
<button class="modal-close" onclick="closeModal()">Schließen</button>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
// ─────────────────────────────────────────────────────────────────────────────
// DATA SCHEMA
// ─────────────────────────────────────────────────────────────────────────────
const GUIDE = [
{
id:'p1', phase:'01', title:'Foundations',
intro:'Die Foundations sind das Fundament eures Design Systems. Erfasse hier alle konkreten Werte Farben, Schriften, Abstände. Diese werden später automatisch in dein Style Dokument übernommen.',
dataFields: [
{
id:'df_colors', title:'Farbpalette — Grounded & Radical', type:'colors',
groups:[
{id:'light_brand', label:'Light Mode · Markenfarben', hint:'Anchor Brown als Primär, Deep Teal als CTA'},
{id:'light_accent', label:'Light Mode · Akzente', hint:'Radical Acid, Soft Coral, Soft Violet — die radikale Energie'},
{id:'light_surface', label:'Light Mode · Surfaces & Text', hint:'Paper Linen BG, Dark Earth Text, Border'},
{id:'light_semantic', label:'Light Mode · Semantisch (Website)', hint:'Info-Blau & Error-Rot von der Café Bach Website'},
{id:'dark_brand', label:'Dark Mode · Markenfarben', hint:'Warm Amber als Primär, Bridge Teal als CTA'},
{id:'dark_accent', label:'Dark Mode · Akzente', hint:'Acid & Coral unverändert, Lavender aufgehellt'},
{id:'dark_surface', label:'Dark Mode · Surfaces & Text', hint:'Darkest Earth BG, Paper Linen Text, Border'},
]
},
{
id:'df_typography', title:'Typografie festlegen', type:'typography',
fields:[
{id:'font_display', label:'Display Font · Titel, laut (Anton)', placeholder:'Anton'},
{id:'font_heading', label:'Heading Font · groß, emotional (Lobster Two)', placeholder:'Lobster Two'},
{id:'font_slab', label:'Subheading Font · H2H4 (Roboto Slab)', placeholder:'Roboto Slab'},
{id:'font_body', label:'Body Font · Tabellen, Fließtext (Roboto Condensed)', placeholder:'Roboto Condensed'},
{id:'font_mono', label:'Monospace Font · Code, Daten (Roboto Mono)', placeholder:'Roboto Mono'},
{id:'font_source', label:'Quelle', placeholder:'Google Fonts'},
{id:'type_scale', label:'Typografie-Skala', type:'typescale'},
{id:'base_size', label:'Basis-Schriftgröße', placeholder:'z.B. 16px'},
{id:'line_height', label:'Zeilen­abstände (Body)', placeholder:'z.B. 1.6'},
{id:'font_weights', label:'Schriftgewichte', placeholder:'z.B. 400, 500, 700'},
]
},
{
id:'df_spacing', title:'Abstands-System', type:'spacing',
fields:[
{id:'spacing_base', label:'Basis-Einheit', placeholder:'z.B. 4px oder 8px'},
{id:'spacing_scale', label:'Spacing Skala', type:'spacingscale'},
{id:'spacing_naming', label:'Benennungsschema', placeholder:'z.B. xs/sm/md/lg/xl oder 1/2/4/6/8'},
]
},
{
id:'df_shape', title:'Formen & Schatten', type:'fields',
fields:[
{id:'radius_sm', label:'Border Radius Small', placeholder:'z.B. 4px'},
{id:'radius_md', label:'Border Radius Medium', placeholder:'z.B. 8px'},
{id:'radius_lg', label:'Border Radius Large', placeholder:'z.B. 12px'},
{id:'radius_xl', label:'Border Radius XL', placeholder:'z.B. 16px'},
{id:'radius_full', label:'Border Radius Full', placeholder:'z.B. 9999px'},
{id:'border_default', label:'Standard Border', placeholder:'z.B. 1px solid #E2E8F0'},
{id:'shadow_sm', label:'Shadow Small', placeholder:'z.B. 0 1px 3px rgba(0,0,0,.1)'},
{id:'shadow_md', label:'Shadow Medium', placeholder:'z.B. 0 4px 12px rgba(0,0,0,.12)'},
{id:'shadow_lg', label:'Shadow Large', placeholder:'z.B. 0 8px 24px rgba(0,0,0,.15)'},
]
},
{
id:'df_icons', title:'Ikonografie', type:'fields',
fields:[
{id:'icon_set', label:'Icon-Set', placeholder:'z.B. Lucide Icons, Phosphor Icons'},
{id:'icon_sizes', label:'Icon-Größen', placeholder:'z.B. 16px, 20px, 24px'},
{id:'icon_style', label:'Icon-Stil', placeholder:'z.B. Outline, Filled'},
{id:'icon_source', label:'Bezugsquelle / Link', placeholder:'z.B. lucide.dev'},
]
},
{
id:'df_grid', title:'Raster & Breakpoints', type:'fields',
fields:[
{id:'grid_columns_desktop', label:'Spalten Desktop', placeholder:'z.B. 12'},
{id:'grid_gutter_desktop', label:'Gutter Desktop', placeholder:'z.B. 24px'},
{id:'grid_margin_desktop', label:'Margin Desktop', placeholder:'z.B. 32px'},
{id:'grid_columns_mobile', label:'Spalten Mobile', placeholder:'z.B. 4'},
{id:'grid_gutter_mobile', label:'Gutter Mobile', placeholder:'z.B. 16px'},
{id:'grid_margin_mobile', label:'Margin Mobile', placeholder:'z.B. 16px'},
{id:'bp_mobile', label:'Breakpoint Mobile', placeholder:'z.B. < 768px'},
{id:'bp_tablet', label:'Breakpoint Tablet', placeholder:'z.B. 768px 1024px'},
{id:'bp_desktop', label:'Breakpoint Desktop', placeholder:'z.B. > 1024px'},
{id:'bp_wide', label:'Breakpoint Wide', placeholder:'z.B. > 1440px'},
{id:'max_width', label:'Max. Content-Breite', placeholder:'z.B. 1280px'},
]
}
],
sections:[
{title:'Farben',items:[
{id:'c1',text:'Primärfarbe festlegen',hint:'Hauptfarbe der Marke für Buttons, Links, Highlights.'},
{id:'c2',text:'Sekundärfarbe festlegen',hint:'Ergänzende Akzentfarbe für Badges, Tags, Illustrationen.'},
{id:'c3',text:'Neutralpalette definieren (59 Stufen)',hint:'Von Weiß bis Schwarz für Hintergründe, Text, Borders.'},
{id:'c4',text:'Semantische Farben definieren',hint:'Success (grün), Warning (gelb), Error (rot), Info (blau).'},
{id:'c5',text:'Dark-Mode-Varianten prüfen',hint:'Jede Farbe auf dunklem Hintergrund auf Kontrast testen.'},
{id:'c6',text:'Kontrastverhältnis prüfen (WCAG AA: 4.5:1)',hint:'Tool: contrast-ratio.com oder Figma Plugin "Contrast".'},
]},
{title:'Typografie',items:[
{id:'t1',text:'Schriftart(en) auswählen (max. 2)',hint:'Eine für Headlines, eine für Body Text.'},
{id:'t2',text:'Type Scale definieren (68 Stufen)',hint:'z.B. 12/14/16/18/24/32/48/64px'},
{id:'t3',text:'Schriftgewichte festlegen',hint:'Regular (400), Medium (500), Semibold (600), Bold (700).'},
{id:'t4',text:'Zeilenhöhe (line-height) pro Stufe',hint:'Headlines: 1.11.3 · Body: 1.51.7'},
{id:'t5',text:'Letter-spacing für Headlines',hint:'Große Texte wirken mit leicht negativem Letter-spacing besser.'},
{id:'t6',text:'Maximale Zeilenlänge festlegen',hint:'Empfehlung: 6075 Zeichen pro Zeile.'},
]},
{title:'Abstände (Spacing)',items:[
{id:'s1',text:'Basis-Einheit festlegen (4px oder 8px)',hint:'Alle Abstände sind Vielfache dieser Einheit.'},
{id:'s2',text:'Spacing-Skala erstellen',hint:'z.B. 4/8/12/16/24/32/48/64/96px'},
{id:'s3',text:'Benennungsschema wählen',hint:'xs/sm/md/lg/xl oder numerisch.'},
{id:'s4',text:'Component- vs. Layout-Spacing trennen',hint:'Interne Abstände vs. Abstände zwischen Elementen.'},
]},
{title:'Ecken & Schatten',items:[
{id:'e1',text:'Border-Radius-Stufen definieren',hint:'none / sm / md / lg / xl / full'},
{id:'e2',text:'Konsistenten Stil wählen',hint:'Sharp = modern · Rounded = freundlich · Pill = playful'},
{id:'e3',text:'Shadow/Elevation-Stufen definieren',hint:'35 Stufen: Karten, Dropdowns, Modals, Tooltips.'},
{id:'e4',text:'Border-Farben und -Stärken festlegen',hint:'1px Default, 2px Fokus-Zustände.'},
]},
{title:'Ikonografie',items:[
{id:'i1',text:'Icon-Set auswählen',hint:'Lucide Icons, Phosphor Icons, Heroicons, Material Symbols.'},
{id:'i2',text:'Icon-Größen festlegen',hint:'16px (inline), 20px (default), 24px (large).'},
{id:'i3',text:'Icon-Stil konsistent halten',hint:'Outline ODER Filled nie mischen.'},
{id:'i4',text:'Custom Icons dokumentieren',hint:'Namenskonvention und Exportformat festlegen.'},
]},
{title:'Raster & Layout',items:[
{id:'g1',text:'Spalten-System definieren (Web)',hint:'12-Spalten-Grid ist Standard. Gutter: 1624px.'},
{id:'g2',text:'Breakpoints festlegen',hint:'Mobile / Tablet / Desktop / Wide'},
{id:'g3',text:'Mobile Grid definieren',hint:'4-Spalten-Grid. Gutter: 1216px · Margin: 16px.'},
{id:'g4',text:'Max-Width für Content-Bereiche',hint:'z.B. 768px Text, 1200px Dashboards.'},
]},
]
},
{
id:'p2', phase:'02', title:'Design Tokens',
intro:'Tokens übersetzen eure Foundations in eine plattformübergreifende Sprache. Erfasse hier eure Token-Struktur und Namenskonventionen.',
dataFields:[
{
id:'df_tokens', title:'Token-Konfiguration', type:'fields',
fields:[
{id:'token_format', label:'Token-Format', placeholder:'z.B. W3C Design Token Format, JSON'},
{id:'token_naming', label:'Namenskonvention', placeholder:'z.B. color.brand.primary, --color-brand-primary'},
{id:'token_tool', label:'Build-Tool', placeholder:'z.B. Style Dictionary, Theo, Specify'},
{id:'token_output_web', label:'Web-Output', placeholder:'z.B. CSS Custom Properties (:root {})'},
{id:'token_output_ios', label:'iOS-Output', placeholder:'z.B. Swift Enums, UIColor extensions'},
{id:'token_output_android', label:'Android-Output', placeholder:'z.B. colors.xml, dimens.xml'},
{id:'token_figma_sync', label:'Figma-Sync', placeholder:'z.B. Tokens Studio for Figma'},
{id:'token_repo', label:'Repository / Speicherort', placeholder:'z.B. github.com/org/design-tokens'},
]
}
],
sections:[
{title:'Global Tokens (Ebene 1)',items:[
{id:'gt1',text:'Dateistruktur für Tokens festlegen',hint:'global.json / semantic.json / component.json'},
{id:'gt2',text:'Alle Farben als Global Tokens erfassen',hint:'color.blue.100 bis color.blue.900'},
{id:'gt3',text:'Spacing-Werte als Tokens erfassen',hint:'space.1=4px · space.2=8px · space.4=16px'},
{id:'gt4',text:'Typografie-Werte als Tokens erfassen',hint:'font.size.sm / font.weight.bold usw.'},
{id:'gt5',text:'Radius- und Shadow-Tokens erstellen',hint:'radius.sm=4px · shadow.md=0 4px 12px rgba(...)'},
]},
{title:'Semantic Tokens (Ebene 2)',items:[
{id:'st1',text:'Aktions-Tokens definieren',hint:'color.action.primary → {color.blue.500}'},
{id:'st2',text:'Text-Tokens definieren',hint:'color.text.primary / .secondary / .disabled'},
{id:'st3',text:'Hintergrund-Tokens definieren',hint:'color.background.default / .surface / .elevated'},
{id:'st4',text:'Border-Tokens definieren',hint:'color.border.default / .strong / .focus'},
{id:'st5',text:'Feedback-Tokens definieren',hint:'color.feedback.success / .warning / .error / .info'},
{id:'st6',text:'Dark-Mode-Tokens parallel anlegen',hint:'Gleiche Token-Namen, andere Referenz.'},
]},
{title:'Token-Format & Tooling',items:[
{id:'tf1',text:'Format wählen: W3C Design Token Format oder JSON',hint:'W3C-Format ist der neue Standard.'},
{id:'tf2',text:'Style Dictionary einrichten',hint:'npm install -g style-dictionary'},
{id:'tf3',text:'CSS Custom Properties generieren',hint:'--color-action-primary: #5C6BC0;'},
{id:'tf4',text:'iOS-Konstanten generieren (falls relevant)',hint:'Style Dictionary erzeugt Swift-Enums.'},
{id:'tf5',text:'Android-Ressourcen generieren (falls relevant)',hint:'colors.xml und dimens.xml.'},
{id:'tf6',text:'Figma-Token-Sync einrichten (optional)',hint:'Tokens Studio for Figma.'},
]},
]
},
{
id:'p3', phase:'03', title:'Components',
intro:'Definiere eure Komponenten-Bibliothek. Halte fest, welche Komponenten geplant sind, wo sie leben und wie der Review-Prozess aussieht.',
dataFields:[
{
id:'df_comp', title:'Komponenten-Infrastruktur', type:'fields',
fields:[
{id:'comp_library', label:'Component Library', placeholder:'z.B. React, Vue, Web Components'},
{id:'comp_storybook', label:'Storybook / Doku-Tool', placeholder:'z.B. Storybook 7, Histoire'},
{id:'comp_repo', label:'Repository', placeholder:'z.B. github.com/org/ui-library'},
{id:'comp_npm', label:'NPM-Paket', placeholder:'z.B. @org/ui-components'},
{id:'comp_versioning', label:'Versionierung', placeholder:'z.B. Semantic Versioning (semver)'},
{id:'comp_testing', label:'Testing', placeholder:'z.B. Vitest, Testing Library, Chromatic'},
{id:'comp_figma', label:'Figma-Bibliothek', placeholder:'z.B. Link zur Figma-Datei'},
]
}
],
sections:[
{title:'Basis-Komponenten',items:[
{id:'co1',text:'Button alle Varianten',hint:'Primary/Secondary/Ghost/Destructive · Sizes: sm/md/lg · States: default, hover, active, disabled, loading.'},
{id:'co2',text:'Input-Felder',hint:'Text/Password/Number · States: default, focus, error, disabled.'},
{id:'co3',text:'Checkbox & Radio',hint:'States: unchecked, checked, indeterminate, disabled.'},
{id:'co4',text:'Toggle / Switch',hint:'On/Off · mit optionalem Label · disabled state.'},
{id:'co5',text:'Select / Dropdown',hint:'Native select UND custom Dropdown.'},
{id:'co6',text:'Badge & Tag',hint:'Filled, outlined · mit Icon-Option.'},
]},
{title:'Layout-Komponenten',items:[
{id:'lc1',text:'Card / Panel',hint:'Default, klickbar, mit Header/Footer. Elevation-Varianten.'},
{id:'lc2',text:'Modal / Dialog',hint:'Mit Overlay, Fokus-Trap, ESC-Schließen.'},
{id:'lc3',text:'Drawer / Sheet',hint:'Schiebemenü von links/rechts/unten. Wichtig für Mobile.'},
{id:'lc4',text:'Divider / Separator',hint:'Horizontal und vertikal.'},
]},
{title:'Feedback-Komponenten',items:[
{id:'fc1',text:'Alert / Banner',hint:'Success/Warning/Error/Info. Mit Icon, Titel, Dismiss-Button.'},
{id:'fc2',text:'Toast / Snackbar',hint:'Auto-dismiss nach 4s. Position: bottom-right.'},
{id:'fc3',text:'Loading States',hint:'Spinner, Skeleton Loader, Progress Bar.'},
{id:'fc4',text:'Empty States',hint:'Icon + Titel + Beschreibung + CTA.'},
]},
{title:'Navigation',items:[
{id:'nc1',text:'Navigation Bar (Top / Bottom)',hint:'Mit aktiven Zuständen, Icons.'},
{id:'nc2',text:'Tabs',hint:'Horizontal und vertikal. Mit Badge-Option.'},
{id:'nc3',text:'Breadcrumb',hint:'Mit Trennzeichen-Optionen.'},
{id:'nc4',text:'Pagination',hint:'Mobile: vereinfachte Version.'},
]},
{title:'Dokumentation',items:[
{id:'cd1',text:'Do/Don\'t Beispiele pro Komponente',hint:'Mindestens 2 Do und 2 Don\'t.'},
{id:'cd2',text:'Props / API dokumentieren',hint:'Alle Props mit Typ, Default-Wert, Beschreibung.'},
{id:'cd3',text:'Accessibility-Anforderungen notieren',hint:'ARIA-Labels, Keyboard-Navigation, Fokus-Management.'},
{id:'cd4',text:'Code-Snippets beifügen',hint:'Minimalbeispiel direkt in der Doku.'},
]},
]
},
{
id:'p4', phase:'04', title:'Patterns & Guidelines',
intro:'Patterns kombinieren Komponenten zu bewährten Lösungen. Halte fest, welche Regeln für euer Team gelten.',
dataFields:[
{
id:'df_motion', title:'Motion & Animation', type:'fields',
fields:[
{id:'motion_fast', label:'Fast Duration', placeholder:'z.B. 100ms'},
{id:'motion_default', label:'Default Duration', placeholder:'z.B. 200ms'},
{id:'motion_slow', label:'Slow Duration (Modals)', placeholder:'z.B. 400ms'},
{id:'motion_easing', label:'Standard Easing', placeholder:'z.B. cubic-bezier(0.4, 0, 0.2, 1)'},
{id:'motion_easing_in', label:'Ease In', placeholder:'z.B. cubic-bezier(0.4, 0, 1, 1)'},
{id:'motion_easing_out', label:'Ease Out', placeholder:'z.B. cubic-bezier(0, 0, 0.2, 1)'},
]
}
],
sections:[
{title:'Formular-Patterns',items:[
{id:'fp1',text:'Formular-Layout-Regeln definieren',hint:'Einspaltig oder mehrspaltig? Labels über oder neben dem Feld?'},
{id:'fp2',text:'Validierung und Fehlermeldungen standardisieren',hint:'Wann validiert? On blur / on change / on submit.'},
{id:'fp3',text:'Pflichtfelder-Kennzeichnung',hint:'Asterisk (*) oder "(Optional)" Label?'},
{id:'fp4',text:'Formular-Erfolgs-Zustände',hint:'Was passiert nach Absenden?'},
]},
{title:'Accessibility (A11y)',items:[
{id:'a1',text:'Farbkontrast-Audit durchführen',hint:'Alle Text-/Hintergrund-Kombinationen auf WCAG AA prüfen.'},
{id:'a2',text:'Keyboard-Navigation testen',hint:'Jede interaktive Komponente per Tab erreichbar.'},
{id:'a3',text:'Fokus-Indikator sichtbar und schön',hint:'Fokus-Ring nie entfernen custom stylen. 3px Offset.'},
{id:'a4',text:'ARIA-Rollen und -Labels definieren',hint:'Für Icons, Buttons ohne Text, Statusanzeigen, Modale.'},
{id:'a5',text:'Screen-Reader-Test durchführen',hint:'VoiceOver (Mac/iOS) oder NVDA (Windows).'},
]},
{title:'Motion & Animation',items:[
{id:'ma1',text:'Dauer-Werte festlegen',hint:'Fast: 100ms · Standard: 200300ms · Slow: 400ms.'},
{id:'ma2',text:'Easing-Kurven definieren',hint:'ease-in-out Standard · ease-out Einblenden · ease-in Ausblenden.'},
{id:'ma3',text:'Reduced Motion beachten',hint:'@media (prefers-reduced-motion: reduce)'},
{id:'ma4',text:'Animationen sparsam einsetzen',hint:'Nur dort, wo sie Bedeutung transportieren.'},
]},
]
},
{
id:'p5', phase:'05', title:'Dokumentation & Übergabe',
intro:'Ein Design System ohne Dokumentation ist ein Geheimnis. Halte fest, wo alles lebt und wie euer Team es nutzt.',
dataFields:[
{
id:'df_docs', title:'Dokumentations-Setup', type:'fields',
fields:[
{id:'docs_tool', label:'Dokumentations-Tool', placeholder:'z.B. Storybook, Zeroheight, Notion, eigene Website'},
{id:'docs_url', label:'URL der Dokumentation', placeholder:'z.B. design.company.com'},
{id:'docs_owner', label:'Design System Owner', placeholder:'Name oder Team'},
{id:'docs_maintainers', label:'Maintainer', placeholder:'z.B. Design Team + Frontend Team'},
{id:'docs_channel', label:'Kommunikationskanal', placeholder:'z.B. #design-system auf Slack'},
{id:'docs_release', label:'Release-Zyklus', placeholder:'z.B. alle 2 Wochen, nach Bedarf'},
{id:'docs_versioning', label:'Versionierungsstrategie', placeholder:'z.B. semver (major.minor.patch)'},
{id:'docs_adoption', label:'Adoption messen mit', placeholder:'z.B. GitHub Stars, npm downloads, interne Umfragen'},
]
}
],
sections:[
{title:'Dokumentations-Inhalte',items:[
{id:'do1',text:'Getting Started Seite erstellen',hint:'Für Entwickler UND Designer getrennt.'},
{id:'do2',text:'Changelog pflegen',hint:'Jede Änderung: was, warum, Migration.'},
{id:'do3',text:'Design Principles definieren',hint:'35 Prinzipien, die Entscheidungen leiten.'},
{id:'do4',text:'Beitragsregeln (Contribution Guide)',hint:'Wie kann das Team neue Komponenten vorschlagen?'},
{id:'do5',text:'Glossar erstellen',hint:'Gemeinsame Sprache für alle Begriffe.'},
]},
{title:'Technische Übergabe',items:[
{id:'tu1',text:'Figma-Bibliothek veröffentlichen',hint:'Als Shared Library für alle Figma-Projekte.'},
{id:'tu2',text:'NPM-Paket oder Repo einrichten',hint:'Semantic Versioning (semver) nutzen.'},
{id:'tu3',text:'Storybook oder ähnliches einrichten',hint:'Komponentenübersicht für Entwickler.'},
{id:'tu4',text:'CI/CD für das Design System',hint:'Automatische Tests und Veröffentlichung.'},
]},
{title:'Team & Prozess',items:[
{id:'pr1',text:'Owner und Maintainer benennen',hint:'Wer ist verantwortlich?'},
{id:'pr2',text:'Review-Prozess für neue Komponenten',hint:'Brauchen wir das? Ist es generisch? Ist es dokumentiert?'},
{id:'pr3',text:'Kommunikationskanal einrichten',hint:'Slack-Channel oder wöchentliches Meeting.'},
{id:'pr4',text:'Release-Zyklus definieren',hint:'Wie oft? Wer wird informiert?'},
{id:'pr5',text:'Adoption messen',hint:'Welche Komponenten werden nicht genutzt und warum?'},
]},
]
}
];
// Default color entries
const DEFAULT_COLOR_GROUPS = {
light_brand:[
{color:'#7d4e2b',name:'Anchor Brown · Headlines, Borders'},
{color:'#1a2e2d',name:'Deep Teal · Header, CTA Primary'},
{color:'#3d7a75',name:'Bridge Teal · Links, Zu besetzen'},
],
light_accent:[
{color:'#e8f060',name:'Radical Acid · Fr/Sa/So, Highlight'},
{color:'#ffc8de',name:'Soft Coral · Kein Bedarf'},
{color:'#c8b8e8',name:'Soft Violet · Selbsthilfegruppen'},
],
light_surface:[
{color:'#f2ece4',name:'Paper Linen · Background'},
{color:'#cbbaa2',name:'Website Warm BG · Alt-Surface'},
{color:'#d0c4b4',name:'Border · Divider, Input Border'},
{color:'#3e2716',name:'Dark Earth · Body Text'},
],
light_semantic:[
{color:'#409eff',name:'Info Blue · Links, Info-Buttons (Website)'},
{color:'#e62143',name:'Error Red · Warnungen, Fehler (Website)'},
],
dark_brand:[
{color:'#c4843c',name:'Warm Amber · Headlines (Brown aufgehellt)'},
{color:'#3d7a75',name:'Bridge Teal · CTA Primary'},
{color:'#1a2e2d',name:'Deep Teal · Surface Accent'},
],
dark_accent:[
{color:'#e8f060',name:'Radical Acid · Fr/Sa/So (unverändert)'},
{color:'#ffc8de',name:'Soft Coral · Kein Bedarf (unverändert)'},
{color:'#c8a8e0',name:'Bright Lavender · Selbsthilfe (aufgehellt)'},
],
dark_surface:[
{color:'#1c1108',name:'Darkest Earth · Background'},
{color:'#2e1e0e',name:'Dark Earth 200 · Surface, Card'},
{color:'#4a3020',name:'Border · Divider, Input Border'},
{color:'#f2ece4',name:'Paper Linen · Body Text'},
],
};
const DEFAULT_TYPE_SCALE = ['12','14','16','18','24','32','48','64'];
const DEFAULT_SPACING = ['4','8','12','16','24','32','48','64','96'];
// ─────────────────────────────────────────────────────────────────────────────
// STATE
// ─────────────────────────────────────────────────────────────────────────────
let state = {projects:[], activeProject:null};
function loadState(){
try{ const s=localStorage.getItem('dsg_v2'); if(s) state=JSON.parse(s); }catch(e){}
if(!state.projects) state.projects=[];
if(!state.activeProject && state.projects.length>0) state.activeProject=state.projects[0].id;
if(state.projects.length===0) createProject('Mein erstes Projekt');
}
function saveState(){ localStorage.setItem('dsg_v2',JSON.stringify(state)); }
function getProj(){ return state.projects.find(p=>p.id===state.activeProject); }
function createProject(name){
const id='p'+Date.now();
const checks={}, data={};
// init color groups
data['light_brand']=JSON.parse(JSON.stringify(DEFAULT_COLOR_GROUPS.light_brand));
data['light_accent']=JSON.parse(JSON.stringify(DEFAULT_COLOR_GROUPS.light_accent));
data['light_surface']=JSON.parse(JSON.stringify(DEFAULT_COLOR_GROUPS.light_surface));
data['dark_brand']=JSON.parse(JSON.stringify(DEFAULT_COLOR_GROUPS.dark_brand));
data['dark_accent']=JSON.parse(JSON.stringify(DEFAULT_COLOR_GROUPS.dark_accent));
data['dark_surface']=JSON.parse(JSON.stringify(DEFAULT_COLOR_GROUPS.dark_surface));
data['type_scale']=JSON.parse(JSON.stringify(DEFAULT_TYPE_SCALE));
data['light_semantic']=JSON.parse(JSON.stringify(DEFAULT_COLOR_GROUPS.light_semantic));
// Vorausgefüllte Schriftarten (Café Bach)
data['font_display']='Anton';
data['font_heading']='Lobster Two';
data['font_slab']='Roboto Slab';
data['font_body']='Roboto Condensed';
data['font_mono']='Roboto Mono';
data['font_source']='Google Fonts';
data['base_size']='16px';
data['line_height']='1.6';
data['font_weights']='400, 600, 700, 900';
data['spacing_scale']=JSON.parse(JSON.stringify(DEFAULT_SPACING));
state.projects.push({id,name,checks,data});
state.activeProject=id;
saveState();
}
function isChecked(id){ const p=getProj(); return p&&!!p.checks[id]; }
function toggleCheck(id){ const p=getProj(); if(!p)return; p.checks[id]=!p.checks[id]; saveState(); renderAll(); }
function getData(id){ const p=getProj(); return p&&p.data&&p.data[id]!==undefined?p.data[id]:''; }
function setData(id,val){ const p=getProj(); if(!p)return; if(!p.data)p.data={}; p.data[id]=val; saveState(); }
function countPhase(ph){
let total=0,done=0;
ph.sections.forEach(s=>s.items.forEach(i=>{total++;if(isChecked(i.id))done++;}));
return {total,done};
}
// ─────────────────────────────────────────────────────────────────────────────
// RENDER
// ─────────────────────────────────────────────────────────────────────────────
function renderAll(){
renderProjList(); renderNav(); renderProgress(); renderProjName(); renderGuide();
}
function renderProjList(){
const el=document.getElementById('projectList'); el.innerHTML='';
state.projects.forEach(proj=>{
let t=0,d=0;
GUIDE.forEach(ph=>ph.sections.forEach(s=>s.items.forEach(i=>{t++;if(proj.checks&&proj.checks[i.id])d++;})));
const pct=t?Math.round(d/t*100):0;
const div=document.createElement('div');
div.className='project-item'+(proj.id===state.activeProject?' active':'');
div.innerHTML=`<div class="project-dot"></div><span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${proj.name}</span><span class="project-pct">${pct}%</span>`;
div.onclick=()=>{state.activeProject=proj.id;saveState();renderAll();};
el.appendChild(div);
});
}
function renderNav(){
const nav=document.getElementById('navWrap'); nav.innerHTML='';
GUIDE.forEach(ph=>{
const {total,done}=countPhase(ph);
const a=document.createElement('a');
a.className='nav-item'+(done===total&&total>0?' all-done':'');
a.href='#phase-'+ph.id;
a.innerHTML=`<span style="color:var(--accent);font-family:var(--font-mono);font-size:10.5px">${ph.phase}</span>${ph.title}<span class="nav-badge">${done}/${total}</span>`;
nav.appendChild(a);
});
const sep=document.createElement('div'); sep.className='nav-sep'; nav.appendChild(sep);
const sdBtn=document.createElement('a');
sdBtn.className='nav-item nav-special'; sdBtn.href='#';
sdBtn.innerHTML='✦ Style Dokument ansehen';
sdBtn.onclick=(e)=>{e.preventDefault();showStyleDoc();};
nav.appendChild(sdBtn);
}
function renderProgress(){
let t=0,d=0;
GUIDE.forEach(ph=>{const r=countPhase(ph);t+=r.total;d+=r.done;});
const pct=t?Math.round(d/t*100):0;
document.getElementById('progPct').textContent=pct+'%';
document.getElementById('progFill').style.width=pct+'%';
const pills=document.getElementById('progPills'); pills.innerHTML='';
GUIDE.forEach(ph=>{
const {total,done}=countPhase(ph);
const cls=done===total&&total>0?'done':done>0?'partial':'';
const p=document.createElement('div');
p.className='prog-pill '+cls;
p.innerHTML=`<span class="pill-dot"></span>${ph.phase} ${ph.title} (${done}/${total})`;
pills.appendChild(p);
});
}
function renderProjName(){
const p=getProj(); const el=document.getElementById('projNameDisplay');
if(el&&p) el.textContent=p.name;
}
function renderGuide(){
const el=document.getElementById('guideContent'); el.innerHTML='';
GUIDE.forEach(ph=>{
const {total,done}=countPhase(ph);
const sec=document.createElement('div');
sec.className='phase-section'; sec.id='phase-'+ph.id;
let html=`
<div class="phase-header">
<span class="phase-num">Phase ${ph.phase}</span>
<h2 class="phase-title">${ph.title}</h2>
<span class="phase-stat${done===total&&total>0?' done':''}">${done}/${total}</span>
</div>
<div class="phase-intro">${ph.intro}</div>
`;
// Data fields
if(ph.dataFields){
ph.dataFields.forEach(df=>{
html+=`<div class="data-section"><div class="data-section-title">
<svg viewBox="0 0 14 14" fill="none"><rect x="1" y="1" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.2"/><rect x="8" y="1" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.2"/><rect x="1" y="8" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.2"/><rect x="8" y="8" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.2"/></svg>
${df.title}
</div>`;
if(df.type==='colors'){
df.groups.forEach(grp=>{
html+=`<div style="margin-bottom:16px"><div class="field-label" style="margin-bottom:8px">${grp.label} <span style="color:var(--muted2);font-size:10.5px">${grp.hint}</span></div>`;
html+=`<div class="color-row" id="colorrow_${grp.id}">`;
const colors=getData(grp.id)||DEFAULT_COLOR_GROUPS[grp.id]||[];
colors.forEach((c,i)=>{
html+=colorEntryHTML(grp.id,i,c.color,c.name);
});
html+=`</div>`;
html+=`<button class="add-color-btn" onclick="addColor('${grp.id}')">+ Farbe hinzufügen</button></div>`;
});
} else if(df.type==='typography'){
html+=`<div class="data-grid">`;
df.fields.forEach(f=>{
if(f.type==='typescale'){
html+=`</div><div style="margin:14px 0 4px"><div class="field-label" style="margin-bottom:6px">Typografie-Skala (px-Werte, kommagetrennt)</div>
<input class="field-input" id="field_type_scale" value="${(getData('type_scale')||DEFAULT_TYPE_SCALE).join(', ')}"
oninput="setData('type_scale',this.value.split(',').map(v=>v.trim()).filter(Boolean));renderTypePreview()"
placeholder="z.B. 12, 14, 16, 18, 24, 32, 48, 64">
</div><div id="typePreview"></div><div class="data-grid">`;
} else {
html+=`<div class="field-group"><div class="field-label">${f.label}</div>
<input class="field-input" id="field_${f.id}" value="${escHtml(getData(f.id))}"
oninput="setData('${f.id}',this.value)"
placeholder="${f.placeholder||''}"></div>`;
}
});
html+=`</div>`;
} else if(df.type==='spacing'){
html+=`<div class="data-grid">`;
df.fields.forEach(f=>{
if(f.type==='spacingscale'){
html+=`</div><div style="margin:14px 0 4px"><div class="field-label" style="margin-bottom:6px">Spacing Skala (px-Werte, kommagetrennt)</div>
<input class="field-input" id="field_spacing_scale" value="${(getData('spacing_scale')||DEFAULT_SPACING).join(', ')}"
oninput="setData('spacing_scale',this.value.split(',').map(v=>v.trim()).filter(Boolean));renderSpacingPreview()"
placeholder="z.B. 4, 8, 12, 16, 24, 32, 48, 64">
</div><div id="spacingPreview"></div><div class="data-grid">`;
} else {
html+=`<div class="field-group"><div class="field-label">${f.label}</div>
<input class="field-input" id="field_${f.id}" value="${escHtml(getData(f.id))}"
oninput="setData('${f.id}',this.value)"
placeholder="${f.placeholder||''}"></div>`;
}
});
html+=`</div>`;
} else {
// generic fields
const cols=df.fields.length>6?2:df.fields.length===1?1:2;
html+=`<div class="data-grid cols${cols}">`;
df.fields.forEach(f=>{
html+=`<div class="field-group"><div class="field-label">${f.label}</div>
<input class="field-input" id="field_${f.id}" value="${escHtml(getData(f.id))}"
oninput="setData('${f.id}',this.value)"
placeholder="${f.placeholder||''}"></div>`;
});
html+=`</div>`;
}
html+=`</div>`;
});
}
// Checklist sections
ph.sections.forEach(s=>{
html+=`<div class="subsection"><div class="sub-title">${s.title}</div><div class="checklist">`;
s.items.forEach(item=>{
const chk=isChecked(item.id);
html+=`<div class="check-item${chk?' checked':''}" onclick="toggleCheck('${item.id}')">
<div class="check-box"><svg class="check-tick" viewBox="0 0 10 10" fill="none"><path d="M1.5 5L4 7.5L8.5 2.5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
<div class="check-content"><div class="check-text">${item.text}</div>${item.hint?`<div class="check-hint">${item.hint}</div>`:''}</div>
</div>`;
});
html+=`</div></div>`;
});
sec.innerHTML=html; el.appendChild(sec);
});
// render previews after DOM settles
setTimeout(()=>{renderTypePreview();renderSpacingPreview();},50);
}
// ─────────────────────────────────────────────────────────────────────────────
// COLOR ENTRIES
// ─────────────────────────────────────────────────────────────────────────────
function colorEntryHTML(groupId,idx,color,name){
return `<div class="color-entry" id="ce_${groupId}_${idx}">
<div class="color-swatch-wrap">
<input type="color" value="${color}" oninput="updateColor('${groupId}',${idx},'color',this.value);document.getElementById('chex_${groupId}_${idx}').value=this.value">
</div>
<input class="color-hex" id="chex_${groupId}_${idx}" value="${color}" maxlength="7"
oninput="if(this.value.match(/^#[0-9a-fA-F]{6}$/))updateColor('${groupId}',${idx},'color',this.value)"
placeholder="#000000">
<input class="color-name-in" value="${escHtml(name)}"
oninput="updateColor('${groupId}',${idx},'name',this.value)"
placeholder="Name (z.B. Primary 500)">
<button class="remove-color" onclick="removeColor('${groupId}',${idx})" title="Entfernen">×</button>
</div>`;
}
function addColor(groupId){
const p=getProj(); if(!p)return;
if(!p.data[groupId]) p.data[groupId]=[];
p.data[groupId].push({color:'#6366f1',name:''});
saveState();
const row=document.getElementById('colorrow_'+groupId);
if(row){
const idx=p.data[groupId].length-1;
const tmp=document.createElement('div');
tmp.innerHTML=colorEntryHTML(groupId,idx,'#6366f1','');
row.appendChild(tmp.firstElementChild);
}
}
function updateColor(groupId,idx,key,val){
const p=getProj(); if(!p||!p.data[groupId])return;
if(!p.data[groupId][idx]) p.data[groupId][idx]={};
p.data[groupId][idx][key]=val; saveState();
}
function removeColor(groupId,idx){
const p=getProj(); if(!p||!p.data[groupId])return;
p.data[groupId].splice(idx,1); saveState();
const row=document.getElementById('colorrow_'+groupId);
if(row){row.innerHTML=''; p.data[groupId].forEach((c,i)=>{
const tmp=document.createElement('div');
tmp.innerHTML=colorEntryHTML(groupId,i,c.color,c.name);
row.appendChild(tmp.firstElementChild);
});}
}
// ─────────────────────────────────────────────────────────────────────────────
// PREVIEWS
// ─────────────────────────────────────────────────────────────────────────────
function renderTypePreview(){
const el=document.getElementById('typePreview'); if(!el)return;
const scale=getData('type_scale')||DEFAULT_TYPE_SCALE;
const font=getData('font_display')||'inherit';
let html=`<div class="type-preview">`;
[...scale].reverse().slice(0,6).forEach(sz=>{
const s=parseInt(sz)||16;
const capped=Math.min(s,36);
html+=`<div class="type-row"><span class="type-size-label">${sz}px</span><span class="type-sample" style="font-size:${capped}px;font-family:'${font}',var(--font-display)">Aa</span></div>`;
});
html+=`</div>`;
el.innerHTML=html;
}
function renderSpacingPreview(){
const el=document.getElementById('spacingPreview'); if(!el)return;
const scale=getData('spacing_scale')||DEFAULT_SPACING;
let html=`<div class="spacing-preview">`;
scale.slice(0,9).forEach(v=>{
const px=parseInt(v)||8;
const h=Math.min(px,64);
html+=`<div class="sp-item"><div class="sp-bar" style="height:${h}px"></div><span class="sp-label">${v}px</span></div>`;
});
html+=`</div>`;
el.innerHTML=html;
}
// ─────────────────────────────────────────────────────────────────────────────
// PROJECT NAME
// ─────────────────────────────────────────────────────────────────────────────
function editName(){
const display=document.getElementById('projNameDisplay');
const p=getProj(); if(!p)return;
const inp=document.createElement('input');
inp.className='proj-name-input'; inp.value=p.name;
display.replaceWith(inp); inp.focus(); inp.select();
function done(){
p.name=inp.value.trim()||p.name; saveState();
const nd=document.createElement('div');
nd.id='projNameDisplay'; nd.className='proj-name'; nd.textContent=p.name; nd.onclick=editName;
inp.replaceWith(nd); renderProjList();
}
inp.onblur=done; inp.onkeydown=e=>{if(e.key==='Enter')inp.blur();if(e.key==='Escape'){inp.value=p.name;inp.blur();}};
}
// ─────────────────────────────────────────────────────────────────────────────
// NEW PROJECT
// ─────────────────────────────────────────────────────────────────────────────
function showAddForm(){ document.getElementById('addForm').classList.add('show'); document.getElementById('addInput').focus(); }
function hideAddForm(){ document.getElementById('addForm').classList.remove('show'); document.getElementById('addInput').value=''; }
function doAdd(){
const name=document.getElementById('addInput').value.trim(); if(!name)return;
createProject(name); hideAddForm(); renderAll();
}
// ─────────────────────────────────────────────────────────────────────────────
// STYLE DOC GENERATOR
// ─────────────────────────────────────────────────────────────────────────────
function showStyleDoc(){
closeModal();
document.getElementById('guideView').classList.add('hidden');
document.getElementById('styleView').classList.add('active');
renderStyleDoc();
}
function showGuide(){
document.getElementById('styleView').classList.remove('active');
document.getElementById('guideView').classList.remove('hidden');
}
function renderStyleDoc(){
const p=getProj(); if(!p) return;
const d=p.data||{};
const el=document.getElementById('styleDocContent');
// helper
const fld=id=>d[id]||'';
const colors=grp=>(d[grp]||DEFAULT_COLOR_GROUPS[grp]||[]);
const scale=d['type_scale']||DEFAULT_TYPE_SCALE;
const spacing=d['spacing_scale']||DEFAULT_SPACING;
// count checks
let totalChecks=0,doneChecks=0;
GUIDE.forEach(ph=>ph.sections.forEach(s=>s.items.forEach(i=>{totalChecks++;if(p.checks&&p.checks[i.id])doneChecks++;})));
const pct=totalChecks?Math.round(doneChecks/totalChecks*100):0;
const now=new Date().toLocaleDateString('de-DE',{year:'numeric',month:'long',day:'numeric'});
let html=`
<div class="style-hero">
<div class="style-hero-name">${escHtml(p.name)}<br><span style="color:var(--muted);font-size:0.55em;font-family:var(--font-display);font-style:italic">Style Guide</span></div>
<div class="style-hero-meta">
<span>✦ Erstellt ${now}</span>
<span>◈ Fortschritt: ${pct}% (${doneChecks}/${totalChecks} Aufgaben)</span>
</div>
</div>`;
// ── 1. FARBEN ──
html+=`<div class="style-section"><div class="style-section-title">01 · Farben</div>`;
// helper: is a color light enough to need dark text?
function isLight(hex){
const r=parseInt(hex.slice(1,3),16),g=parseInt(hex.slice(3,5),16),b=parseInt(hex.slice(5,7),16);
return (r*299+g*587+b*114)/1000>128;
}
// Light mode block
html+=`<div class="style-sub">Light Mode</div>
<div style="background:#f2ece4;border-radius:12px;padding:20px 22px;margin-bottom:16px;border:1px solid #d0c4b4">`;
['light_brand','light_accent','light_surface'].forEach(grpId=>{
const grpLabel={light_brand:'Markenfarben',light_accent:'Akzente',light_surface:'Surfaces & Text'}[grpId];
const cols=colors(grpId);
if(!cols.length)return;
html+=`<div style="margin-bottom:16px"><div style="font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:#7d4e2b;margin-bottom:10px">${grpLabel}</div><div class="swatch-grid">`;
cols.forEach(c=>{
html+=`<div class="swatch-card" style="border-color:#d0c4b4">
<div class="swatch-color" style="background:${c.color}"></div>
<div class="swatch-info" style="background:#fff">
<div class="swatch-hex">${c.color}</div>
<div class="swatch-nm">${escHtml(c.name)}</div>
</div>
</div>`;
});
html+=`</div></div>`;
});
html+=`</div>`;
// Dark mode block
html+=`<div class="style-sub">Dark Mode</div>
<div style="background:#1c1108;border-radius:12px;padding:20px 22px;margin-bottom:16px;border:1px solid #4a3020">`;
['dark_brand','dark_accent','dark_surface'].forEach(grpId=>{
const grpLabel={dark_brand:'Markenfarben',dark_accent:'Akzente',dark_surface:'Surfaces & Text'}[grpId];
const cols=colors(grpId);
if(!cols.length)return;
html+=`<div style="margin-bottom:16px"><div style="font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:#c4843c;margin-bottom:10px">${grpLabel}</div><div class="swatch-grid">`;
cols.forEach(c=>{
html+=`<div class="swatch-card" style="border-color:#4a3020">
<div class="swatch-color" style="background:${c.color}"></div>
<div class="swatch-info" style="background:#2e1e0e">
<div class="swatch-hex" style="color:#f2ece4">${c.color}</div>
<div class="swatch-nm" style="color:#c4a882">${escHtml(c.name)}</div>
</div>
</div>`;
});
html+=`</div></div>`;
});
html+=`</div>`;
// Token swap table
html+=`<div class="style-sub">Token-Swap Übersicht</div>
<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:8px">
<table style="width:100%;border-collapse:collapse;font-size:13px">
<thead><tr style="border-bottom:1px solid var(--border)">
<td style="padding:10px 14px;color:var(--muted);font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase">Token</td>
<td style="padding:10px 14px;color:var(--muted);font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase">Light</td>
<td style="padding:10px 14px;color:var(--muted);font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase">Dark</td>
<td style="padding:10px 14px;color:var(--muted);font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase">Swap?</td>
</tr></thead><tbody>`;
const tokenSwaps=[
['--color-bg','#f2ece4','#1c1108','✓ ja'],
['--color-surface','#e8ddd0','#2e1e0e','✓ ja'],
['--color-border','#d0c4b4','#4a3020','✓ ja'],
['--color-text-primary','#3e2716','#f2ece4','✓ ja'],
['--color-brand','#7d4e2b','#c4843c','✓ ja'],
['--color-teal-strong','#1a2e2d','#3d7a75','✓ ja'],
['--color-lavender','#b9a3cc','#c8a8e0','✓ ja'],
['--color-acid','#e8f060','#e8f060','— gleich'],
['--color-coral','#ffc8de','#ffc8de','— gleich'],
['--color-info','#409eff','#409eff','— gleich'],
['--color-error','#e62143','#e62143','— gleich'],
];
tokenSwaps.forEach(([token,light,dark,swap],i)=>{
const bg=i%2===0?'':'background:var(--surface2)';
const sameColor=swap.includes('gleich');
html+=`<tr style="${bg};border-bottom:1px solid var(--border)">
<td style="padding:9px 14px;font-family:var(--font-mono);font-size:12px;color:var(--accent)">${token}</td>
<td style="padding:9px 14px"><span style="display:inline-flex;align-items:center;gap:8px"><span style="display:inline-block;width:14px;height:14px;border-radius:3px;background:${light};border:1px solid rgba(0,0,0,.15);flex-shrink:0"></span><span style="font-family:var(--font-mono);font-size:11px;color:var(--muted)">${light}</span></span></td>
<td style="padding:9px 14px"><span style="display:inline-flex;align-items:center;gap:8px"><span style="display:inline-block;width:14px;height:14px;border-radius:3px;background:${dark};border:1px solid rgba(255,255,255,.15);flex-shrink:0"></span><span style="font-family:var(--font-mono);font-size:11px;color:var(--muted)">${dark}</span></span></td>
<td style="padding:9px 14px;font-size:12px;color:${sameColor?'var(--success)':'var(--warning)'}">${swap}</td>
</tr>`;
});
html+=`</tbody></table></div></div>`;
// ── 2. TYPOGRAFIE ──
html+=`<div class="style-section"><div class="style-section-title">02 · Typografie</div>`;
const fontDisplay=fld('font_display');
const fontHeading=fld('font_heading');
const fontSlab=fld('font_slab');
const fontBody=fld('font_body');
const fontMon=fld('font_mono');
if(fontDisplay||fontHeading||fontSlab||fontBody||fontMon){
html+=`<div class="style-sub">Schrift-Hierarchie</div>
<div style="display:flex;flex-direction:column;gap:8px;margin-bottom:16px">`;
const fontRows=[
[fontDisplay,'Display · Titel, Kategorien, laut','font-display','2.4em','letter-spacing:2px;font-weight:900'],
[fontHeading,'Heading · groß, emotional, schnörkelig','font-heading','1.9em','font-style:italic'],
[fontSlab,'Subheading · H2H4, Abschnitte','font-slab','1.3em','font-weight:700'],
[fontBody,'Body · Fließtext, Tabellen, Labels','font-body','1em','font-weight:400'],
[fontMon,'Mono · Code, Tokens, Daten','font-mono','0.9em',''],
];
fontRows.forEach(([f,role,token,size,extra])=>{
if(!f)return;
html+=`<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 18px;display:flex;align-items:center;gap:16px">
<div style="width:180px;flex-shrink:0"><div class="swatch-role">${role}</div><div style="font-size:11px;font-family:var(--font-mono);color:var(--accent);margin-top:2px">--${token}</div></div>
<div style="font-size:${size};color:var(--text);${extra};flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(f)}</div>
<div style="font-size:11px;color:var(--muted);font-family:var(--font-mono);flex-shrink:0">${escHtml(fld('font_source'))}</div>
</div>`;
});
html+=`</div>`;
}
html+=`<div class="style-sub">Typografie-Skala</div><div class="type-specimen">`;
const labels=['Caption','Body S','Body','Body L','H4','H3','H2','H1','Display'];
[...scale].reverse().forEach((sz,i)=>{
const s=parseInt(sz)||16;
const capped=Math.min(s,52);
const label=labels[Math.min(i,labels.length-1)];
html+=`<div class="type-spec-row">
<div class="type-spec-meta">${label}<br>${sz}px</div>
<div class="type-spec-sample" style="font-size:${capped}px;line-height:1.2">${label}</div>
</div>`;
});
html+=`</div>`;
if(fld('base_size')||fld('line_height')||fld('font_weights')){
html+=`<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-top:12px">`;
if(fld('base_size')) html+=`<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px"><div class="swatch-role">Basis-Größe</div><div style="font-size:16px;margin-top:4px;font-family:var(--font-mono);color:var(--text)">${escHtml(fld('base_size'))}</div></div>`;
if(fld('line_height')) html+=`<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px"><div class="swatch-role">Zeilenabstand</div><div style="font-size:16px;margin-top:4px;font-family:var(--font-mono);color:var(--text)">${escHtml(fld('line_height'))}</div></div>`;
if(fld('font_weights')) html+=`<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px"><div class="swatch-role">Schriftgewichte</div><div style="font-size:16px;margin-top:4px;font-family:var(--font-mono);color:var(--text)">${escHtml(fld('font_weights'))}</div></div>`;
html+=`</div>`;
}
html+=`</div>`;
// ── 3. SPACING ──
html+=`<div class="style-section"><div class="style-section-title">03 · Abstände</div>`;
html+=`<div class="style-sub">Spacing-Skala</div>`;
if(fld('spacing_base')) html+=`<div style="color:var(--muted);font-size:13px;margin-bottom:12px">Basis-Einheit: <span style="font-family:var(--font-mono);color:var(--text)">${escHtml(fld('spacing_base'))}</span></div>`;
html+=`<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 20px">`;
spacing.forEach((v,i)=>{
const px=parseInt(v)||8;
const w=Math.min(px*3,280);
html+=`<div class="spacing-out-row">
<div class="sp-out-name">space-${i+1}</div>
<div class="sp-out-val">${v}px</div>
<div class="sp-out-bar-wrap"><div class="sp-out-bar" style="width:${w}px"></div></div>
</div>`;
});
html+=`</div></div>`;
// ── 4. FORMEN & SCHATTEN ──
const hasShape=['radius_sm','radius_md','radius_lg','radius_full','shadow_sm','shadow_md','shadow_lg','border_default'].some(k=>fld(k));
if(hasShape){
html+=`<div class="style-section"><div class="style-section-title">04 · Formen &amp; Schatten</div>`;
html+=`<div class="style-sub">Border Radius</div><div style="display:flex;flex-wrap:wrap;gap:10px;margin-bottom:20px">`;
[['radius_sm','Small'],['radius_md','Medium'],['radius_lg','Large'],['radius_xl','XL'],['radius_full','Full']].forEach(([k,label])=>{
if(!fld(k))return;
const r=fld(k);
const rNum=parseInt(r)||0;
const rCapped=Math.min(rNum,28);
html+=`<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 16px;display:flex;flex-direction:column;align-items:center;gap:10px;min-width:90px">
<div style="width:48px;height:48px;background:var(--accent);opacity:.5;border-radius:${rCapped}px"></div>
<div style="text-align:center"><div class="swatch-role">${label}</div><div style="font-size:11px;font-family:var(--font-mono);color:var(--text)">${escHtml(r)}</div></div>
</div>`;
});
html+=`</div>`;
const hasShadow=fld('shadow_sm')||fld('shadow_md')||fld('shadow_lg');
if(hasShadow){
html+=`<div class="style-sub">Schatten / Elevation</div><div style="display:flex;flex-wrap:wrap;gap:12px">`;
[['shadow_sm','Small'],['shadow_md','Medium'],['shadow_lg','Large']].forEach(([k,label])=>{
if(!fld(k))return;
html+=`<div style="background:#fff;border-radius:var(--radius);padding:14px 20px;display:flex;flex-direction:column;align-items:center;gap:8px;min-width:110px;box-shadow:${escHtml(fld(k))}">
<div class="swatch-role" style="color:#888">${label}</div>
<div style="font-size:11px;font-family:var(--font-mono);color:#444;text-align:center;max-width:120px;word-break:break-all">${escHtml(fld(k))}</div>
</div>`;
});
html+=`</div>`;
}
html+=`</div>`;
}
// ── 5. ICONS ──
const hasIcons=fld('icon_set')||fld('icon_sizes')||fld('icon_style');
if(hasIcons){
html+=`<div class="style-section"><div class="style-section-title">05 · Ikonografie</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">`;
[['icon_set','Icon-Set'],['icon_sizes','Größen'],['icon_style','Stil'],['icon_source','Quelle']].forEach(([k,label])=>{
if(!fld(k))return;
html+=`<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px"><div class="swatch-role">${label}</div><div style="font-size:14px;color:var(--text);margin-top:4px">${escHtml(fld(k))}</div></div>`;
});
html+=`</div></div>`;
}
// ── 6. GRID ──
const hasGrid=fld('grid_columns_desktop')||fld('bp_mobile');
if(hasGrid){
html+=`<div class="style-section"><div class="style-section-title">06 · Raster &amp; Breakpoints</div>`;
html+=`<div class="style-sub">Desktop Grid</div>`;
const cols=parseInt(fld('grid_columns_desktop'))||12;
html+=`<div class="grid-vis">`;
for(let i=0;i<Math.min(cols,12);i++) html+=`<div class="grid-col"></div>`;
html+=`</div>`;
html+=`<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-top:12px margin-bottom:16px">`;
[['grid_columns_desktop','Spalten Desktop'],['grid_gutter_desktop','Gutter Desktop'],['grid_margin_desktop','Margin Desktop'],
['grid_columns_mobile','Spalten Mobile'],['grid_gutter_mobile','Gutter Mobile'],['grid_margin_mobile','Margin Mobile']].forEach(([k,label])=>{
if(!fld(k))return;
html+=`<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:10px 12px"><div class="swatch-role">${label}</div><div style="font-size:13px;font-family:var(--font-mono);color:var(--text);margin-top:3px">${escHtml(fld(k))}</div></div>`;
});
html+=`</div>`;
html+=`<div class="style-sub" style="margin-top:14px">Breakpoints</div><div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">`;
[['bp_mobile','Mobile'],['bp_tablet','Tablet'],['bp_desktop','Desktop'],['bp_wide','Wide'],['max_width','Max. Content-Breite']].forEach(([k,label])=>{
if(!fld(k))return;
html+=`<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:10px 12px;display:flex;align-items:center;gap:10px"><div class="swatch-role" style="width:100px">${label}</div><div style="font-size:13px;font-family:var(--font-mono);color:var(--accent)">${escHtml(fld(k))}</div></div>`;
});
html+=`</div></div>`;
}
// ── 7. TOKENS (CSS) ──
html+=`<div class="style-section"><div class="style-section-title">07 · CSS Design Tokens</div>
<div class="style-sub">Light Mode · :root</div>`;
// Light mode tokens
let cssLight=`:root {\n`;
cssLight+=` <span class="token-comment">/* ── Grounded & Radical · Light Mode ── */</span>\n\n`;
cssLight+=` <span class="token-comment">/* Surfaces */</span>\n`;
const lsurf=colors('light_surface');
if(lsurf[0]) cssLight+=` <span class="token-key">--color-bg</span>: <span class="token-str">${lsurf[0].color}</span>; <span class="token-comment">/* ${lsurf[0].name} */</span>\n`;
if(lsurf[1]) cssLight+=` <span class="token-key">--color-surface</span>: <span class="token-str">${lsurf[1].color}</span>; <span class="token-comment">/* ${lsurf[1].name} */</span>\n`;
if(lsurf[2]) cssLight+=` <span class="token-key">--color-border</span>: <span class="token-str">${lsurf[2].color}</span>; <span class="token-comment">/* ${lsurf[2].name} */</span>\n`;
if(lsurf[3]) cssLight+=` <span class="token-key">--color-text-primary</span>: <span class="token-str">${lsurf[3].color}</span>; <span class="token-comment">/* ${lsurf[3].name} */</span>\n`;
cssLight+=`\n <span class="token-comment">/* Brand */</span>\n`;
const lbrand=colors('light_brand');
if(lbrand[0]) cssLight+=` <span class="token-key">--color-brand</span>: <span class="token-str">${lbrand[0].color}</span>; <span class="token-comment">/* ${lbrand[0].name} */</span>\n`;
if(lbrand[1]) cssLight+=` <span class="token-key">--color-teal-strong</span>: <span class="token-str">${lbrand[1].color}</span>; <span class="token-comment">/* ${lbrand[1].name} */</span>\n`;
if(lbrand[2]) cssLight+=` <span class="token-key">--color-teal</span>: <span class="token-str">${lbrand[2].color}</span>; <span class="token-comment">/* ${lbrand[2].name} */</span>\n`;
cssLight+=`\n <span class="token-comment">/* Accents — identisch in Light & Dark */</span>\n`;
const lacc=colors('light_accent');
if(lacc[0]) cssLight+=` <span class="token-key">--color-acid</span>: <span class="token-str">${lacc[0].color}</span>; <span class="token-comment">/* ${lacc[0].name} */</span>\n`;
if(lacc[1]) cssLight+=` <span class="token-key">--color-coral</span>: <span class="token-str">${lacc[1].color}</span>; <span class="token-comment">/* ${lacc[1].name} */</span>\n`;
if(lacc[2]) cssLight+=` <span class="token-key">--color-violet</span>: <span class="token-str">${lacc[2].color}</span>; <span class="token-comment">/* ${lacc[2].name} */</span>\n`;
const lsem=colors('light_semantic');
if(lsem&&lsem.length){
cssLight+=`\n <span class="token-comment">/* Semantisch (Café Bach Website) */</span>\n`;
if(lsem[0]) cssLight+=` <span class="token-key">--color-info</span>: <span class="token-str">${lsem[0].color}</span>; <span class="token-comment">/* ${lsem[0].name} */</span>\n`;
if(lsem[1]) cssLight+=` <span class="token-key">--color-error</span>: <span class="token-str">${lsem[1].color}</span>; <span class="token-comment">/* ${lsem[1].name} */</span>\n`;
}
cssLight+=`\n <span class="token-comment">/* Typografie */</span>\n`;
if(fld('font_display')) cssLight+=` <span class="token-key">--font-display</span>: <span class="token-str">'${escHtml(fld('font_display'))}', serif</span>; <span class="token-comment">/* Titel, laut */</span>\n`;
if(fld('font_heading')) cssLight+=` <span class="token-key">--font-heading</span>: <span class="token-str">'${escHtml(fld('font_heading'))}', cursive</span>; <span class="token-comment">/* Headlines, emotional */</span>\n`;
if(fld('font_slab')) cssLight+=` <span class="token-key">--font-slab</span>: <span class="token-str">'${escHtml(fld('font_slab'))}', serif</span>; <span class="token-comment">/* H2H4 Subheadings */</span>\n`;
if(fld('font_body')) cssLight+=` <span class="token-key">--font-body</span>: <span class="token-str">'${escHtml(fld('font_body'))}', sans-serif</span>; <span class="token-comment">/* Body, Tabellen */</span>\n`;
if(fld('font_mono')) cssLight+=` <span class="token-key">--font-mono</span>: <span class="token-str">'${escHtml(fld('font_mono'))}', monospace</span>; <span class="token-comment">/* Code, Daten */</span>\n`;
if(fld('base_size')) cssLight+=` <span class="token-key">--font-size-base</span>: <span class="token-val">${escHtml(fld('base_size'))}</span>;\n`;
scale.forEach((s,i)=>{ cssLight+=` <span class="token-key">--font-size-${i+1}</span>: <span class="token-val">${s}px</span>;\n`; });
cssLight+=`\n <span class="token-comment">/* Abstände */</span>\n`;
spacing.forEach((s,i)=>{ cssLight+=` <span class="token-key">--space-${i+1}</span>: <span class="token-val">${s}px</span>;\n`; });
cssLight+=`\n <span class="token-comment">/* Radius */</span>\n`;
[['radius_sm','sm'],['radius_md','md'],['radius_lg','lg'],['radius_xl','xl'],['radius_full','full']].forEach(([k,n])=>{
if(fld(k)) cssLight+=` <span class="token-key">--radius-${n}</span>: <span class="token-val">${escHtml(fld(k))}</span>;\n`;
});
if(fld('shadow_sm')||fld('shadow_md')||fld('shadow_lg')){
cssLight+=`\n <span class="token-comment">/* Schatten */</span>\n`;
[['shadow_sm','sm'],['shadow_md','md'],['shadow_lg','lg']].forEach(([k,n])=>{
if(fld(k)) cssLight+=` <span class="token-key">--shadow-${n}</span>: <span class="token-val">${escHtml(fld(k))}</span>;\n`;
});
}
if(fld('motion_fast')||fld('motion_default')){
cssLight+=`\n <span class="token-comment">/* Motion */</span>\n`;
if(fld('motion_fast')) cssLight+=` <span class="token-key">--duration-fast</span>: <span class="token-val">${escHtml(fld('motion_fast'))}</span>;\n`;
if(fld('motion_default')) cssLight+=` <span class="token-key">--duration-default</span>: <span class="token-val">${escHtml(fld('motion_default'))}</span>;\n`;
if(fld('motion_slow')) cssLight+=` <span class="token-key">--duration-slow</span>: <span class="token-val">${escHtml(fld('motion_slow'))}</span>;\n`;
if(fld('motion_easing')) cssLight+=` <span class="token-key">--easing-default</span>: <span class="token-val">${escHtml(fld('motion_easing'))}</span>;\n`;
}
cssLight+=`}`;
html+=`<div class="token-block">${cssLight}</div>`;
// Dark mode tokens
html+=`<div class="style-sub" style="margin-top:20px">Dark Mode · @media (prefers-color-scheme: dark)</div>`;
let cssDark=`@media (prefers-color-scheme: dark) {\n :root {\n`;
cssDark+=` <span class="token-comment"> /* ── Grounded & Radical · Dark Mode ── */</span>\n\n`;
cssDark+=` <span class="token-comment"> /* Surfaces */</span>\n`;
const dsurf=colors('dark_surface');
if(dsurf[0]) cssDark+=` <span class="token-key">--color-bg</span>: <span class="token-str">${dsurf[0].color}</span>; <span class="token-comment">/* ${dsurf[0].name} */</span>\n`;
if(dsurf[1]) cssDark+=` <span class="token-key">--color-surface</span>: <span class="token-str">${dsurf[1].color}</span>; <span class="token-comment">/* ${dsurf[1].name} */</span>\n`;
if(dsurf[2]) cssDark+=` <span class="token-key">--color-border</span>: <span class="token-str">${dsurf[2].color}</span>; <span class="token-comment">/* ${dsurf[2].name} */</span>\n`;
if(dsurf[3]) cssDark+=` <span class="token-key">--color-text-primary</span>: <span class="token-str">${dsurf[3].color}</span>; <span class="token-comment">/* ${dsurf[3].name} */</span>\n`;
cssDark+=`\n <span class="token-comment"> /* Brand */</span>\n`;
const dbrand=colors('dark_brand');
if(dbrand[0]) cssDark+=` <span class="token-key">--color-brand</span>: <span class="token-str">${dbrand[0].color}</span>; <span class="token-comment">/* ${dbrand[0].name} */</span>\n`;
if(dbrand[1]) cssDark+=` <span class="token-key">--color-teal-strong</span>: <span class="token-str">${dbrand[1].color}</span>; <span class="token-comment">/* ${dbrand[1].name} */</span>\n`;
if(dbrand[2]) cssDark+=` <span class="token-key">--color-teal</span>: <span class="token-str">${dbrand[2].color}</span>; <span class="token-comment">/* ${dbrand[2].name} */</span>\n`;
cssDark+=`\n <span class="token-comment"> /* Lavender — aufgehellt für Kontrast */</span>\n`;
const dacc=colors('dark_accent');
if(dacc[2]) cssDark+=` <span class="token-key">--color-lavender</span>: <span class="token-str">${dacc[2].color}</span>; <span class="token-comment">/* ${dacc[2].name} */</span>\n`;
cssDark+=`\n <span class="token-comment"> /* Acid & Coral — kein Swap, bleiben identisch */</span>\n`;
if(dacc[0]) cssDark+=` <span class="token-comment"> /* --color-acid: ${dacc[0].color} — unverändert */</span>\n`;
if(dacc[1]) cssDark+=` <span class="token-comment"> /* --color-coral: ${dacc[1].color} — unverändert */</span>\n`;
const lsem2=colors('light_semantic');
if(lsem2&&lsem2.length){
cssDark+=`\n <span class="token-comment"> /* Semantisch — auch im Dark Mode identisch */</span>\n`;
if(lsem2[0]) cssDark+=` <span class="token-comment"> /* --color-info: ${lsem2[0].color} — unverändert */</span>\n`;
if(lsem2[1]) cssDark+=` <span class="token-comment"> /* --color-error: ${lsem2[1].color} — unverändert */</span>\n`;
}
cssDark+=` }\n}`;
html+=`<div class="token-block">${cssDark}</div></div>`;
// ── 8. INFRASTRUKTUR ──
const hasInfra=fld('comp_library')||fld('docs_tool')||fld('docs_owner');
if(hasInfra){
html+=`<div class="style-section"><div class="style-section-title">08 · Infrastruktur &amp; Team</div><div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">`;
[['comp_library','Component Library'],['comp_storybook','Storybook'],['comp_npm','NPM-Paket'],['comp_repo','Repository'],
['docs_tool','Dokumentation'],['docs_url','URL'],['docs_owner','Owner'],['docs_maintainers','Maintainer'],
['docs_channel','Kanal'],['docs_release','Release-Zyklus']].forEach(([k,label])=>{
if(!fld(k))return;
html+=`<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px"><div class="swatch-role">${label}</div><div style="font-size:13.5px;color:var(--text);margin-top:3px">${escHtml(fld(k))}</div></div>`;
});
html+=`</div></div>`;
}
// ── 9. CHECKLISTE ──
html+=`<div class="style-section"><div class="style-section-title">09 · Checkliste Fortschritt</div>`;
GUIDE.forEach(ph=>{
const {total,done}=countPhase(ph);
html+=`<div class="style-sub">${ph.phase} · ${ph.title} (${done}/${total})</div><div class="checklist-out">`;
ph.sections.forEach(s=>s.items.forEach(i=>{
const chk=p.checks&&p.checks[i.id];
html+=`<div class="check-out-item ${chk?'done-item':'skip-item'}">
<span class="check-icon">${chk?'✓':'○'}</span>${i.text}
</div>`;
}));
html+=`</div>`;
});
html+=`</div>`;
el.innerHTML=html;
}
// ─────────────────────────────────────────────────────────────────────────────
// DOWNLOAD STYLE DOC
// ─────────────────────────────────────────────────────────────────────────────
function downloadStyleDoc(){
const p=getProj(); if(!p)return;
const content=document.getElementById('styleDocContent').innerHTML;
const name=p.name;
const now=new Date().toLocaleDateString('de-DE',{year:'numeric',month:'long',day:'numeric'});
const html=`<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${name} Style Guide</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Anton&family=Lobster+Two:ital,wght@0,400;0,700;1,400;1,700&family=Roboto+Slab:wght@400;600;700&family=Roboto+Condensed:wght@300;400;500;600&family=Roboto+Mono:wght@400;500&display=swap');
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{--bg:#0f0f10;--surface:#18181b;--surface2:#1f1f24;--border:#2a2a32;--accent:#7c6dfa;--accent2:#c084fc;--text:#e8e6f0;--muted:#7a7890;--muted2:#5a5870;--success:#4ade80;--radius:10px;--radius-lg:14px;--font-body:'Roboto Condensed',sans-serif;--font-display:'Anton',serif;--font-heading:'Lobster Two',cursive;--font-slab:'Roboto Slab',serif;--font-mono:'Roboto Mono',monospace}
body{background:var(--bg);color:var(--text);font-family:var(--font-body);font-size:15px;line-height:1.7;padding:48px 64px;max-width:960px;margin:0 auto}
.style-hero{margin-bottom:48px;padding-bottom:32px;border-bottom:1px solid var(--border)}
.style-hero-name{font-family:var(--font-display);font-size:48px;letter-spacing:-.8px;line-height:1.1;margin-bottom:8px}
.style-hero-meta{font-size:13px;color:var(--muted);display:flex;gap:16px}
.style-section{margin-bottom:44px}
.style-section-title{font-family:var(--font-display);font-size:26px;letter-spacing:-.3px;margin-bottom:20px;color:var(--text)}
.style-sub{font-size:11px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:12px;margin-top:20px}
.swatch-grid{display:flex;flex-wrap:wrap;gap:12px}
.swatch-card{border-radius:var(--radius);overflow:hidden;border:1px solid var(--border);width:120px}
.swatch-color{height:72px}
.swatch-info{padding:8px 10px;background:var(--surface)}
.swatch-role{font-size:11px;font-weight:600;color:var(--muted);letter-spacing:.05em;margin-bottom:2px}
.swatch-hex{font-size:12px;font-family:var(--font-mono);color:var(--text)}
.swatch-nm{font-size:11px;color:var(--muted);margin-top:1px}
.type-specimen{border:1px solid var(--border);border-radius:var(--radius-lg);overflow:hidden;margin-bottom:8px}
.type-spec-row{display:flex;align-items:baseline;gap:16px;padding:14px 18px;border-bottom:1px solid var(--border)}
.type-spec-row:last-child{border-bottom:none}
.type-spec-meta{width:80px;flex-shrink:0;font-size:11px;font-family:var(--font-mono);color:var(--muted);line-height:1.4}
.type-spec-sample{flex:1;color:var(--text)}
.token-block{background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius);padding:16px 18px;font-family:var(--font-mono);font-size:12px;color:#c9c5e8;overflow-x:auto;white-space:pre;line-height:1.75}
.token-comment{color:var(--muted)}
.token-key{color:#c084fc}
.token-val{color:#7dd3fc}
.token-str{color:#86efac}
.spacing-out-row{display:flex;align-items:center;gap:14px;padding:8px 0;border-bottom:1px solid var(--border)}
.spacing-out-row:last-child{border-bottom:none}
.sp-out-name{font-family:var(--font-mono);font-size:12px;color:var(--accent);width:80px}
.sp-out-val{font-family:var(--font-mono);font-size:12px;color:var(--muted);width:50px}
.sp-out-bar-wrap{flex:1}
.sp-out-bar{height:8px;background:var(--accent);border-radius:99px;opacity:.5}
.grid-vis{display:flex;gap:4px;margin-top:12px;padding:16px;background:var(--surface2);border-radius:var(--radius);border:1px solid var(--border)}
.grid-col{flex:1;height:48px;background:rgba(124,109,250,.18);border-radius:4px}
.checklist-out{display:flex;flex-direction:column;gap:4px}
.check-out-item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:6px;font-size:13px}
.done-item{color:var(--success)}
.skip-item{color:var(--muted);text-decoration:line-through}
.check-icon{flex-shrink:0;font-size:14px}
::-webkit-scrollbar{width:5px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--border);border-radius:99px}
</style>
</head>
<body>${content}</body>
</html>`;
const blob=new Blob([html],{type:'text/html'});
const a=document.createElement('a');
a.href=URL.createObjectURL(blob);
a.download=name.replace(/[^a-z0-9]/gi,'_').toLowerCase()+'_style_guide.html';
a.click();
showToast('Style Dokument wird heruntergeladen…');
}
// ─────────────────────────────────────────────────────────────────────────────
// MODAL / SAVE
// ─────────────────────────────────────────────────────────────────────────────
function openModal(){ document.getElementById('modal').classList.add('open'); }
function closeModal(){ document.getElementById('modal').classList.remove('open'); }
function downloadGuide(){
const content=document.documentElement.outerHTML;
const blob=new Blob([content],{type:'text/html'});
const a=document.createElement('a');
a.href=URL.createObjectURL(blob);
const p=getProj();
a.download=(p?p.name.replace(/[^a-z0-9]/gi,'_').toLowerCase():'design_system')+'_guide.html';
a.click(); closeModal(); showToast('Guide wird heruntergeladen…');
}
function printPage(){ closeModal(); window.print(); }
// ─────────────────────────────────────────────────────────────────────────────
// TOAST / UTILS
// ─────────────────────────────────────────────────────────────────────────────
function showToast(msg){
const t=document.getElementById('toast'); t.textContent=msg;
t.classList.add('show'); setTimeout(()=>t.classList.remove('show'),3000);
}
function escHtml(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
// ─────────────────────────────────────────────────────────────────────────────
// INIT
// ─────────────────────────────────────────────────────────────────────────────
loadState();
renderAll();
</script>
</body>
</html>