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:
Erik Thiele
2026-07-25 16:29:28 +02:00
parent a0641c6fbf
commit 2633ca0e0d
9 changed files with 4570 additions and 0 deletions

961
deck-template.html Normal file
View File

@@ -0,0 +1,961 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CANCOM · [DECK-TITEL]</title>
<!-- General Sans für Headlines, Satoshi für Body, JetBrains Mono für alles Technische -->
<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">
<style>
/* ===========================================
THEME TOKENS — für ein Kunden-Theme nur hier anfassen
=========================================== */
: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);
--accent-cool: #4CC9F0;
--accent-warm: #FFB627;
--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);
--ease-swift: cubic-bezier(0.65, 0, 0.35, 1);
--stage-bg: #000;
--slide-bg: var(--ink);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
/* ===========================================
VIEWPORT-BASE (FIXED 16:9 STAGE) — NICHT ÄNDERN
=========================================== */
html, body {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
background: var(--stage-bg);
font-family: var(--font-body);
color: var(--paper);
}
.deck-viewport { position: fixed; inset: 0; overflow: hidden; background: var(--stage-bg); }
.deck-stage {
position: absolute; left: 0; top: 0;
width: 1920px; height: 1080px;
overflow: hidden; transform-origin: 0 0;
background: var(--slide-bg);
}
.slide {
position: absolute; inset: 0;
width: 1920px; height: 1080px;
overflow: hidden; display: block;
visibility: hidden; opacity: 0;
pointer-events: none;
background: var(--slide-bg);
}
.slide.active, .slide.visible {
visibility: visible; opacity: 1;
pointer-events: auto; z-index: 1;
}
img, video, canvas, svg { max-width: 100%; max-height: 100%; }
@media print {
html, body { width: 1920px; height: auto; overflow: visible; background: #fff; }
.deck-viewport { position: static; overflow: visible; background: #fff; }
.deck-stage { position: static; width: auto; height: auto; transform: none !important; background: none; }
.slide { position: relative; visibility: visible !important; opacity: 1 !important;
pointer-events: auto !important; width: 1920px; height: 1080px;
break-after: page; page-break-after: always; }
.slide:last-child { break-after: auto; page-break-after: auto; }
.deck-controls, .deck-hud, .edit-hotzone, .edit-toggle { display: none !important; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.2s !important;
}
}
/* ===========================================
HUD & CHROME (außerhalb der Slides)
=========================================== */
.deck-hud {
position: absolute;
left: 48px; bottom: 32px;
z-index: 900;
display: flex; align-items: center; gap: 18px;
font-family: var(--font-mono);
font-size: 18px;
color: var(--paper-mute);
letter-spacing: 0.14em;
text-transform: uppercase;
mix-blend-mode: difference;
pointer-events: none;
}
.deck-hud .dot { width: 8px; height: 8px; background: var(--cancom-red); border-radius: 50%;
box-shadow: 0 0 12px var(--cancom-red-glow); animation: pulse 1.6s infinite; }
.deck-progress {
position: fixed; left: 0; top: 0;
height: 3px; width: 100%;
background: rgba(255,255,255,0.06);
z-index: 900;
}
.deck-progress .bar {
height: 100%; width: 0;
background: linear-gradient(90deg, var(--cancom-red), #ff5060);
box-shadow: 0 0 20px var(--cancom-red-glow);
transition: width 0.6s var(--ease-out-expo);
}
@keyframes pulse {
0%,100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(1.4); }
}
/* Edit-Hotzone + Toggle (JS-gesteuert) */
.edit-hotzone {
position: fixed; top: 0; left: 0;
width: 80px; height: 80px; z-index: 10000; cursor: pointer;
}
.edit-toggle {
position: fixed; top: 18px; left: 18px;
width: 44px; height: 44px;
border-radius: 12px;
border: 1px solid rgba(255,255,255,0.15);
background: rgba(20,20,28,0.85);
backdrop-filter: blur(10px);
color: var(--paper);
font-size: 18px; cursor: pointer;
opacity: 0; pointer-events: none;
transition: opacity 0.3s ease, transform 0.3s ease;
z-index: 10001;
}
.edit-toggle.show, .edit-toggle.active { opacity: 1; pointer-events: auto; }
.edit-toggle.active { background: var(--cancom-red); border-color: var(--cancom-red); }
[contenteditable="true"] {
outline: 2px dashed var(--cancom-red);
outline-offset: 4px;
border-radius: 2px;
}
/* ===========================================
REVEAL ANIMATION SYSTEM
DOM-Reihenfolge = Delay-Reihenfolge. Nie d5 vor d3.
=========================================== */
.reveal {
opacity: 0;
transform: translateY(40px);
transition: opacity 0.9s var(--ease-out-expo),
transform 0.9s var(--ease-out-expo);
}
.slide.visible .reveal { opacity: 1; transform: translateY(0); }
.slide.visible .reveal.d1 { transition-delay: 0.10s; }
.slide.visible .reveal.d2 { transition-delay: 0.22s; }
.slide.visible .reveal.d3 { transition-delay: 0.34s; }
.slide.visible .reveal.d4 { transition-delay: 0.46s; }
.slide.visible .reveal.d5 { transition-delay: 0.58s; }
.slide.visible .reveal.d6 { transition-delay: 0.70s; }
.slide.visible .reveal.d7 { transition-delay: 0.82s; }
.reveal-slide-x {
opacity: 0; transform: translateX(-60px);
transition: opacity 0.9s var(--ease-out-expo), transform 0.9s var(--ease-out-expo);
}
.slide.visible .reveal-slide-x { opacity: 1; transform: translateX(0); }
/* ===========================================
TITLE SLIDE
=========================================== */
.title-slide {
background: radial-gradient(ellipse at 20% 30%, #1a0409 0%, var(--ink) 55%),
var(--ink);
padding: 120px;
display: flex; flex-direction: column; justify-content: space-between;
}
.title-canvas { position: absolute; inset: 0; z-index: 0; opacity: 0.9; }
.title-inner { position: relative; z-index: 2; }
.title-topbar {
display: flex; justify-content: space-between; align-items: center;
font-family: var(--font-mono);
font-size: 18px; letter-spacing: 0.28em;
text-transform: uppercase; color: var(--paper-mute);
}
.brand-mark {
display: inline-flex; align-items: baseline; gap: 14px;
font-family: var(--font-display); font-weight: 700;
color: var(--paper); font-size: 26px; letter-spacing: -0.02em;
}
.brand-mark::before {
content: ""; width: 14px; height: 14px;
background: var(--cancom-red);
display: inline-block;
box-shadow: 0 0 20px var(--cancom-red-glow);
animation: pulse 2s infinite;
}
.title-headline {
position: relative; z-index: 2;
font-family: var(--font-display); font-weight: 700;
font-size: 220px; line-height: 0.88;
letter-spacing: -0.045em;
color: var(--paper);
}
.title-headline .accent { color: var(--cancom-red); display: inline-block; position: relative; }
.title-headline .accent::after {
content: ""; position: absolute; left: 0; bottom: 8px;
width: 100%; height: 8px;
background: var(--cancom-red);
transform: scaleX(0); transform-origin: left;
animation: underline-in 1.4s var(--ease-out-expo) 1.2s forwards;
}
@keyframes underline-in { to { transform: scaleX(1); } }
.title-strike { display: inline-block; position: relative; color: var(--paper-mute); }
.title-strike::after {
content: ""; position: absolute; left: -4%; top: 52%;
width: 108%; height: 10px;
background: var(--cancom-red);
transform: scaleX(0); transform-origin: left;
animation: strike-in 0.8s var(--ease-swift) 0.8s forwards;
}
@keyframes strike-in { to { transform: scaleX(1); } }
.title-meta {
position: relative; z-index: 2;
display: flex; justify-content: space-between; align-items: flex-end;
font-family: var(--font-mono); font-size: 16px;
color: var(--paper-mute); letter-spacing: 0.14em;
text-transform: uppercase;
}
.title-meta .huge {
font-family: var(--font-display); font-size: 90px;
color: var(--cancom-red); letter-spacing: -0.03em;
text-transform: none; line-height: 1;
text-shadow: 0 0 40px var(--cancom-red-glow);
}
/* ===========================================
GENERIC CONTENT SLIDE — der Standard-Fall
=========================================== */
.slide-content {
padding: 100px 120px;
display: flex; flex-direction: column;
height: 100%; position: relative;
}
.slide-content::before {
content: attr(data-num);
position: absolute;
top: 60px; right: 120px;
font-family: var(--font-mono);
font-size: 14px; letter-spacing: 0.2em;
color: var(--paper-mute);
}
.slide-eyebrow {
font-family: var(--font-mono); font-size: 16px;
letter-spacing: 0.28em; text-transform: uppercase;
color: var(--cancom-red);
display: inline-flex; align-items: center; gap: 14px;
margin-bottom: 32px;
}
.slide-eyebrow::before { content: ""; width: 40px; height: 2px; background: var(--cancom-red); }
.slide-title {
font-family: var(--font-display); font-weight: 700;
font-size: 112px; line-height: 0.94;
letter-spacing: -0.035em;
color: var(--paper);
margin-bottom: 24px;
}
.slide-title .accent { color: var(--cancom-red); }
.slide-lead {
font-size: 30px; line-height: 1.4;
color: var(--paper-dim);
max-width: 1300px;
margin-bottom: 60px;
}
/* ===========================================
CARD GRID
=========================================== */
.pain-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 32px;
flex: 1;
}
.pain-card {
border: 1px solid var(--ink-line);
background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(0,0,0,0.15));
padding: 44px;
display: flex; flex-direction: column; justify-content: space-between;
position: relative; overflow: hidden;
transition: transform 0.15s ease-out, border-color 0.12s ease-out;
}
.pain-card:hover { transform: translateY(-8px); border-color: var(--cancom-red); }
.pain-card::before {
content: ""; position: absolute; inset: 0;
background: radial-gradient(400px circle at var(--mx, 50%) var(--my, 50%), var(--cancom-red-dim), transparent 70%);
opacity: 0; transition: opacity 0.12s ease-out;
pointer-events: none;
}
.pain-card:hover::before { opacity: 1; }
.pain-num {
font-family: var(--font-mono);
font-size: 14px; color: var(--paper-mute);
letter-spacing: 0.24em;
}
.pain-title {
font-family: var(--font-display); font-weight: 600;
font-size: 42px; color: var(--paper);
line-height: 1.05; letter-spacing: -0.02em;
margin: 24px 0 16px;
}
.pain-body { font-size: 20px; line-height: 1.5; color: var(--paper-dim); }
/* ===========================================
VERSUS SLIDE — Split Layout
=========================================== */
.versus { position: absolute; inset: 0; display: grid; grid-template-columns: 1fr 1fr; }
.versus-side {
padding: 120px 90px;
position: relative;
display: flex; flex-direction: column; justify-content: center;
}
.versus-side.left {
background: linear-gradient(135deg, #1a1a22 0%, #0e0e14 100%);
color: var(--paper);
}
.versus-side.right {
background: linear-gradient(135deg, #24060d 0%, var(--ink) 100%);
color: var(--paper);
border-left: 3px solid var(--cancom-red);
}
.versus-label {
font-family: var(--font-mono); font-size: 16px;
letter-spacing: 0.3em; text-transform: uppercase;
margin-bottom: 30px;
}
.versus-side.left .versus-label { color: rgba(255,255,255,0.55); }
.versus-side.right .versus-label { color: var(--cancom-red); }
.versus-headline {
font-family: var(--font-display); font-weight: 700;
font-size: 100px; line-height: 0.92;
letter-spacing: -0.035em;
margin-bottom: 48px;
}
.versus-list { list-style: none; }
.versus-list li {
padding: 18px 0;
font-size: 24px; line-height: 1.35;
border-bottom: 1px solid rgba(255,255,255,0.08);
display: flex; gap: 18px; align-items: baseline;
}
.versus-side.left .versus-list li::before {
content: "×"; color: #a0a0ac; font-size: 28px; font-family: var(--font-mono);
}
.versus-side.right .versus-list li::before {
content: "→"; color: var(--cancom-red); font-size: 22px; font-family: var(--font-mono);
}
.versus-vs {
position: absolute;
top: 50%; left: 50%;
width: 140px; height: 140px;
margin: -70px 0 0 -70px;
border-radius: 50%;
background: var(--cancom-red);
color: var(--paper);
display: flex; align-items: center; justify-content: center;
font-family: var(--font-display); font-weight: 700;
font-size: 46px; letter-spacing: -0.02em;
box-shadow: 0 0 60px var(--cancom-red-glow), inset 0 0 40px rgba(0,0,0,0.2);
z-index: 2;
opacity: 0;
transition: opacity 0.9s var(--ease-out-expo);
}
.slide.visible .versus-vs { opacity: 1; }
.versus-vs::before {
content: ""; position: absolute; inset: -10px;
border: 2px solid var(--cancom-red);
border-radius: 50%;
opacity: 0.4;
animation: expand-pulse 2s infinite;
}
@keyframes expand-pulse {
0% { transform: scale(1); opacity: 0.5; }
100% { transform: scale(1.4); opacity: 0; }
}
/* ===========================================
COUNTER SLIDE — animierte Zahlen
=========================================== */
.counter-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 60px; flex: 1; align-items: center; }
.counter-item { text-align: left; }
.counter-value {
font-family: var(--font-display); font-weight: 700;
font-size: 220px; line-height: 0.9;
letter-spacing: -0.05em;
color: var(--cancom-red);
text-shadow: 0 0 60px var(--cancom-red-glow);
display: block;
}
.counter-value .unit { font-size: 80px; color: var(--paper); text-shadow: none; }
.counter-label {
font-family: var(--font-mono); font-size: 18px;
letter-spacing: 0.16em; text-transform: uppercase;
color: var(--paper-mute);
margin-top: 16px; line-height: 1.5;
}
.counter-caption { font-size: 22px; color: var(--paper-dim); margin-top: 12px; max-width: 380px; line-height: 1.4; }
/* ===========================================
CODE SLIDE
=========================================== */
.code-slide { padding: 100px 120px; display: flex; gap: 80px; height: 100%; }
.code-left { flex: 1; display: flex; flex-direction: column; justify-content: center; }
.code-right { flex: 1.2; display: flex; flex-direction: column; }
.code-window {
background: #0d0d13;
border: 1px solid var(--ink-line);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 40px 100px rgba(0,0,0,0.6), 0 0 0 1px rgba(228,0,43,0.15);
flex: 1;
display: flex; flex-direction: column;
}
.code-tabs {
background: #16161e;
padding: 14px 20px;
display: flex; align-items: center; gap: 12px;
border-bottom: 1px solid var(--ink-line);
}
.code-tabs .dots { display: flex; gap: 8px; }
.code-tabs .dots span { width: 12px; height: 12px; border-radius: 50%; background: #35353f; }
.code-tabs .dots span:first-child { background: #ff5f57; }
.code-tabs .dots span:nth-child(2) { background: #febc2e; }
.code-tabs .dots span:nth-child(3) { background: #28c840; }
.code-tabs .filename { font-family: var(--font-mono); font-size: 14px; color: var(--paper-mute); margin-left: 12px; }
.code-tabs .live {
margin-left: auto;
font-family: var(--font-mono); font-size: 12px;
letter-spacing: 0.2em; color: var(--cancom-red);
display: flex; align-items: center; gap: 8px;
}
.code-tabs .live::before {
content: ""; width: 8px; height: 8px; background: var(--cancom-red);
border-radius: 50%; animation: pulse 1.2s infinite;
box-shadow: 0 0 10px var(--cancom-red-glow);
}
.code-body {
padding: 32px;
font-family: var(--font-mono); font-size: 20px;
line-height: 1.55;
color: #d6d6dc;
flex: 1; overflow: hidden;
}
.code-body .kw { color: #ff5060; }
.code-body .fn { color: #4CC9F0; }
.code-body .str { color: #ffb627; }
.code-body .com { color: #5a5a68; font-style: italic; }
.code-body .num { color: #b5f4a5; }
.code-body .prop { color: #d0a8ff; }
.code-preview {
margin-top: 32px;
padding: 40px;
background: linear-gradient(135deg, #16161e, #0d0d13);
border: 1px solid var(--ink-line);
border-radius: 12px;
display: flex; align-items: center; justify-content: center;
gap: 24px;
}
.demo-button {
padding: 20px 40px;
background: var(--cancom-red);
color: #fff;
border: none;
border-radius: 8px;
font-family: var(--font-display); font-weight: 600;
font-size: 22px; letter-spacing: 0.02em;
cursor: pointer;
position: relative; overflow: hidden;
transition: transform 0.3s var(--ease-out-expo);
box-shadow: 0 10px 40px var(--cancom-red-glow);
}
.demo-button:hover { transform: translateY(-3px) scale(1.03); }
.demo-button .ripple {
position: absolute;
left: var(--rx, 50%);
top: var(--ry, 50%);
width: 0; height: 0;
background: rgba(255,255,255,0.35);
border-radius: 50%;
transform: translate(-50%,-50%);
pointer-events: none;
animation: ripple-out 0.7s ease-out forwards;
}
@keyframes ripple-out { to { width: 400px; height: 400px; opacity: 0; } }
/* ===========================================
FEATURE GRID — 4×2 Tiles, .wide = span 2
=========================================== */
.feature-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(2, minmax(0, 1fr));
gap: 20px;
flex: 1;
min-height: 0;
}
.feature-tile {
border: 1px solid var(--ink-line);
padding: 28px;
background: #0f0f16;
position: relative; overflow: hidden;
display: flex; flex-direction: column; justify-content: space-between;
gap: 12px;
transition: transform 0.15s ease-out, border-color 0.12s ease-out, background 0.15s ease-out;
cursor: default;
min-height: 0;
}
.feature-tile.wide { grid-column: span 2; }
.feature-tile:hover { background: #14141c; border-color: var(--cancom-red); transform: translateY(-4px); }
.feature-name {
font-family: var(--font-display); font-weight: 600;
font-size: 28px; color: var(--paper);
letter-spacing: -0.02em; line-height: 1.05;
}
.feature-desc { font-size: 16px; color: var(--paper-dim); line-height: 1.4; margin-top: 8px; }
.feature-tag {
font-family: var(--font-mono); font-size: 12px;
color: var(--cancom-red); letter-spacing: 0.2em;
text-transform: uppercase;
}
/* ===========================================
CLOSING SLIDE — invertiert, hell
=========================================== */
.closing-slide {
background: var(--paper);
color: var(--ink);
padding: 120px;
display: flex; flex-direction: column; justify-content: space-between;
position: relative; overflow: hidden;
}
.closing-slide::before {
content: ""; position: absolute;
top: -20%; right: -10%;
width: 900px; height: 900px;
background: var(--cancom-red);
border-radius: 50%;
filter: blur(120px);
opacity: 0.3;
animation: blob-drift 12s ease-in-out infinite;
}
@keyframes blob-drift {
0%, 100% { transform: translate(0, 0) scale(1); }
50% { transform: translate(-100px, 80px) scale(1.15); }
}
.closing-top {
position: relative; z-index: 2;
font-family: var(--font-mono); font-size: 18px;
letter-spacing: 0.3em; text-transform: uppercase;
color: var(--cancom-red);
display: flex; align-items: center; gap: 16px;
}
.closing-top::before { content: ""; width: 60px; height: 2px; background: var(--cancom-red); }
.closing-headline {
position: relative; z-index: 2;
font-family: var(--font-display); font-weight: 700;
font-size: 240px; line-height: 0.86;
letter-spacing: -0.045em;
color: var(--ink);
}
.closing-headline .accent { color: var(--cancom-red); }
.closing-meta {
position: relative; z-index: 2;
display: flex; justify-content: space-between; align-items: flex-end;
font-family: var(--font-mono); font-size: 16px;
letter-spacing: 0.16em; text-transform: uppercase;
color: var(--ink);
}
.closing-meta .brand {
font-family: var(--font-display); font-weight: 700;
font-size: 42px; letter-spacing: -0.02em;
text-transform: none; color: var(--ink);
display: flex; align-items: center; gap: 14px;
}
.closing-meta .brand::before { content: ""; width: 14px; height: 14px; background: var(--cancom-red); }
/* ===========================================
SLIDE-SPEZIFISCHES CSS — hier anhängen
=========================================== */
</style>
</head>
<body>
<div class="deck-progress"><div class="bar" id="progressBar"></div></div>
<div class="edit-hotzone"></div>
<button class="edit-toggle" id="editToggle" title="Edit mode (E)">✏️</button>
<!-- ===========================================
16:9 BÜHNE — 1920×1080
=========================================== -->
<div class="deck-viewport">
<main class="deck-stage" id="deckStage">
<div class="deck-hud">
<span class="dot"></span>
<span id="hudCounter">01 / 04</span>
<span>·</span>
<span>[DECK-NAME]</span>
</div>
<!-- ============ SLIDE 1: TITEL ============ -->
<section class="slide title-slide active">
<canvas class="title-canvas" id="titleCanvas"></canvas>
<div class="title-inner title-topbar reveal d1">
<span class="brand-mark">CANCOM</span>
<span>Deck · MM · JJJJ</span>
</div>
<h1 class="title-headline reveal d2">
Erste Zeile.<br>
Zweite <span class="accent">Zeile.</span>
</h1>
<div class="title-meta reveal d4">
<div>
<div class="huge">CLAIM.</div>
<div style="margin-top: 16px; font-size: 26px; letter-spacing: 0.14em;">Untertitel in einer Zeile</div>
</div>
<div style="text-align: right;">
<div>[Sprecher:in]</div>
<div style="margin-top: 6px; opacity: 0.6;">CANCOM · [Anlass]</div>
</div>
</div>
</section>
<!-- ============ SLIDE 2: STANDARD-CONTENT ============ -->
<section class="slide">
<div class="slide-content" data-num="02 / 04">
<span class="slide-eyebrow reveal d1">Eyebrow</span>
<h2 class="slide-title reveal d2">Die Aussage<br>der <span class="accent">Slide.</span></h2>
<p class="slide-lead reveal d3">Ein bis zwei Sätze, die die Headline aufmachen und zum Content-Block überleiten.</p>
<div class="pain-grid">
<div class="pain-card reveal d4">
<span class="pain-num">01</span>
<div>
<div class="pain-title">Karten-<br>Titel</div>
<div class="pain-body">Zwei bis drei Sätze. Konkret, kein Marketing-Sprech.</div>
</div>
</div>
<div class="pain-card reveal d5">
<span class="pain-num">02</span>
<div>
<div class="pain-title">Karten-<br>Titel</div>
<div class="pain-body">Zwei bis drei Sätze. Konkret, kein Marketing-Sprech.</div>
</div>
</div>
<div class="pain-card reveal d6">
<span class="pain-num">03</span>
<div>
<div class="pain-title">Karten-<br>Titel</div>
<div class="pain-body">Zwei bis drei Sätze. Konkret, kein Marketing-Sprech.</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============ SLIDE 3: ZAHLEN ============ -->
<section class="slide">
<div class="slide-content" data-num="03 / 04">
<span class="slide-eyebrow reveal d1">Die Zahlen</span>
<h2 class="slide-title reveal d2">Warum sich das<br><span class="accent">rechnet.</span></h2>
<div class="counter-grid">
<div class="counter-item reveal d3">
<span class="counter-value"><span data-count="100">0</span><span class="unit">%</span></span>
<div class="counter-label">Label der Kennzahl</div>
<div class="counter-caption">Ein Satz Kontext, damit die Zahl etwas bedeutet.</div>
</div>
<div class="counter-item reveal d4">
<span class="counter-value"><span data-count="0.2">0</span><span class="unit">MB</span></span>
<div class="counter-label">Label der Kennzahl</div>
<div class="counter-caption">Ein Satz Kontext, damit die Zahl etwas bedeutet.</div>
</div>
<div class="counter-item reveal d5">
<span class="counter-value"><span data-count="60">0</span><span class="unit">fps</span></span>
<div class="counter-label">Label der Kennzahl</div>
<div class="counter-caption">Ein Satz Kontext, damit die Zahl etwas bedeutet.</div>
</div>
</div>
</div>
</section>
<!-- ============ SLIDE 4: CLOSING ============ -->
<section class="slide">
<div class="closing-slide">
<div class="closing-top reveal d1">Take-away · [Deck-Name]</div>
<h2 class="closing-headline reveal d2">
Der eine Satz,<br>der <span class="accent">hängen</span><br>bleibt.
</h2>
<div class="closing-meta reveal d4">
<div class="brand">CANCOM</div>
<div style="text-align: right;">
<div>Fragen? → [mail@cancom.de]</div>
<div style="margin-top: 6px; opacity: 0.55;">Deck gebaut mit HTML · CSS · JS</div>
</div>
</div>
</div>
</section>
</main>
</div>
<script>
/* ===========================================
CANVAS BACKGROUND — Partikelfeld für die Titelslide
Entfernen, wenn die Titelslide kein <canvas id="titleCanvas"> hat.
=========================================== */
(function initTitleCanvas() {
const canvas = document.getElementById('titleCanvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
let w, h, dpr = window.devicePixelRatio || 1;
let particles = [];
const N = 80;
function resize() {
w = canvas.offsetWidth = 1920;
h = canvas.offsetHeight = 1080;
canvas.width = w * dpr;
canvas.height = h * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
function init() {
particles = [];
for (let i = 0; i < N; i++) {
particles.push({
x: Math.random() * w,
y: Math.random() * h,
vx: (Math.random() - 0.5) * 0.3,
vy: (Math.random() - 0.5) * 0.3,
r: Math.random() * 1.6 + 0.4,
});
}
}
function tick() {
ctx.clearRect(0, 0, w, h);
for (let i = 0; i < particles.length; i++) {
const a = particles[i];
a.x += a.vx; a.y += a.vy;
if (a.x < 0 || a.x > w) a.vx *= -1;
if (a.y < 0 || a.y > h) a.vy *= -1;
for (let j = i + 1; j < particles.length; j++) {
const b = particles[j];
const dx = a.x - b.x, dy = a.y - b.y;
const dist = Math.sqrt(dx*dx + dy*dy);
if (dist < 180) {
ctx.strokeStyle = `rgba(228, 0, 43, ${(1 - dist/180) * 0.35})`;
ctx.lineWidth = 0.6;
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.stroke();
}
}
ctx.fillStyle = 'rgba(255,255,255,0.65)';
ctx.beginPath();
ctx.arc(a.x, a.y, a.r, 0, Math.PI * 2);
ctx.fill();
}
requestAnimationFrame(tick);
}
resize(); init(); tick();
})();
/* ===========================================
CARDS — Mouse-Follow Highlight
=========================================== */
document.querySelectorAll('.pain-card').forEach(card => {
card.addEventListener('mousemove', (e) => {
const rect = card.getBoundingClientRect();
card.style.setProperty('--mx', ((e.clientX - rect.left) / rect.width * 100) + '%');
card.style.setProperty('--my', ((e.clientY - rect.top) / rect.height * 100) + '%');
});
});
/* ===========================================
COUNTER ANIMATION — zählt beim ersten Anzeigen der Slide
=========================================== */
function animateCounter(el) {
const target = parseFloat(el.dataset.count);
const isFloat = target % 1 !== 0;
const duration = 1600;
const start = performance.now();
function step(now) {
const p = Math.min((now - start) / duration, 1);
const eased = 1 - Math.pow(1 - p, 3);
const val = target * eased;
el.textContent = isFloat ? val.toFixed(1) : Math.round(val);
if (p < 1) requestAnimationFrame(step);
else el.textContent = isFloat ? target.toFixed(1) : target;
}
requestAnimationFrame(step);
}
/* ===========================================
SLIDE PRESENTATION CONTROLLER
=========================================== */
class SlidePresentation {
constructor() {
this.slides = document.querySelectorAll('.slide');
this.currentSlide = 0;
this.stage = document.getElementById('deckStage');
this.progress = document.getElementById('progressBar');
this.hud = document.getElementById('hudCounter');
this.setupStageScale();
this.setupKeyboardNav();
this.setupTouchNav();
this.setupWheelNav();
this.showSlide(0);
}
setupStageScale() {
const scale = () => {
const factor = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
const x = (window.innerWidth - 1920 * factor) / 2;
const y = (window.innerHeight - 1080 * factor) / 2;
this.stage.style.transform = `translate(${x}px, ${y}px) scale(${factor})`;
};
scale();
window.addEventListener('resize', scale);
}
setupKeyboardNav() {
document.addEventListener('keydown', (e) => {
if (e.target.getAttribute && e.target.getAttribute('contenteditable') === 'true') return;
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') {
e.preventDefault(); this.showSlide(this.currentSlide + 1);
} else if (e.key === 'ArrowLeft' || e.key === 'PageUp') {
e.preventDefault(); this.showSlide(this.currentSlide - 1);
} else if (e.key === 'Home') {
this.showSlide(0);
} else if (e.key === 'End') {
this.showSlide(this.slides.length - 1);
}
});
}
setupTouchNav() {
let startX = 0;
document.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; }, {passive: true});
document.addEventListener('touchend', (e) => {
const dx = e.changedTouches[0].clientX - startX;
if (Math.abs(dx) > 60) this.showSlide(this.currentSlide + (dx < 0 ? 1 : -1));
});
}
setupWheelNav() {
let cooldown = false;
document.addEventListener('wheel', (e) => {
if (cooldown) return;
if (Math.abs(e.deltaY) < 30) return;
cooldown = true;
this.showSlide(this.currentSlide + (e.deltaY > 0 ? 1 : -1));
setTimeout(() => cooldown = false, 900);
}, {passive: true});
}
showSlide(index) {
this.currentSlide = Math.max(0, Math.min(index, this.slides.length - 1));
this.slides.forEach((slide, i) => {
const active = i === this.currentSlide;
slide.classList.toggle('active', active);
slide.classList.toggle('visible', active);
if (active) {
slide.querySelectorAll('[data-count]').forEach(el => {
if (!el.dataset.animated) {
el.dataset.animated = 'true';
animateCounter(el);
}
});
}
});
const pct = (this.currentSlide + 1) / this.slides.length * 100;
this.progress.style.width = pct + '%';
this.hud.textContent =
String(this.currentSlide + 1).padStart(2, '0') + ' / ' +
String(this.slides.length).padStart(2, '0');
}
}
const deck = new SlidePresentation();
/* ===========================================
INLINE EDITOR — Live-Korrektur auf der Bühne (Taste E)
=========================================== */
const editor = {
isActive: false,
toggleEditMode() {
this.isActive = !this.isActive;
const toggle = document.getElementById('editToggle');
toggle.classList.toggle('active', this.isActive);
document.querySelectorAll(
'.slide h1, .slide h2, .slide p, .slide li, .slide .pain-title, .slide .pain-body, ' +
'.slide .feature-name, .slide .feature-desc, .slide .counter-label, .slide .counter-caption, ' +
'.slide .slide-lead, .slide .versus-headline, .slide .closing-headline'
).forEach(el => {
el.setAttribute('contenteditable', this.isActive ? 'true' : 'false');
});
if (!this.isActive) {
try {
localStorage.setItem('cancom-deck-edits', document.querySelector('.deck-stage').innerHTML);
} catch(_) {}
}
}
};
document.getElementById('editToggle').addEventListener('click', () => editor.toggleEditMode());
// Hotzone mit 400 ms Grace-Period
const hotzone = document.querySelector('.edit-hotzone');
const editToggleBtn = document.getElementById('editToggle');
let hideTimeout = null;
hotzone.addEventListener('mouseenter', () => {
clearTimeout(hideTimeout);
editToggleBtn.classList.add('show');
});
hotzone.addEventListener('mouseleave', () => {
hideTimeout = setTimeout(() => {
if (!editor.isActive) editToggleBtn.classList.remove('show');
}, 400);
});
editToggleBtn.addEventListener('mouseenter', () => clearTimeout(hideTimeout));
editToggleBtn.addEventListener('mouseleave', () => {
hideTimeout = setTimeout(() => {
if (!editor.isActive) editToggleBtn.classList.remove('show');
}, 400);
});
hotzone.addEventListener('click', () => editor.toggleEditMode());
document.addEventListener('keydown', (e) => {
if ((e.key === 'e' || e.key === 'E') &&
!(e.target.getAttribute && e.target.getAttribute('contenteditable') === 'true')) {
editor.toggleEditMode();
}
});
/* ===========================================
SLIDE-SPEZIFISCHES JS — hier anhängen
=========================================== */
</script>
</body>
</html>