1500 lines
94 KiB
HTML
Executable file
1500 lines
94 KiB
HTML
Executable file
<!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 & 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 & 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 · H2–H4 (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:'Zeilenabstä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 (5–9 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 (6–8 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.1–1.3 · Body: 1.5–1.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: 60–75 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:'3–5 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: 16–24px.'},
|
||
{id:'g2',text:'Breakpoints festlegen',hint:'Mobile / Tablet / Desktop / Wide'},
|
||
{id:'g3',text:'Mobile Grid definieren',hint:'4-Spalten-Grid. Gutter: 12–16px · 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: 200–300ms · 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:'3–5 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 · H2–H4, 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 & 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 & 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">/* H2–H4 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 & 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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// INIT
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
loadState();
|
||
renderAll();
|
||
</script>
|
||
</body>
|
||
</html>
|