Self-injecting script following impressum.js pattern: - data-tone attribute: playful | serious | minimal | none - Reads document.documentElement.lang for KI (de) vs AI (en) - MutationObserver on lang attr for i18n toggle compat - All tones link to msbls.de/ki - Injected into all 54 custom sites with data-tone="playful" - Template infra: base.html includes script, render.sh reads disclosure.tone - disclosure.tone added to 3 example site.yaml files Implements m/onepager#2
449 lines
14 KiB
HTML
449 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Warte bitte.</title>
|
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🫖</text></svg>">
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500&family=Newsreader:ital,wght@0,300;0,400;1,300;1,400&display=swap');
|
|
|
|
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
:root {
|
|
--bg: #faf8f5;
|
|
--text: #2a2520;
|
|
--text-soft: #6b6259;
|
|
--text-faint: #a69e94;
|
|
--accent: #c4a882;
|
|
--accent-soft: rgba(196, 168, 130, 0.15);
|
|
--line: rgba(196, 168, 130, 0.2);
|
|
}
|
|
|
|
html { scroll-behavior: smooth; }
|
|
|
|
body {
|
|
font-family: 'Newsreader', Georgia, serif;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
line-height: 1.8;
|
|
-webkit-font-smoothing: antialiased;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.page {
|
|
max-width: 540px;
|
|
margin: 0 auto;
|
|
padding: 120px 24px 80px;
|
|
}
|
|
|
|
/* Teapot */
|
|
.icon {
|
|
font-size: 2.8rem;
|
|
margin-bottom: 48px;
|
|
opacity: 0.8;
|
|
animation: steam 4s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes steam {
|
|
0%, 100% { transform: translateY(0); }
|
|
50% { transform: translateY(-4px); }
|
|
}
|
|
|
|
h1 {
|
|
font-family: 'Newsreader', Georgia, serif;
|
|
font-size: clamp(2.2rem, 5vw, 3rem);
|
|
font-weight: 400;
|
|
letter-spacing: -0.02em;
|
|
line-height: 1.2;
|
|
margin-bottom: 40px;
|
|
color: var(--text);
|
|
}
|
|
|
|
.rule {
|
|
width: 40px;
|
|
height: 1px;
|
|
background: var(--accent);
|
|
margin-bottom: 40px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.prose {
|
|
margin-bottom: 48px;
|
|
}
|
|
|
|
.prose p {
|
|
font-size: 1.08rem;
|
|
color: var(--text-soft);
|
|
font-weight: 300;
|
|
line-height: 1.9;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.prose p:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.prose em {
|
|
font-style: italic;
|
|
color: var(--text);
|
|
}
|
|
|
|
.highlight {
|
|
background: var(--accent-soft);
|
|
padding: 28px 32px;
|
|
border-radius: 12px;
|
|
border-left: 2px solid var(--accent);
|
|
margin: 48px 0;
|
|
}
|
|
|
|
.highlight p {
|
|
font-size: 1.05rem;
|
|
color: var(--text-soft);
|
|
font-weight: 300;
|
|
line-height: 1.8;
|
|
font-style: italic;
|
|
}
|
|
|
|
.closing {
|
|
margin-top: 56px;
|
|
padding-top: 40px;
|
|
border-top: 1px solid var(--line);
|
|
}
|
|
|
|
.closing p {
|
|
font-size: 1rem;
|
|
color: var(--text-faint);
|
|
font-weight: 300;
|
|
line-height: 1.8;
|
|
}
|
|
|
|
.breath-container {
|
|
text-align: center;
|
|
margin: 56px 0;
|
|
font-family: 'Inter', sans-serif;
|
|
}
|
|
|
|
.breath-circle {
|
|
width: 140px; height: 140px;
|
|
border-radius: 50%;
|
|
border: 2px solid var(--accent);
|
|
margin: 0 auto 24px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
transition: transform 0.1s ease, box-shadow 0.1s ease, border-color 0.3s;
|
|
cursor: pointer;
|
|
position: relative;
|
|
}
|
|
|
|
.breath-circle.active {
|
|
animation: breathe var(--cycle-duration, 8s) ease-in-out infinite;
|
|
}
|
|
|
|
.breath-circle .ring {
|
|
position: absolute; inset: -8px;
|
|
border-radius: 50%;
|
|
border: 1px solid transparent;
|
|
transition: border-color 0.5s;
|
|
}
|
|
|
|
.breath-circle.active .ring {
|
|
border-color: var(--accent-soft);
|
|
animation: ringPulse var(--cycle-duration, 8s) ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes breathe {
|
|
0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 var(--accent-soft); }
|
|
25% { transform: scale(1.35); box-shadow: 0 0 40px 10px var(--accent-soft); }
|
|
50% { transform: scale(1.35); box-shadow: 0 0 40px 10px var(--accent-soft); }
|
|
75% { transform: scale(1); box-shadow: 0 0 0 0 var(--accent-soft); }
|
|
}
|
|
|
|
@keyframes ringPulse {
|
|
0%, 100% { transform: scale(1); opacity: 0.3; }
|
|
25% { transform: scale(1.3); opacity: 0.6; }
|
|
50% { transform: scale(1.3); opacity: 0.6; }
|
|
75% { transform: scale(1); opacity: 0.3; }
|
|
}
|
|
|
|
.breath-label {
|
|
font-size: 0.85rem;
|
|
color: var(--text-faint);
|
|
letter-spacing: 0.15em;
|
|
font-weight: 300;
|
|
min-height: 1.4em;
|
|
transition: opacity 0.5s;
|
|
}
|
|
|
|
.breath-label.inhale { color: var(--accent); }
|
|
|
|
.breath-timer-select {
|
|
display: flex; gap: 8px; justify-content: center;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.breath-timer-select button {
|
|
font-family: 'Inter', sans-serif;
|
|
font-size: 0.72rem;
|
|
font-weight: 400;
|
|
color: var(--text-faint);
|
|
background: none;
|
|
border: 1px solid var(--line);
|
|
border-radius: 100px;
|
|
padding: 5px 14px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
.breath-timer-select button:hover { color: var(--text-soft); border-color: var(--accent); }
|
|
.breath-timer-select button.active { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
|
|
|
|
.breath-countdown {
|
|
font-family: 'Inter', sans-serif;
|
|
font-size: 0.72rem;
|
|
color: var(--text-faint);
|
|
margin-top: 12px;
|
|
font-weight: 300;
|
|
min-height: 1.2em;
|
|
}
|
|
|
|
.dots {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
margin: 48px 0;
|
|
}
|
|
|
|
.dots span {
|
|
width: 4px;
|
|
height: 4px;
|
|
border-radius: 50%;
|
|
background: var(--accent);
|
|
opacity: 0.4;
|
|
animation: pulse 3s ease-in-out infinite;
|
|
}
|
|
|
|
.dots span:nth-child(2) { animation-delay: 1s; }
|
|
.dots span:nth-child(3) { animation-delay: 2s; }
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 0.2; }
|
|
50% { opacity: 0.7; }
|
|
}
|
|
|
|
footer {
|
|
margin-top: 80px;
|
|
padding-top: 24px;
|
|
border-top: 1px solid var(--line);
|
|
text-align: center;
|
|
}
|
|
|
|
footer p {
|
|
font-family: 'Inter', sans-serif;
|
|
font-size: 0.72rem;
|
|
color: var(--text-faint);
|
|
font-weight: 300;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
/* Fade in */
|
|
@keyframes fadeUp {
|
|
from { opacity: 0; transform: translateY(16px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.page > * {
|
|
animation: fadeUp 1s ease forwards;
|
|
opacity: 0;
|
|
}
|
|
|
|
.page > *:nth-child(1) { animation-delay: 0.2s; }
|
|
.page > *:nth-child(2) { animation-delay: 0.5s; }
|
|
.page > *:nth-child(3) { animation-delay: 0.8s; }
|
|
.page > *:nth-child(4) { animation-delay: 1.1s; }
|
|
.page > *:nth-child(5) { animation-delay: 1.4s; }
|
|
.page > *:nth-child(6) { animation-delay: 1.7s; }
|
|
.page > *:nth-child(7) { animation-delay: 2.0s; }
|
|
.page > *:nth-child(8) { animation-delay: 2.3s; }
|
|
.page > *:nth-child(9) { animation-delay: 2.6s; }
|
|
|
|
@media (max-width: 540px) {
|
|
.page { padding: 80px 20px 60px; }
|
|
.highlight { padding: 20px 24px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="page">
|
|
|
|
<div class="icon">🫖</div>
|
|
|
|
<h1 data-de="Warte bitte." data-en="Please wait.">Warte bitte.</h1>
|
|
|
|
<div class="rule"></div>
|
|
|
|
<div class="prose">
|
|
<p data-de="Ich weiss, du wartest. Und Warten fuehlt sich manchmal an wie
|
|
Stillstand. Wie verlorene Zeit. Wie etwas, das nicht sein muesste." data-en="I know you're waiting. And waiting sometimes feels like
|
|
standing still. Like lost time. Like something that shouldn't have to be.">
|
|
Ich weiss, du wartest. Und Warten fuehlt sich manchmal an wie
|
|
Stillstand. Wie verlorene Zeit. Wie etwas, das nicht sein muesste.
|
|
</p>
|
|
<p data-de="Aber manche Dinge brauchen genau diese Zeit. Nicht weil jemand
|
|
troedelt, sondern weil <em>gute Ergebnisse nicht gehetzt werden koennen</em>.
|
|
Ein Gedanke muss reifen. Eine Loesung muss stimmen. Nicht nur schnell,
|
|
sondern richtig." data-en="But some things need exactly this time. Not because someone
|
|
is dawdling, but because <em>good results cannot be rushed</em>.
|
|
A thought must ripen. A solution must be right. Not just fast,
|
|
but correct.">
|
|
Aber manche Dinge brauchen genau diese Zeit. Nicht weil jemand
|
|
troedelt, sondern weil <em>gute Ergebnisse nicht gehetzt werden koennen</em>.
|
|
Ein Gedanke muss reifen. Eine Loesung muss stimmen. Nicht nur schnell,
|
|
sondern richtig.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="dots"><span></span><span></span><span></span></div>
|
|
|
|
<div class="highlight">
|
|
<p data-de="Zeit ist unser kostbarstes Gut. Wer sie gibt, gibt das Wertvollste,
|
|
was er hat. Und wer sie bekommt, sollte wissen: Da arbeitet jemand
|
|
daran, dass es sich lohnt." data-en="Time is our most precious asset. Whoever gives it, gives the most valuable
|
|
thing they have. And whoever receives it should know: someone is working
|
|
to make it worthwhile.">
|
|
Zeit ist unser kostbarstes Gut. Wer sie gibt, gibt das Wertvollste,
|
|
was er hat. Und wer sie bekommt, sollte wissen: Da arbeitet jemand
|
|
daran, dass es sich lohnt.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="prose">
|
|
<p data-de="Es ist okay, ungeduldig zu sein. Ungeduld zeigt, dass dir etwas
|
|
wichtig ist. Aber lass sie nicht den Blick trueben fuer das,
|
|
was gerade im Hintergrund passiert." data-en="It's okay to be impatient. Impatience shows that something matters to you.
|
|
But don't let it cloud your view of what's happening in the background right now.">
|
|
Es ist okay, ungeduldig zu sein. Ungeduld zeigt, dass dir etwas
|
|
wichtig ist. Aber lass sie nicht den Blick trueben fuer das,
|
|
was gerade im Hintergrund passiert.
|
|
</p>
|
|
<p data-de="Gute Dinge brauchen Raum. Einen ruhigen Moment.
|
|
Einen zweiten Blick. Manchmal einen dritten." data-en="Good things need space. A quiet moment.
|
|
A second look. Sometimes a third.">
|
|
Gute Dinge brauchen Raum. Einen ruhigen Moment.
|
|
Einen zweiten Blick. Manchmal einen dritten.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="breath-container">
|
|
<div class="breath-circle" id="breathCircle" onclick="toggleBreathing()">
|
|
<div class="ring"></div>
|
|
<span class="breath-label" id="breathLabel">start</span>
|
|
</div>
|
|
<div class="breath-timer-select">
|
|
<button onclick="setTimer(1)" id="t1" data-de="1 min" data-en="1 min">1 min</button>
|
|
<button onclick="setTimer(2)" id="t2" class="active" data-de="2 min" data-en="2 min">2 min</button>
|
|
<button onclick="setTimer(5)" id="t5" data-de="5 min" data-en="5 min">5 min</button>
|
|
<button onclick="setTimer(0)" id="t0" data-de="endlos" data-en="endless">endlos</button>
|
|
</div>
|
|
<div class="breath-countdown" id="countdown"></div>
|
|
</div>
|
|
|
|
<script>
|
|
let breathing = false;
|
|
let timer = null;
|
|
let duration = 120;
|
|
let remaining = 0;
|
|
let phaseTimer = null;
|
|
const cycle = 8000;
|
|
const phases = [
|
|
{ label: 'einatmen', duration: 2000, cls: 'inhale' },
|
|
{ label: 'halten', duration: 2000, cls: '' },
|
|
{ label: 'ausatmen', duration: 2000, cls: '' },
|
|
{ label: 'ruhe', duration: 2000, cls: '' },
|
|
];
|
|
|
|
function setTimer(min) {
|
|
duration = min * 60;
|
|
document.querySelectorAll('.breath-timer-select button').forEach(b => b.classList.remove('active'));
|
|
document.getElementById('t' + min).classList.add('active');
|
|
if (breathing) { stopBreathing(); startBreathing(); }
|
|
}
|
|
|
|
function toggleBreathing() {
|
|
if (breathing) stopBreathing(); else startBreathing();
|
|
}
|
|
|
|
function startBreathing() {
|
|
breathing = true;
|
|
remaining = duration;
|
|
const circle = document.getElementById('breathCircle');
|
|
circle.style.setProperty('--cycle-duration', cycle + 'ms');
|
|
circle.classList.add('active');
|
|
runPhases();
|
|
if (duration > 0) {
|
|
updateCountdown();
|
|
timer = setInterval(() => {
|
|
remaining--;
|
|
updateCountdown();
|
|
if (remaining <= 0) stopBreathing();
|
|
}, 1000);
|
|
}
|
|
}
|
|
|
|
function stopBreathing() {
|
|
breathing = false;
|
|
document.getElementById('breathCircle').classList.remove('active');
|
|
document.getElementById('breathLabel').textContent = 'start';
|
|
document.getElementById('breathLabel').className = 'breath-label';
|
|
document.getElementById('countdown').textContent = '';
|
|
clearInterval(timer);
|
|
clearTimeout(phaseTimer);
|
|
}
|
|
|
|
function runPhases() {
|
|
let i = 0;
|
|
function next() {
|
|
if (!breathing) return;
|
|
const p = phases[i % phases.length];
|
|
const label = document.getElementById('breathLabel');
|
|
label.textContent = p.label;
|
|
label.className = 'breath-label' + (p.cls ? ' ' + p.cls : '');
|
|
i++;
|
|
phaseTimer = setTimeout(next, p.duration);
|
|
}
|
|
next();
|
|
}
|
|
|
|
function updateCountdown() {
|
|
if (duration === 0) { document.getElementById('countdown').textContent = ''; return; }
|
|
const m = Math.floor(remaining / 60);
|
|
const s = remaining % 60;
|
|
document.getElementById('countdown').textContent = m + ':' + String(s).padStart(2, '0');
|
|
}
|
|
</script>
|
|
|
|
<div class="closing">
|
|
<p data-de="Du bekommst, was du brauchst. Nur nicht genau jetzt.
|
|
Und das ist voellig in Ordnung." data-en="You'll get what you need. Just not right now.
|
|
And that's perfectly fine.">
|
|
Du bekommst, was du brauchst. Nur nicht genau jetzt.
|
|
Und das ist voellig in Ordnung.
|
|
</p>
|
|
</div>
|
|
|
|
<footer>
|
|
<p>wartebitte.de</p>
|
|
<div style="text-align:center;margin-top:16px;">
|
|
<button data-i18n-toggle title="Maschinell übersetzt / Machine-translated — German is the original." style="background:none;border:1px solid var(--text-muted,#444);color:var(--text-muted,#444);font-size:0.65rem;letter-spacing:0.1em;padding:4px 12px;border-radius:4px;cursor:pointer;">EN</button>
|
|
<br><small data-de="Maschinell übersetzt" data-en="Machine-translated" style="color:var(--text-muted,#444);font-size:0.6rem;opacity:0.5;">Maschinell übersetzt</small>
|
|
</div>
|
|
</footer>
|
|
|
|
</div>
|
|
|
|
<script src="/shared/ai-disclosure.js" data-tone="playful"></script>
|
|
<script src="/shared/i18n.js"></script>
|
|
</body>
|
|
</html>
|