Add full deck, config tool, and updated design docs
Adds the complete "KI verstehen" presentation (36 slides), the Präsentations-Konfigurator (config.html + slides-manifest.js) for curating which slides to show, and updates DESIGN.md/PATTERNS.md/ deck-template.html to reflect the new house font (General Sans) and newly established patterns (chapter dividers, term tooltips, the mitmach game, ambient sound, and the slide configurator).
This commit is contained in:
263
config.html
Normal file
263
config.html
Normal file
@@ -0,0 +1,263 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CANCOM · Deck konfigurieren</title>
|
||||
|
||||
<link rel="preconnect" href="https://api.fontshare.com">
|
||||
<link rel="stylesheet" href="https://api.fontshare.com/v2/css?f[]=general-sans@700,600,500&f[]=satoshi@700,500,400&f[]=jetbrains-mono@700,500,400&display=swap">
|
||||
<script src="slides-manifest.js?v=2"></script>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--cancom-red: #E4002B;
|
||||
--cancom-red-glow: rgba(228,0,43,0.55);
|
||||
--cancom-red-dim: rgba(228,0,43,0.15);
|
||||
--ink: #0A0A0F;
|
||||
--ink-soft: #14141C;
|
||||
--ink-line: #23232E;
|
||||
--paper: #F5F5F2;
|
||||
--paper-dim: rgba(245,245,242,0.6);
|
||||
--paper-mute: rgba(245,245,242,0.35);
|
||||
--font-display: 'General Sans', sans-serif;
|
||||
--font-body: 'Satoshi', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body { background: var(--ink); color: var(--paper); font-family: var(--font-body); min-height: 100%; }
|
||||
body { padding: 64px 64px 140px; max-width: 1100px; margin: 0 auto; }
|
||||
|
||||
.page-eyebrow {
|
||||
font-family: var(--font-mono); font-size: 14px; letter-spacing: 0.28em; text-transform: uppercase;
|
||||
color: var(--cancom-red); display: inline-flex; align-items: center; gap: 14px; margin-bottom: 20px;
|
||||
}
|
||||
.page-eyebrow::before { content: ""; width: 40px; height: 2px; background: var(--cancom-red); }
|
||||
h1 {
|
||||
font-family: var(--font-display); font-weight: 700; font-size: 48px; letter-spacing: -0.03em;
|
||||
line-height: 1.1; margin-bottom: 14px;
|
||||
}
|
||||
.page-lead { font-size: 18px; color: var(--paper-dim); line-height: 1.5; max-width: 640px; margin-bottom: 40px; }
|
||||
|
||||
.toolbar {
|
||||
position: sticky; top: 0; z-index: 50;
|
||||
display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
|
||||
background: rgba(10,10,15,0.9); backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--ink-line); border-radius: 14px;
|
||||
padding: 18px 22px; margin-bottom: 36px;
|
||||
}
|
||||
.toolbar .count {
|
||||
font-family: var(--font-mono); font-size: 14px; letter-spacing: 0.1em; color: var(--paper-mute);
|
||||
margin-right: auto;
|
||||
}
|
||||
.toolbar .count strong { color: var(--paper); font-weight: 700; }
|
||||
.btn {
|
||||
font-family: var(--font-mono); font-size: 13px; letter-spacing: 0.08em; text-transform: uppercase;
|
||||
padding: 12px 18px; border-radius: 8px; border: 1px solid var(--ink-line);
|
||||
background: #0f0f16; color: var(--paper-dim); cursor: pointer;
|
||||
transition: border-color 0.12s ease-out, color 0.12s ease-out, background 0.12s ease-out;
|
||||
}
|
||||
.btn:hover { border-color: var(--cancom-red); color: var(--paper); }
|
||||
.btn.primary {
|
||||
background: var(--cancom-red); border-color: var(--cancom-red); color: #fff; font-weight: 700;
|
||||
box-shadow: 0 10px 30px var(--cancom-red-glow);
|
||||
}
|
||||
.btn.primary:hover { transform: translateY(-1px); }
|
||||
|
||||
.chapter { margin-bottom: 36px; }
|
||||
.chapter-head {
|
||||
display: flex; align-items: center; gap: 14px; margin-bottom: 14px;
|
||||
font-family: var(--font-mono); font-size: 13px; letter-spacing: 0.18em; text-transform: uppercase;
|
||||
color: var(--paper-mute);
|
||||
}
|
||||
.chapter-head .chapter-toggle-all {
|
||||
margin-left: auto; font-size: 11px; color: var(--cancom-red); cursor: pointer; letter-spacing: 0.1em;
|
||||
background: none; border: none; font-family: var(--font-mono); padding: 4px 8px;
|
||||
}
|
||||
.chapter-head .chapter-toggle-all:hover { text-decoration: underline; }
|
||||
|
||||
.row {
|
||||
display: flex; align-items: center; gap: 18px;
|
||||
padding: 16px 20px; border: 1px solid var(--ink-line); border-radius: 10px;
|
||||
background: #0f0f16; margin-bottom: 10px;
|
||||
transition: border-color 0.12s ease-out, background 0.12s ease-out;
|
||||
}
|
||||
.row:hover { border-color: rgba(228,0,43,0.35); background: #14141c; }
|
||||
.row.divider-row { background: transparent; border-style: dashed; }
|
||||
.row.locked { opacity: 0.55; }
|
||||
|
||||
.row input[type="checkbox"] {
|
||||
appearance: none; width: 22px; height: 22px; flex-shrink: 0;
|
||||
border: 1.5px solid var(--ink-line); border-radius: 6px; background: #0a0a0f;
|
||||
cursor: pointer; position: relative; transition: border-color 0.12s ease-out, background 0.12s ease-out;
|
||||
}
|
||||
.row input[type="checkbox"]:checked { background: var(--cancom-red); border-color: var(--cancom-red); }
|
||||
.row input[type="checkbox"]:checked::after {
|
||||
content: ""; position: absolute; left: 7px; top: 3px; width: 5px; height: 10px;
|
||||
border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg);
|
||||
}
|
||||
.row input[type="checkbox"]:disabled { cursor: not-allowed; }
|
||||
|
||||
.row .slide-id {
|
||||
font-family: var(--font-mono); font-size: 13px; color: var(--paper-mute); width: 28px; flex-shrink: 0;
|
||||
}
|
||||
.row .slide-title { font-size: 17px; color: var(--paper); flex: 1; }
|
||||
.row.divider-row .slide-title { color: var(--paper-mute); font-family: var(--font-mono); font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase; }
|
||||
.row .badge {
|
||||
font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
|
||||
color: var(--paper-mute); border: 1px solid var(--ink-line); border-radius: 20px; padding: 4px 10px;
|
||||
}
|
||||
|
||||
.footer-bar {
|
||||
position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
|
||||
display: flex; justify-content: center; gap: 16px;
|
||||
padding: 22px; background: linear-gradient(0deg, var(--ink) 60%, transparent);
|
||||
}
|
||||
.footer-bar .btn { padding: 16px 32px; font-size: 14px; }
|
||||
|
||||
.hint { font-size: 14px; color: var(--paper-mute); margin-top: -8px; margin-bottom: 30px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-eyebrow">Präsentations-Konfigurator</div>
|
||||
<h1>Welche Slides sollen gezeigt werden?</h1>
|
||||
<p class="page-lead">Häkchen entfernen blendet eine Slide für den Vortrag aus, ohne sie zu löschen. Die Auswahl wird lokal in diesem Browser gespeichert und beim nächsten Öffnen des Decks automatisch angewendet.</p>
|
||||
|
||||
<div class="toolbar">
|
||||
<span class="count" id="count">– / – Slides aktiv</span>
|
||||
<button class="btn" id="btnAll">Alle an</button>
|
||||
<button class="btn" id="btnNone">Alle aus</button>
|
||||
<button class="btn" id="btnReset">Zurücksetzen</button>
|
||||
<button class="btn primary" id="btnOpen">Zur Präsentation →</button>
|
||||
</div>
|
||||
|
||||
<div id="chapters"></div>
|
||||
|
||||
<div class="footer-bar">
|
||||
<button class="btn primary" id="btnSaveBottom">Speichern & zur Präsentation →</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const chaptersEl = document.getElementById('chapters');
|
||||
const countEl = document.getElementById('count');
|
||||
|
||||
// Slides nach Kapitel gruppieren, Reihenfolge aus dem Manifest beibehalten
|
||||
const groups = [];
|
||||
const groupIndex = {};
|
||||
SLIDE_MANIFEST.forEach((slide) => {
|
||||
const key = slide.kapitel;
|
||||
if (!(key in groupIndex)) {
|
||||
groupIndex[key] = groups.length;
|
||||
groups.push({ kapitel: key, slides: [] });
|
||||
}
|
||||
groups[groupIndex[key]].slides.push(slide);
|
||||
});
|
||||
|
||||
let enabled = loadSlideConfig();
|
||||
if (!enabled) {
|
||||
enabled = new Set(SLIDE_MANIFEST.map((s) => s.id));
|
||||
}
|
||||
|
||||
function render() {
|
||||
chaptersEl.innerHTML = '';
|
||||
groups.forEach((group) => {
|
||||
const chapterDiv = document.createElement('div');
|
||||
chapterDiv.className = 'chapter';
|
||||
|
||||
const head = document.createElement('div');
|
||||
head.className = 'chapter-head';
|
||||
head.innerHTML = '<span>' + group.kapitel + '</span>';
|
||||
const toggleAllBtn = document.createElement('button');
|
||||
toggleAllBtn.className = 'chapter-toggle-all';
|
||||
toggleAllBtn.textContent = 'Kapitel an/aus';
|
||||
toggleAllBtn.addEventListener('click', () => {
|
||||
const toggleable = group.slides.filter((s) => !s.locked);
|
||||
const allOn = toggleable.every((s) => enabled.has(s.id));
|
||||
toggleable.forEach((s) => {
|
||||
if (allOn) enabled.delete(s.id); else enabled.add(s.id);
|
||||
});
|
||||
render();
|
||||
});
|
||||
head.appendChild(toggleAllBtn);
|
||||
chapterDiv.appendChild(head);
|
||||
|
||||
group.slides.forEach((slide) => {
|
||||
const row = document.createElement('label');
|
||||
row.className = 'row' + (slide.type === 'divider' ? ' divider-row' : '') + (slide.locked ? ' locked' : '');
|
||||
|
||||
const cb = document.createElement('input');
|
||||
cb.type = 'checkbox';
|
||||
cb.checked = enabled.has(slide.id);
|
||||
cb.disabled = !!slide.locked;
|
||||
cb.addEventListener('change', () => {
|
||||
if (cb.checked) enabled.add(slide.id); else enabled.delete(slide.id);
|
||||
updateCount();
|
||||
});
|
||||
|
||||
const idEl = document.createElement('span');
|
||||
idEl.className = 'slide-id';
|
||||
idEl.textContent = String(slide.id).padStart(2, '0');
|
||||
|
||||
const titleEl = document.createElement('span');
|
||||
titleEl.className = 'slide-title';
|
||||
titleEl.textContent = slide.title;
|
||||
|
||||
row.appendChild(cb);
|
||||
row.appendChild(idEl);
|
||||
row.appendChild(titleEl);
|
||||
|
||||
if (slide.locked) {
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'badge';
|
||||
badge.textContent = 'immer aktiv';
|
||||
row.appendChild(badge);
|
||||
} else if (slide.type === 'divider') {
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'badge';
|
||||
badge.textContent = 'Kapitel-Trenner';
|
||||
row.appendChild(badge);
|
||||
}
|
||||
|
||||
chapterDiv.appendChild(row);
|
||||
});
|
||||
|
||||
chaptersEl.appendChild(chapterDiv);
|
||||
});
|
||||
updateCount();
|
||||
}
|
||||
|
||||
function updateCount() {
|
||||
countEl.innerHTML = '<strong>' + enabled.size + '</strong> / ' + SLIDE_MANIFEST.length + ' Slides aktiv';
|
||||
saveSlideConfig(enabled);
|
||||
}
|
||||
|
||||
document.getElementById('btnAll').addEventListener('click', () => {
|
||||
enabled = new Set(SLIDE_MANIFEST.map((s) => s.id));
|
||||
render();
|
||||
});
|
||||
document.getElementById('btnNone').addEventListener('click', () => {
|
||||
enabled = new Set(SLIDE_MANIFEST.filter((s) => s.locked).map((s) => s.id));
|
||||
render();
|
||||
});
|
||||
document.getElementById('btnReset').addEventListener('click', () => {
|
||||
enabled = new Set(SLIDE_MANIFEST.map((s) => s.id));
|
||||
render();
|
||||
});
|
||||
function goToDeck() {
|
||||
saveSlideConfig(enabled);
|
||||
// localStorage wird bei file:// nicht zuverlässig zwischen Dateien geteilt (v. a. Safari) —
|
||||
// die Auswahl deshalb zusätzlich als URL-Parameter mitgeben, das ist die verlässliche Quelle.
|
||||
const ids = Array.from(enabled).sort((a, b) => a - b).join(',');
|
||||
window.location.href = 'ki-verstehen.html?slides=' + encodeURIComponent(ids);
|
||||
}
|
||||
document.getElementById('btnOpen').addEventListener('click', goToDeck);
|
||||
document.getElementById('btnSaveBottom').addEventListener('click', goToDeck);
|
||||
|
||||
render();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user