Newsletter-Modul mit Persönlichkeit
Die „Maschenpost“ einer Flensburger Strickmarke: benannter Newsletter mit konkretem Vorteil, Knopf „Anschlagen“, Status-Roll und einer Maschenreihe als Bestätigung.
D2C, Mode, Manufaktur: ein benannter Newsletter mit drei konkreten Vorteilen, eigener Knopftext mit Status-Roll und eine kleine Erfolgsszene; im Footer oder als Overlay.
Ein Newsletter-Feld im Footer oder direkt darüber, mit eigenem Namen und eigenem Ton. Ein einzelnes E-Mail-Feld mit Knopf in derselben Zeile, der Einwilligungshinweis passt in eine Zeile. Erfolg und Fehler erscheinen als animierter Textwechsel im Knopf oder als kleine Meldung. Manche Seiten öffnen das Modul als Overlay aus einem Link im Footer.
Ein benannter Newsletter wirkt wie ein Produkt und nicht wie eine Pflicht. Die Mikrotexte tragen die Markenstimme bis in Knopf und Fehlermeldung.
Wann einsetzen
Für Marken mit regelmäßigen Inhalten: Shops mit neuen Kollektionen, Studios mit Journal, Manufakturen.
Wann nicht
Wenn gar kein Newsletter verschickt wird. Wenn der Vorteil nicht konkret benannt werden kann.
Varianten
- Benannter Newsletter mit eigenem Titel und konkretem Vorteil
- Verspielter Knopftext, der den Status per Roll wechselt
- Overlay, das aus einem Link im Footer aufgeht
- Newsletter im Menü-Overlay
- Programmauswahl (Kultur): Sparten und Takt wählen, eine Vorschau des nächsten Briefs filtert live mit
- Saisonbrief (Gastro, Hotel): vier Briefe im Jahr als Kacheln, der nächste ist markiert, im dunklen Footer neben Öffnungszeiten
Umsetzung
Ein <form> mit <label> (darf visuell versteckt sein), type=email, autocomplete=email und required. Inline-Validierung erst nach Blur, danach bei jeder Eingabe. Der Status wechselt im Knopf per Text-Roll, das Ergebnis steht zusätzlich in einem Bereich mit aria-live=polite. Double-Opt-In serverseitig (DSGVO). Die Knopfbreite auf den längsten Statustext festlegen. Fallstrick: Ein Placeholder ersetzt kein Label.
Robust umsetzen
Telefon. Feld und Knopf untereinander, oder der Knopf als Icon im Feld. inputmode=email.
Ohne Bewegung. Status ohne Roll wechseln. Erfolgsanimationen in der Endlage zeigen.
Barrierefrei. Echtes Label. Fehlermeldung über aria-describedby am Feld, aria-invalid setzen. Erfolgsmeldung per aria-live. Overlay als <dialog> mit showModal, Esc und Schließen-Knopf.
Code
<!DOCTYPE html>
<!--
Newsletter-Modul mit Persönlichkeit
Fundus · Footer · https://cecodesign.de/lab/fundus/footer/footer-newsletter-mit-persoenlichkeit/
Eigenständige Datei. Bilder liegen hier unter bilder/ – eigene einsetzen.
-->
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Newsletter-Modul mit Persönlichkeit · Fundus-Demo</title>
<script>document.documentElement.classList.add('js');if(matchMedia('(prefers-reduced-motion: reduce)').matches)document.documentElement.classList.add('reduziert')</script>
<style>
@font-face { font-family: 'Instrument Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-sans:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 400 700; font-display: swap; }
@font-face { font-family: 'Young Serif'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/young-serif@latest/latin-400-normal.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Schibsted Grotesk'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/schibsted-grotesk:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 400 900; font-style: normal; font-display: swap; }
@font-face { font-family: 'Atkinson Hyperlegible Next'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/atkinson-hyperlegible-next:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 200 800; font-display: swap; }
@font-face { font-family: 'IBM Plex Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/ibm-plex-mono@latest/latin-500-normal.woff2') format('woff2'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'Fraunces'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/fraunces:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-style: normal; font-display: swap; }
@font-face { font-family: 'Fraunces'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/fraunces:vf@latest/latin-wght-italic.woff2') format('woff2-variations'); font-weight: 100 900; font-style: italic; font-display: swap; }
@font-face { font-family: 'Commissioner'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/commissioner:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }
@font-face { font-family: 'Geist Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/geist-mono:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }
:root {
--grund: #f5f4f1; --kachel: #ece9e2; --tinte: #212121; --leise: #666666; --linie: rgba(33, 33, 33, .14);
--gruen: #10714e; --akzent: #d4528d; --papier: #fffdf8;
--auf: cubic-bezier(0, 0, 0.2, 1); /* Öffnen, Einblenden: Ausklang */
--zu: cubic-bezier(0.4, 0, 1, 1); /* Schließen, Ausblenden: Anlauf */
--rand: clamp(16px, 3vw, 40px);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--grund); color: var(--tinte); font-family: 'Instrument Sans', system-ui, sans-serif; font-size: 100%; -webkit-font-smoothing: antialiased; }
body { overflow-x: clip; }
a { color: inherit; text-decoration: none; }
button, input { font: inherit; color: inherit; }
.label { font-size: .75rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }
.weg { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
:focus-visible { outline: 2px solid var(--tinte); outline-offset: 3px; }
/* ---------- Demo-Pult (nicht Teil der erfundenen Seite) ---------- */
.pult { position: fixed; right: var(--rand); top: 12px; z-index: 40; max-width: 560px; display: flex; align-items: center; gap: 6px 12px; flex-wrap: wrap; justify-content: flex-end; padding: 6px 6px 6px 14px; border-radius: 16px; background: rgba(33, 33, 33, .9); color: rgba(245, 244, 241, .6); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
font: 500 10.5px/1.3 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; }
.pult b { color: #f5f4f1; font-weight: 500; }
.pult .gruppe { display: flex; gap: 3px; }
.pult button { font: inherit; letter-spacing: inherit; text-transform: inherit; color: rgba(245, 244, 241, .7); background: none; border: 1px solid rgba(245, 244, 241, .18); border-radius: 999px; height: 28px; padding: 0 10px; cursor: pointer; transition: color .15s var(--auf), background-color .15s var(--auf); }
.pult button:hover { color: #f5f4f1; }
.pult button[aria-pressed='true'] { background: #f5f4f1; color: var(--tinte); border-color: #f5f4f1; }
.pult :focus-visible { outline-color: #f5f4f1; }
/* ---------- Ende der Shopseite ---------- */
.vertrauen { display: flex; flex-wrap: wrap; gap: 10px 32px; min-height: 60px; align-items: center; padding: 12px var(--rand); border-bottom: 1px solid var(--linie); font-size: .9375rem; }
@media (min-width: 1200px) { .vertrauen { padding-right: calc(var(--rand) + 600px); } }
.vertrauen li { display: flex; align-items: center; gap: 10px; list-style: none; }
.vertrauen svg { flex: none; width: 22px; height: 22px; }
/* ---------- Newsletter-Modul ---------- */
.post { position: relative; display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: clamp(20px, 4vw, 64px); align-items: center; margin: clamp(16px, 2.4vw, 32px) var(--rand); padding: clamp(24px, 4vw, 60px); background: var(--kachel); border-radius: 20px; overflow: hidden; }
.post .label { color: var(--leise); display: flex; gap: 10px; align-items: center; }
.post .label .nr { flex: none; white-space: nowrap; color: var(--tinte); background: var(--papier); border-radius: 999px; padding: 4px 9px; }
.claim { font-family: 'Young Serif', Georgia, serif; font-weight: 400; font-size: clamp(2.25rem, 1.4rem + 3vw, 4.25rem); line-height: 1.05; letter-spacing: -.01em; margin: 16px 0 20px; text-wrap: balance; }
.claim em { display: block; font-style: normal; color: var(--akzent); }
.vorteile { list-style: none; display: grid; gap: 6px; margin-bottom: 26px; font-size: 1.0625rem; line-height: 1.45; }
.vorteile li { display: flex; gap: 10px; align-items: baseline; }
.vorteile li::before { content: ''; flex: none; width: 14px; height: 8px; border-left: 2px solid var(--gruen); border-bottom: 2px solid var(--gruen); transform: translateY(-3px) rotate(-45deg); }
.formular { max-width: 560px; }
.feldzeile { display: flex; gap: 6px; padding: 6px; background: var(--papier); border: 1.5px solid transparent; border-radius: 999px; transition: border-color .15s var(--auf), box-shadow .15s var(--auf); }
.feldzeile:focus-within, .feldzeile.fokus { border-color: var(--tinte); box-shadow: 0 0 0 4px rgba(33, 33, 33, .08); }
.feldzeile.fehlerhaft { border-color: var(--akzent); }
.feldzeile input { flex: 1; min-width: 0; border: 0; background: none; padding: 0 16px; font-size: 1rem; height: 48px; outline: none; }
.feldzeile input::placeholder { color: #9a978f; }
.senden { position: relative; flex: none; height: 48px; padding: 0 22px; border: 0; border-radius: 999px; background: var(--tinte); color: var(--grund); font-weight: 600; font-size: 1rem; cursor: pointer; overflow: hidden; transition: background-color .15s var(--auf), transform .15s var(--auf); }
.senden:hover { background: #000; }
.senden:active, .senden.druck { transform: scale(.97); }
.senden.fertig { background: var(--gruen); }
.senden .rolle { position: relative; display: block; height: 1.3em; line-height: 1.3; overflow: hidden; }
.senden .rolle span { display: block; white-space: nowrap; text-align: center; }
.senden .rolle span + span { position: absolute; inset: 0; }
.senden .icon { display: none; }
.zeile-unter { display: flex; justify-content: space-between; gap: 12px; min-height: 1.5em; margin-top: 10px; padding: 0 18px; font-size: .875rem; color: var(--leise); line-height: 1.45; }
.fehler { color: #b3245f; font-weight: 500; }
.fehler[hidden] { display: none; }
.hinweis a { text-decoration: underline; text-underline-offset: 2px; }
.status { margin-top: 12px; padding: 0 18px; font-size: 1rem; line-height: 1.45; max-width: 46ch; }
.status:empty { margin-top: 0; }
.status .nochmal { background: none; border: 0; padding: 0; text-decoration: underline; text-underline-offset: 2px; color: var(--leise); cursor: pointer; }
/* Masche für Masche: Erfolgsreihe */
.maschen { display: none; gap: 2px; height: 14px; margin: 14px 18px 0; overflow: hidden; }
.maschen.an { display: flex; }
.maschen i { flex: none; width: 12px; height: 14px; opacity: 0; transform: translateY(6px); }
.maschen i svg { display: block; width: 100%; height: 100%; }
.maschen.an i { animation: masche .3s var(--auf) forwards; animation-delay: calc(var(--n) * 24ms); }
@keyframes masche { to { opacity: 1; transform: none; } }
/* Illustration: Umschlag mit Socke */
.bild { position: relative; justify-self: center; width: min(100%, 440px); aspect-ratio: 1; }
.bild svg { width: 100%; height: 100%; overflow: visible; }
.socke { transition: transform .4s var(--zu); }
.post:not(.erfolg) .bild:hover .socke { transform: translateY(-14px) rotate(-3deg); transition: transform .3s var(--auf); }
.erfolg .socke { transform: translateY(340px); transition: transform .4s var(--zu); }
.klappe-hinten { transform-box: fill-box; transform-origin: 50% 100%; transition: transform .3s var(--auf) .2s; }
.erfolg .klappe-hinten { transform: scaleY(0); transition: transform .15s var(--zu) .3s; }
.klappe { transform-box: fill-box; transform-origin: 50% 0; transform: scaleY(0); transition: transform .2s var(--zu); }
.erfolg .klappe { transform: scaleY(1); transition: transform .3s var(--auf) .45s; }
.stempel { transform-box: fill-box; transform-origin: 50% 50%; opacity: 0; transform: scale(1.4) rotate(-14deg); }
.erfolg .stempel { opacity: 1; transform: scale(1) rotate(-14deg); transition: opacity .2s var(--auf) .75s, transform .3s var(--auf) .75s; }
/* Variante Overlay: der Footer zeigt nur eine Zeile, das Modul geht als Dialog von unten auf */
.anstoss { display: none; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 14px 24px; margin: 24px var(--rand); padding: 20px 24px; border-radius: 16px; background: var(--kachel); }
.anstoss p { font-family: 'Young Serif', Georgia, serif; font-size: clamp(1.25rem, 1rem + 1vw, 1.75rem); line-height: 1.2; }
.anstoss p em { font-style: normal; color: var(--akzent); }
.anstoss button { height: 48px; padding: 0 22px; border: 0; border-radius: 999px; background: var(--tinte); color: var(--grund); font-weight: 600; cursor: pointer; }
.mit-overlay .anstoss { display: flex; }
.mit-overlay .fuss > .post { display: none; }
dialog.ueberlage { position: fixed; inset: auto 0 0; width: min(1080px, calc(100% - 2 * var(--rand))); max-width: none; max-height: calc(100svh - 24px); margin: 0 auto; padding: 0; border: 0; background: none; overflow: auto; }
dialog.ueberlage::backdrop { background: rgba(33, 33, 33, 0); transition: background-color .2s var(--zu); }
dialog.ueberlage.offen::backdrop { background: rgba(33, 33, 33, .42); transition: background-color .3s var(--auf); }
dialog.ueberlage .post { margin: 0 0 12px; transform: translateY(105%); transition: transform .2s var(--zu); box-shadow: 0 30px 80px rgba(0, 0, 0, .25); }
dialog.ueberlage.offen .post { transform: none; transition: transform .3s var(--auf); }
.schliessen { display: none; position: absolute; top: 14px; right: 14px; z-index: 2; width: 40px; height: 40px; border-radius: 50%; border: 0; background: var(--papier); cursor: pointer; font-size: 20px; line-height: 1; }
dialog .schliessen { display: block; }
/* ---------- Footer-Spalten ---------- */
.spalten { display: grid; grid-template-columns: 1.3fr repeat(3, minmax(0, 1fr)) 1.1fr; gap: 28px clamp(16px, 2.4vw, 40px); padding: clamp(24px, 3vw, 44px) var(--rand); font-size: .9375rem; line-height: 1.5; }
.spalten h3 { color: var(--leise); margin-bottom: 12px; }
.spalten ul { list-style: none; display: grid; gap: 4px; }
.spalten a { transition: color .15s var(--auf); }
.spalten a:hover { color: var(--akzent); }
.marke { font-family: 'Young Serif', Georgia, serif; font-size: 1.75rem; line-height: 1; margin-bottom: 12px; }
.marke-satz { color: var(--leise); max-width: 28ch; }
.lager { display: inline-flex; align-items: center; gap: 8px; margin-top: 14px; font-size: .875rem; }
.lager::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--gruen); }
.zahlung { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; padding: 0 var(--rand) 22px; }
.zahlung .label { color: var(--leise); margin-right: 6px; }
.zahlung span.chip { font-size: .8125rem; font-weight: 600; padding: 5px 10px; border: 1px solid var(--linie); border-radius: 6px; background: #fff; }
.recht { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px 24px; margin: 0 var(--rand); padding: 16px 0 72px; border-top: 1px solid var(--linie); font-size: .8125rem; color: var(--leise); }
.recht nav { display: flex; flex-wrap: wrap; gap: 6px 18px; }
.recht a:hover { color: var(--tinte); }
/* Gedachter Zeiger der Vorführung */
.zeiger { position: fixed; left: 0; top: 0; z-index: 60; width: 22px; height: 22px; margin: -11px 0 0 -11px; border: 1.5px solid var(--tinte); border-radius: 50%; background: rgba(33, 33, 33, .1); pointer-events: none; opacity: 0; transition: opacity .2s var(--auf), transform .5s var(--auf); }
.zeiger.an { opacity: 1; }
/* Reduzierte Bewegung: Status ohne Roll, Maschen sofort da */
:root.reduziert *, :root.reduziert ::backdrop { transition-duration: .01ms !important; transition-delay: 0s !important; animation-duration: .01ms !important; animation-delay: 0s !important; }
@media (max-width: 1020px) { .spalten { grid-template-columns: repeat(3, minmax(0, 1fr)); } .spalten .wir, .spalten .kontakt { grid-column: span 3; } }
@media (max-width: 760px) {
.post { grid-template-columns: minmax(0, 1fr); }
.bild { order: -1; width: min(62%, 260px); margin: 18px 0 -12px; }
.vertrauen { flex-direction: column; align-items: flex-start; gap: 6px; padding-right: 150px; }
}
@media (max-width: 520px) {
.feldzeile { flex-direction: column; border-radius: 18px; }
.feldzeile input { flex: none; height: 50px; }
.senden { width: 100%; height: 50px; }
.zeile-unter, .status { padding: 0 6px; }
.maschen { margin-left: 6px; }
.spalten { grid-template-columns: 1fr 1fr; }
.spalten .wir, .spalten .kontakt { grid-column: span 2; }
}
@media (hover: none) { .post:not(.erfolg) .bild:hover .socke { transform: none; } }
/* =========================================================================
Varianten (Regel 13): ?v=basis | kultur | saison
========================================================================= */
html[data-v="kultur"] [data-welt]:not([data-welt="kultur"]), html:not([data-v="kultur"]) [data-welt="kultur"], html[data-v="saison"] [data-welt]:not([data-welt="saison"]), html:not([data-v="saison"]) [data-welt="saison"] { display: none !important; }
html[data-v="kultur"] { background: #efede9; }
html[data-v="saison"] { background: #f0ece0; }
html[data-v]:not([data-v="basis"]) [data-teil="basis"] { display: none !important; }
/* Gemeinsam: Knopf mit Status-Roll, Feld mit Validierung nach Blur */
.nl-feld { display: flex; gap: 6px; padding: 5px; border: 1.5px solid currentColor; transition: box-shadow .2s var(--k), border-color .2s var(--k); }
.nl-feld input { flex: 1; min-width: 0; height: 48px; padding: 0 14px; border: 0; background: none; font-size: 1.0625rem; outline: none; }
.nl-feld:focus-within, .nl-feld.fokus { box-shadow: 0 0 0 4px rgba(127, 127, 127, .22); }
.nl-feld.fehlerhaft { border-color: var(--fehler); }
.nl-knopf { position: relative; flex: none; height: 48px; padding: 0 22px; border: 0; cursor: pointer; overflow: hidden; font-weight: 600; font-size: 1rem; transition: background-color .2s var(--k), transform .15s var(--k); }
.nl-knopf:active, .nl-knopf.druck { transform: scale(.97); }
.nl-knopf .rolle { position: relative; display: block; height: 1.3em; line-height: 1.3; overflow: hidden; }
.nl-knopf .rolle span { display: block; white-space: nowrap; text-align: center; }
.nl-knopf .rolle span + span { position: absolute; inset: 0; }
.nl-unter { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 4px 16px; min-height: 1.5em; margin-top: 10px; font-size: .875rem; line-height: 1.45; }
.nl-fehler { color: var(--fehler); font-weight: 600; }
.nl-fehler[hidden] { display: none; }
.nl-status { margin-top: 12px; max-width: 52ch; line-height: 1.5; }
.nl-status:empty { display: none; }
.nl-status button { background: none; border: 0; padding: 0; color: inherit; text-decoration: underline; text-underline-offset: 2px; cursor: pointer; opacity: .75; }
/* ---------- Kultur: Wochenzettel mit Programmauswahl (Rezept kultur-programm-haltung) ---------- */
.ku { --k: cubic-bezier(0.215, 0.61, 0.355, 1); --tinte: #0b0b0b; --off: #efede9; --fehler: #b3261e; --linie: rgba(11, 11, 11, .16);
min-height: 100svh; display: flex; flex-direction: column; background: var(--off); color: var(--tinte); font-family: 'Atkinson Hyperlegible Next', system-ui, sans-serif; }
.ku :focus-visible { outline: 3px solid var(--tinte); outline-offset: 3px; }
.ku-kopf { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; padding: 18px var(--rand); border-bottom: 2px solid var(--tinte); font: 500 .8125rem/1.3 'IBM Plex Mono', ui-monospace, monospace; }
.ku-kopf b { font: 800 1.25rem/1 'Schibsted Grotesk', sans-serif; letter-spacing: -.02em; }
.ku-modul { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: clamp(28px, 5vw, 72px); padding: clamp(32px, 5vw, 64px) var(--rand) clamp(36px, 5vw, 64px); }
.ku-label { font: 500 .8125rem/1.3 'IBM Plex Mono', monospace; font-variant-numeric: tabular-nums; }
.ku h2 { margin: 14px 0 18px; font: 800 clamp(2.5rem, 1.2rem + 4.2vw, 5rem)/.98 'Schibsted Grotesk', sans-serif; letter-spacing: -.02em; text-wrap: balance; }
.ku-text { font-size: 1.125rem; line-height: 1.6; max-width: 46ch; }
.ku fieldset { border: 0; padding: 0; margin: 26px 0 0; min-width: 0; }
.ku legend { padding: 0; margin-bottom: 10px; font: 700 1rem/1.3 'Schibsted Grotesk', sans-serif; }
.ku-sparten { display: flex; flex-wrap: wrap; gap: 8px; }
.sparte { display: inline-flex; align-items: center; gap: 9px; height: 42px; padding: 0 14px 0 10px; border: 2px solid var(--tinte); background: transparent; color: var(--tinte); font: 600 .9375rem/1 'Atkinson Hyperlegible Next', sans-serif; cursor: pointer; transition: background-color .2s var(--k), color .2s var(--k), border-color .2s var(--k); }
.sparte i { width: 14px; height: 14px; background: var(--farbe); box-shadow: inset 0 0 0 2px var(--off); transition: box-shadow .2s var(--k); }
.sparte small { font: 500 .75rem/1 'IBM Plex Mono', monospace; opacity: .7; }
.sparte[aria-pressed="true"] { background: var(--farbe); border-color: var(--farbe); color: #fff; }
.sparte[aria-pressed="true"] i { box-shadow: inset 0 0 0 2px #fff; background: #fff; }
.sparte[aria-pressed="true"] small { opacity: .9; }
.ku-takt { display: inline-flex; border: 2px solid var(--tinte); }
.ku-takt button { height: 40px; padding: 0 14px; border: 0; background: none; color: var(--tinte); font: 600 .9375rem/1 'Atkinson Hyperlegible Next', sans-serif; cursor: pointer; transition: background-color .2s var(--k), color .2s var(--k); }
.ku-takt button[aria-pressed="true"] { background: var(--tinte); color: var(--off); }
.ku form { margin-top: 26px; max-width: 560px; }
.ku .nl-feld { background: #fff; border-width: 2px; }
.ku .nl-knopf { background: var(--tinte); color: var(--off); }
.ku .nl-knopf.fertig { background: #2f7d4a; }
.ku .nl-unter { color: rgba(11, 11, 11, .7); }
.ku-vorschau { align-self: start; border: 2px solid var(--tinte); background: #fff; }
.ku-vorschau header { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 14px 18px; border-bottom: 2px solid var(--tinte); }
.ku-vorschau header b { font: 800 1.125rem/1.2 'Schibsted Grotesk', sans-serif; }
.ku-liste { list-style: none; margin: 0; padding: 0; }
.ku-liste li { display: grid; grid-template-columns: 6.6em minmax(0, 1fr); gap: 4px 14px; padding: 12px 18px 12px 14px; border-bottom: 1px solid var(--linie); border-left: 6px solid var(--farbe); overflow: hidden; transition: opacity .3s var(--k); }
.ku-liste li.weg { display: none; }
.ku-liste time { font: 500 .8125rem/1.45 'IBM Plex Mono', monospace; font-variant-numeric: tabular-nums; }
.ku-liste strong { display: block; font: 700 1rem/1.3 'Schibsted Grotesk', sans-serif; }
.ku-liste span { font-size: .875rem; color: rgba(11, 11, 11, .68); }
.ku-leer { padding: 22px 18px; font-size: 1rem; }
.ku-leer[hidden] { display: none; }
.ku-vorschau footer { display: flex; justify-content: space-between; gap: 12px; padding: 12px 18px; font: 500 .8125rem/1.3 'IBM Plex Mono', monospace; }
.ku-stempel { padding: 3px 8px; background: #2f7d4a; color: #fff; opacity: 0; transform: translateY(6px); transition: opacity .3s var(--k), transform .3s var(--k); }
.ku-vorschau.abonniert .ku-stempel { opacity: 1; transform: none; }
.ku-fuss { margin-top: auto; display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px 24px; padding: 16px var(--rand) 22px; border-top: 2px solid var(--tinte); font-size: .875rem; }
.ku-fuss nav { display: flex; flex-wrap: wrap; gap: 6px 18px; }
.ku-fuss a { text-decoration: underline; text-underline-offset: 2px; }
/* ---------- Gastro: Saisonbrief im dunklen Footer (Rezept gastro-warm-appetitlich) ---------- */
.ga { --k: cubic-bezier(0.16, 1, 0.3, 1); --papier: #f0ece0; --wald: #293e3a; --salbei: #7f937a; --senf: #eac16e; --rose: #e1a894; --tinte: #353839; --fehler: #f0a08a;
min-height: 100svh; display: flex; flex-direction: column; background: var(--papier); color: var(--tinte); font-family: 'Commissioner', system-ui, sans-serif; font-variation-settings: 'FLAR' 0; }
.ga :focus-visible { outline: 2px solid var(--senf); outline-offset: 3px; }
.ga-band { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 14px 24px; padding: clamp(26px, 4vw, 44px) var(--rand); }
.ga-band p { font: 450 clamp(1.5rem, 1rem + 2vw, 2.5rem)/1.1 'Fraunces', Georgia, serif; font-variation-settings: 'SOFT' 100; letter-spacing: -.01em; max-width: 24ch; }
.ga-band a { display: inline-flex; align-items: center; height: 48px; padding: 0 22px; border-radius: 999px; background: var(--wald); color: var(--papier); font-weight: 600; }
.ga-fuss { flex: 1; background: var(--wald); color: var(--papier); padding: clamp(36px, 5vw, 64px) var(--rand) 22px; border-radius: 28px 28px 0 0; }
.ga-raster { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, .8fr) minmax(0, .8fr); gap: clamp(28px, 4vw, 64px); }
.ga-rubrik { font: 600 .75rem/1.2 'Commissioner', sans-serif; font-variation-settings: 'FLAR' 80; letter-spacing: .15em; text-transform: uppercase; color: var(--senf); }
.ga h2 { margin: 14px 0 12px; font: 450 clamp(2.25rem, 1.4rem + 3vw, 3.75rem)/1.02 'Fraunces', Georgia, serif; font-variation-settings: 'SOFT' 100; letter-spacing: -.01em; text-wrap: balance; }
.ga h2 em { font-style: italic; color: var(--rose); }
.ga-text { font-size: 1.0625rem; line-height: 1.55; max-width: 48ch; color: rgba(240, 236, 224, .82); }
.ga-jahr { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin: 22px 0 22px; }
.ga-jahr li { position: relative; display: grid; align-content: start; gap: 4px; padding: 12px 12px 14px; border-radius: 14px; background: rgba(240, 236, 224, .06); border: 1px solid rgba(240, 236, 224, .14); font-size: .875rem; line-height: 1.35; color: rgba(240, 236, 224, .78); transition: background-color .2s var(--k), border-color .2s var(--k); }
.ga-jahr li svg { width: 34px; height: 34px; margin-bottom: 4px; }
.ga-jahr li b { font: 500 1.0625rem/1.2 'Fraunces', serif; color: var(--papier); }
.ga-jahr li.naechster { background: var(--senf); border-color: var(--senf); color: var(--tinte); }
.ga-jahr li.naechster b { color: var(--tinte); }
.ga-marke { justify-self: start; margin-top: 6px; padding: 3px 8px; border-radius: 999px; background: var(--wald); color: var(--senf); font: 600 .6875rem/1.2 'Commissioner', sans-serif; font-variation-settings: 'FLAR' 80; letter-spacing: .1em; text-transform: uppercase; font-style: normal; }
.ga-jahr .haken { position: absolute; top: 10px; right: 10px; width: 22px; height: 22px; border-radius: 50%; background: var(--papier); color: var(--wald); display: grid; place-items: center; opacity: 0; transform: scale(.6); }
.ga-jahr .haken svg { width: 12px; height: 12px; margin: 0; }
.ga-fuss.vorgemerkt .ga-jahr .haken { opacity: 1; transform: none; transition: opacity .7s var(--k), transform .7s var(--k); transition-delay: calc(var(--n) * 120ms); }
.ga form { max-width: 560px; }
.ga .nl-feld { border-color: rgba(240, 236, 224, .5); border-radius: 999px; background: rgba(240, 236, 224, .06); }
.ga .nl-feld:focus-within { border-color: var(--papier); }
.ga .nl-feld input { color: var(--papier); }
.ga .nl-feld input::placeholder { color: rgba(240, 236, 224, .5); }
.ga .nl-knopf { border-radius: 999px; background: var(--senf); color: var(--tinte); }
.ga .nl-knopf:hover { background: #f1cf8a; }
.ga .nl-knopf.fertig { background: var(--papier); }
.ga .nl-unter, .ga .nl-unter a { color: rgba(240, 236, 224, .7); }
.ga .nl-unter a { text-decoration: underline; text-underline-offset: 2px; }
.ga-spalte h3 { margin-bottom: 12px; }
.ga-spalte dl { display: grid; grid-template-columns: auto 1fr; gap: 6px 16px; font-size: .9375rem; font-variant-numeric: tabular-nums; }
.ga-spalte dt { color: rgba(240, 236, 224, .7); }
.ga-spalte p, .ga-spalte a { font-size: .9375rem; line-height: 1.55; }
.ga-spalte a { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: rgba(240, 236, 224, .4); }
.ga-unten { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px 24px; margin-top: clamp(36px, 5vw, 56px); padding-top: 16px; border-top: 1px solid rgba(240, 236, 224, .16); font-size: .8125rem; color: rgba(240, 236, 224, .62); }
.ga-unten nav { display: flex; gap: 6px 18px; flex-wrap: wrap; }
@media (max-width: 900px) {
.ku-modul { grid-template-columns: minmax(0, 1fr); }
.ga-raster { grid-template-columns: 1fr 1fr; }
.ga-raster > :first-child { grid-column: 1 / -1; }
}
@media (max-width: 560px) {
.nl-feld { flex-direction: column; }
.nl-feld input { flex: none; height: 50px; }
.nl-knopf { width: 100%; height: 50px; }
.ga .nl-feld { border-radius: 22px; }
.ga-jahr { grid-template-columns: 1fr 1fr; }
.ga-raster { grid-template-columns: 1fr; }
.ku-kopf span { display: none; }
.ku-liste li { grid-template-columns: 1fr; }
.ga-band { padding-top: 64px; }
}
@media (hover: none) { .sparte, .ku-takt button { min-height: 44px; } }
.vorschau .ku-fuss, .vorschau .ku-kopf, .vorschau .ga-band, .vorschau .ga-spalte, .vorschau .ga-unten { display: none; }
.vorschau .ga-raster { grid-template-columns: 1fr; }
.vorschau .ga-fuss { border-radius: 0; }
/* Vorschau in der Galerie: nur das Modul */
.vorschau body { overflow: hidden; }
.vorschau .vertrauen { display: none; }
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
</style>
</head>
<body>
<div class="pult">
<span data-werte></span>
<div class="gruppe" role="group" aria-label="Variante">
<button type="button" data-welt-wahl="basis" aria-pressed="true">Shop</button>
<button type="button" data-welt-wahl="kultur" aria-pressed="false">Kultur</button>
<button type="button" data-welt-wahl="saison" aria-pressed="false">Gastro</button>
</div>
<div class="gruppe" role="group" aria-label="Ort des Moduls" data-teil="basis">
<button type="button" data-variante="footer" aria-pressed="true">Im Footer</button>
<button type="button" data-variante="overlay" aria-pressed="false">Overlay</button>
</div>
<div class="gruppe" role="group" aria-label="Mikrotexte" data-teil="basis">
<button type="button" data-ton="marke" aria-pressed="true">Markenstimme</button>
<button type="button" data-ton="neutral" aria-pressed="false">Neutral</button>
</div>
</div>
<ul class="vertrauen" aria-label="Unsere Versprechen" data-welt="basis">
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="M3 7h11v9H3zM14 10h4l3 3v3h-7z"/><circle cx="7" cy="17.5" r="1.7" fill="#f5f4f1"/><circle cx="17" cy="17.5" r="1.7" fill="#f5f4f1"/></svg>Ab 49 € versandkostenfrei</li>
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="M4 12a8 8 0 1 0 3-6.2M4 4v4h4"/></svg>30 Tage Rückgabe</li>
<li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="M12 3c3 3 6 4 6 9a6 6 0 0 1-12 0c0-5 3-6 6-9z"/></svg>Wolle aus Angeln</li>
</ul>
<footer class="fuss" data-welt="basis">
<section class="post" data-post aria-labelledby="post-titel">
<button type="button" class="schliessen" data-schliessen aria-label="Schließen">×</button>
<div class="post-text">
<p class="label"><span class="nr" data-nr>Nr. 38</span><span data-t="unterzeile">Maschenpost · erscheint am 1. Dezember</span></p>
<h2 class="claim" id="post-titel" data-t="claim">Post von der Förde. <em>Einmal im Monat, nie öfter.</em></h2>
<ul class="vorteile" data-t="vorteile">
<li>Neue Farben drei Tage, bevor sie im Shop sind</li>
<li>Jede Ausgabe eine Strickanleitung zum Selbermachen</li>
<li>10 % auf deine nächste Bestellung</li>
</ul>
<form class="formular" data-form novalidate>
<label class="weg" for="post-mail" data-t="feldlabel">Deine E-Mail-Adresse für die Maschenpost</label>
<div class="feldzeile" data-feldzeile>
<input id="post-mail" name="email" type="email" inputmode="email" autocomplete="email" required placeholder="name@beispiel.de" aria-describedby="post-fehler post-hinweis">
<button class="senden" type="submit" data-senden><span class="rolle" data-rolle><span>Anschlagen</span></span></button>
</div>
<div class="zeile-unter">
<p class="fehler" id="post-fehler" data-fehler hidden></p>
<p class="hinweis" id="post-hinweis" data-hinweis data-t="hinweis">Abmelden geht mit einem Klick. <a href="#">Datenschutz</a></p>
</div>
<div class="maschen" data-maschen aria-hidden="true"></div>
<p class="status" data-status aria-live="polite"></p>
</form>
</div>
<div class="bild" aria-hidden="true">
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="strick" width="12" height="10" patternUnits="userSpaceOnUse">
<path d="M1 1 L6 8 L11 1" fill="none" stroke="rgba(0,0,0,.16)" stroke-width="1.6" stroke-linecap="round"/>
</pattern>
<clipPath id="sockenform"><path d="M150 40 h100 v170 c0 14 6 22 18 30 l40 26 c22 14 26 44 6 62 c-14 13 -34 14 -52 4 l-96 -56 c-12 -7 -18 -20 -18 -34 z"/></clipPath>
<clipPath id="umschlag-innen"><rect x="0" y="-40" width="400" height="340"/></clipPath>
</defs>
<!-- Rückseite des Umschlags -->
<rect x="40" y="170" width="320" height="200" rx="10" fill="#e0dacf"/>
<!-- Klappe hinten, offen -->
<path class="klappe-hinten" d="M40 172 h320 l-160 -104 z" fill="#f3efe6" stroke="#e0dacf" stroke-width="2"/>
<!-- Socke, steckt im Umschlag -->
<g clip-path="url(#umschlag-innen)">
<g class="socke">
<g transform="translate(-6 -96) rotate(-9 200 150)">
<g clip-path="url(#sockenform)">
<rect x="100" y="0" width="260" height="360" fill="#f5f4f1"/>
<rect x="100" y="40" width="260" height="34" fill="#212121"/>
<rect x="100" y="92" width="260" height="22" fill="#eb5c9d"/>
<rect x="100" y="132" width="260" height="22" fill="#eb5c9d"/>
<rect x="100" y="172" width="260" height="22" fill="#10714e"/>
<path d="M250 228 l70 40 v90 h-200 z" fill="#eb5c9d"/>
<rect x="100" y="0" width="260" height="360" fill="url(#strick)"/>
</g>
</g>
</g>
</g>
<!-- Vorderseite mit Faltlinien -->
<path d="M40 180 L200 280 L360 180 V360 a10 10 0 0 1 -10 10 H50 a10 10 0 0 1 -10 -10 Z" fill="#fffdf8"/>
<path d="M40 360 L170 262 M360 360 L230 262" stroke="#e0dacf" stroke-width="2" fill="none"/>
<!-- Klappe vorn: bei Erfolg zu -->
<path class="klappe" d="M40 172 h320 l-160 104 z" fill="#f3efe6" stroke="#e0dacf" stroke-width="2"/>
<!-- Briefmarke mit Schaf -->
<g transform="translate(272 288)">
<rect x="0" y="0" width="64" height="72" fill="#fff" stroke="#e0dacf" stroke-dasharray="3 3" stroke-width="3"/>
<rect x="7" y="7" width="50" height="58" fill="#10714e"/>
<ellipse cx="34" cy="38" rx="16" ry="11" fill="#fffdf8"/>
<circle cx="22" cy="31" r="7" fill="#fffdf8"/><circle cx="32" cy="27" r="7" fill="#fffdf8"/><circle cx="43" cy="30" r="7" fill="#fffdf8"/>
<ellipse cx="16" cy="40" rx="5.5" ry="4.5" fill="#212121"/>
<path d="M26 48 v8 M40 48 v8" stroke="#212121" stroke-width="3" stroke-linecap="round"/>
<text x="32" y="20" text-anchor="middle" font-family="Instrument Sans, sans-serif" font-size="8" font-weight="700" fill="#fffdf8">95</text>
</g>
<!-- Poststempel, erscheint nach der Anmeldung -->
<g transform="translate(196 312)"><g class="stempel">
<circle r="46" fill="none" stroke="#eb5c9d" stroke-width="3"/>
<circle r="37" fill="none" stroke="#eb5c9d" stroke-width="1.5"/>
<text y="-14" text-anchor="middle" font-family="Instrument Sans, sans-serif" font-size="9" font-weight="700" letter-spacing="1.2" fill="#eb5c9d">FLENSBURG</text>
<text y="3" text-anchor="middle" font-family="Instrument Sans, sans-serif" font-size="11" font-weight="700" fill="#eb5c9d">ANGE-</text>
<text y="16" text-anchor="middle" font-family="Instrument Sans, sans-serif" font-size="11" font-weight="700" fill="#eb5c9d">SCHLAGEN</text>
</g></g>
</svg>
</div>
</section>
<div class="anstoss">
<p data-t="anstoss">Maschenpost: <em>einmal im Monat Post von der Förde.</em></p>
<button type="button" data-oeffnen data-t="anstossKnopf">Maschenpost abonnieren</button>
</div>
<nav class="spalten" aria-label="Fußbereich">
<div class="wir">
<p class="marke">Fördewolle</p>
<p class="marke-satz">Socken und Pullover aus Wolle von Schafen aus Angeln. Gestrickt in Flensburg, seit 2019.</p>
<p class="lager">Alle Farben ab Lager lieferbar</p>
</div>
<div>
<h3 class="label">Shop</h3>
<ul><li><a href="#">Socken</a></li><li><a href="#">Pullover</a></li><li><a href="#">Mützen & Schals</a></li><li><a href="#">Garn & Anleitungen</a></li><li><a href="#">Gutscheine</a></li></ul>
</div>
<div>
<h3 class="label">Hilfe</h3>
<ul><li><a href="#">Versand & Lieferzeit</a></li><li><a href="#">Rückgabe</a></li><li><a href="#">Größentabelle</a></li><li><a href="#">Wollpflege</a></li><li><a href="#">Häufige Fragen</a></li></ul>
</div>
<div>
<h3 class="label">Fördewolle</h3>
<ul><li><a href="#">Über uns</a></li><li><a href="#">Unsere Höfe</a></li><li><a href="#">Strickerei besuchen</a></li><li><a href="#">Jobs</a></li></ul>
</div>
<div class="kontakt">
<h3 class="label">Fragen?</h3>
<ul><li><a href="tel:+4946130799420">0461 307 994 20</a></li><li><a href="mailto:hallo@foerdewolle.de">hallo@foerdewolle.de</a></li><li>Mo–Fr, 9–16 Uhr</li></ul>
</div>
</nav>
<div class="zahlung"><span class="label">Bezahlen mit</span><span class="chip">PayPal</span><span class="chip">Kreditkarte</span><span class="chip">Rechnung</span><span class="chip">SEPA-Lastschrift</span></div>
<div class="recht">
<span>© 2026 Fördewolle GmbH · Norderhofenden 12 · 24937 Flensburg · Preise inkl. MwSt.</span>
<nav aria-label="Rechtliches"><a href="#">Impressum</a><a href="#">Datenschutz</a><a href="#">AGB</a><a href="#">Widerruf</a><a href="#">Cookie-Einstellungen</a></nav>
</div>
</footer>
<!-- ===== Variante Kultur: Wochenzettel mit Programmauswahl ===== -->
<div class="ku" data-welt="kultur">
<header class="ku-kopf"><b>Zollhaus</b><span>Haus für Gegenwart · Leipzig-Plagwitz · Kasse Di bis So ab 12 Uhr</span></header>
<section class="ku-modul" aria-labelledby="ku-titel">
<div>
<p class="ku-label">Wochenzettel · seit 2019 · 3 140 Leute lesen mit</p>
<h2 id="ku-titel">Nur das Programm, das Sie interessiert.</h2>
<p class="ku-text">Jeden Donnerstag eine Mail mit den Terminen der nächsten sieben Tage. Sie wählen die Sparten, wir lassen den Rest weg.</p>
<fieldset>
<legend>Sparten</legend>
<div class="ku-sparten" data-sparten>
<button type="button" class="sparte" data-sparte="musik" style="--farbe:#c4421a" aria-pressed="true"><i></i>Musik <small data-anzahl></small></button>
<button type="button" class="sparte" data-sparte="buehne" style="--farbe:#3d5f9a" aria-pressed="false"><i></i>Theater & Tanz <small data-anzahl></small></button>
<button type="button" class="sparte" data-sparte="kunst" style="--farbe:#2f7d4a" aria-pressed="false"><i></i>Kunst <small data-anzahl></small></button>
<button type="button" class="sparte" data-sparte="film" style="--farbe:#b0186b" aria-pressed="true"><i></i>Film <small data-anzahl></small></button>
<button type="button" class="sparte" data-sparte="kinder" style="--farbe:#8a5a00" aria-pressed="false"><i></i>Für Kinder <small data-anzahl></small></button>
</div>
</fieldset>
<fieldset>
<legend>Wie oft?</legend>
<div class="ku-takt" role="group" aria-label="Wie oft" data-takt>
<button type="button" data-oft="woche" aria-pressed="true">Jeden Donnerstag</button>
<button type="button" data-oft="monat" aria-pressed="false">Einmal im Monat</button>
</div>
</fieldset>
<form data-nl="kultur" novalidate>
<label class="weg" for="ku-mail">E-Mail-Adresse für den Wochenzettel</label>
<div class="nl-feld" data-nl-zeile>
<input id="ku-mail" type="email" inputmode="email" autocomplete="email" required placeholder="name@beispiel.de" aria-describedby="ku-fehler ku-hinweis">
<button class="nl-knopf" type="submit" data-nl-knopf><span class="rolle" data-nl-rolle><span>Eintragen</span></span></button>
</div>
<div class="nl-unter">
<p class="nl-fehler" id="ku-fehler" data-nl-fehler hidden></p>
<p id="ku-hinweis">Abmelden mit einem Klick. Keine Zählpixel in unseren Mails. <a href="#">Datenschutz</a></p>
</div>
<p class="nl-status" data-nl-status aria-live="polite"></p>
</form>
</div>
<aside class="ku-vorschau" aria-label="Vorschau des nächsten Wochenzettels" data-ku-vorschau>
<header><b data-ku-titel>Ihr Zettel am Do, 5. November</b><span class="ku-label" data-ku-zahl></span></header>
<ol class="ku-liste" data-ku-liste>
<li data-s="musik" style="--farbe:#c4421a"><time>Do 05.11. 20:00</time><div><strong>Streichquartett Ost spielt Bartók</strong><span>Musik · Saal</span></div></li>
<li data-s="buehne" style="--farbe:#3d5f9a"><time>Fr 06.11. 19:30</time><div><strong>Gastspiel: „Schwerkraft“, Tanzkompanie Weißensee</strong><span>Theater & Tanz · Halle 2</span></div></li>
<li data-s="kinder" style="--farbe:#8a5a00"><time>Sa 07.11. 15:00</time><div><strong>Druckwerkstatt mit Kartoffeln, ab 6 Jahren</strong><span>Für Kinder · Werkstatt</span></div></li>
<li data-s="musik" style="--farbe:#c4421a"><time>Sa 07.11. 22:00</time><div><strong>Clubnacht: Elektronik aus dem Westwerk</strong><span>Musik · Keller</span></div></li>
<li data-s="kunst" style="--farbe:#2f7d4a"><time>So 08.11. 11:00</time><div><strong>Führung durch „Grenzposten“</strong><span>Kunst · Galerie</span></div></li>
<li data-s="film" style="--farbe:#b0186b"><time>So 08.11. 18:00</time><div><strong>Kurzfilme aus Sachsen, mit Gästen</strong><span>Film · Kino</span></div></li>
<li data-s="kunst" style="--farbe:#2f7d4a"><time>Di 10.11. 19:00</time><div><strong>Gespräch mit der Fotografin Rana Okafor</strong><span>Kunst · Foyer</span></div></li>
<li data-s="film" style="--farbe:#b0186b"><time>Mi 11.11. 20:00</time><div><strong>Stummfilm mit Klavier: „Menschen am Sonntag“</strong><span>Film · Kino</span></div></li>
</ol>
<p class="ku-leer" data-ku-leer hidden>Wählen Sie mindestens eine Sparte. Ein leerer Zettel wäre schade ums Postfach.</p>
<footer><span data-ku-fuss>Vorschau, Stand Sonntag</span><span class="ku-stempel">Abonniert</span></footer>
</aside>
</section>
<footer class="ku-fuss">
<span>Zollhaus gGmbH · Karl-Heine-Straße 108 · 04229 Leipzig · Rollstuhlgerecht, Induktionsschleife im Saal</span>
<nav aria-label="Rechtliches"><a href="#">Barrierefreiheit</a><a href="#">Impressum</a><a href="#">Datenschutz</a></nav>
</footer>
</div>
<!-- ===== Variante Gastro: Saisonbrief im dunklen Footer ===== -->
<div class="ga" data-welt="saison">
<div class="ga-band"><p>Mittwoch bis Sonntag ab 17 Uhr, sonntags auch mittags.</p><a href="#reservieren">Tisch reservieren</a></div>
<footer class="ga-fuss" data-ga-fuss>
<div class="ga-raster">
<section aria-labelledby="ga-titel">
<p class="ga-rubrik">Saisonbrief · viermal im Jahr</p>
<h2 id="ga-titel">Wenn der Bärlauch kommt, <em>schreiben wir.</em></h2>
<p class="ga-text">Ein Brief zu jedem Jahreszeitenwechsel: was auf die Karte kommt, welcher Hof liefert und an welchem Abend wir für Sie einen Tisch zurückhalten.</p>
<ol class="ga-jahr" aria-label="Die vier Briefe">
<li style="--n:0"><svg viewBox="0 0 34 34" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d="M17 31V13M17 13c-6-2-9-7-8-11 6 1 9 6 8 11zM17 20c4-1 8-4 9-9-5 0-8 4-9 9z"/></svg><b>März</b>Bärlauch, Spargel, Lamm vom Feldberg<span class="haken" aria-hidden="true"><svg viewBox="0 0 12 12"><path d="M2.5 6.5l2.3 2.2L9.5 3.8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></span></li>
<li style="--n:1"><svg viewBox="0 0 34 34" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="24" r="5"/><circle cx="23" cy="25" r="5"/><path d="M11 19c2-7 6-12 12-14M23 20c-1-6 0-11 0-15M23 5c3 0 6 1 7 3-3 1-5 1-7-3z"/></svg><b>Juni</b>Kirschen, Forelle, Holunder<span class="haken" aria-hidden="true"><svg viewBox="0 0 12 12"><path d="M2.5 6.5l2.3 2.2L9.5 3.8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></span></li>
<li style="--n:2"><svg viewBox="0 0 34 34" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d="M6 17c0-6 5-10 11-10s11 4 11 10z"/><path d="M14 17v9c0 2 1 3 3 3s3-1 3-3v-9"/></svg><b>September</b>Pilze, Zwetschgen, Wild<span class="haken" aria-hidden="true"><svg viewBox="0 0 12 12"><path d="M2.5 6.5l2.3 2.2L9.5 3.8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></span></li>
<li style="--n:3" class="naechster"><svg viewBox="0 0 34 34" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d="M17 3v28M17 9l-6-4M17 9l6-4M17 16l-8-5M17 16l8-5M17 23l-10-5M17 23l10-5"/></svg><b>Dezember</b>Gans, Schäufele, Bratapfel<em class="ga-marke">Nächster Brief</em><span class="haken" aria-hidden="true"><svg viewBox="0 0 12 12"><path d="M2.5 6.5l2.3 2.2L9.5 3.8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></span></li>
</ol>
<form data-nl="saison" novalidate>
<label class="weg" for="ga-mail">E-Mail-Adresse für den Saisonbrief</label>
<div class="nl-feld" data-nl-zeile>
<input id="ga-mail" type="email" inputmode="email" autocomplete="email" required placeholder="name@beispiel.de" aria-describedby="ga-fehler ga-hinweis">
<button class="nl-knopf" type="submit" data-nl-knopf><span class="rolle" data-nl-rolle><span>Vormerken</span></span></button>
</div>
<div class="nl-unter">
<p class="nl-fehler" id="ga-fehler" data-nl-fehler hidden></p>
<p id="ga-hinweis">Vier Mails im Jahr, nicht mehr. <a href="#">Datenschutz</a></p>
</div>
<p class="nl-status" data-nl-status aria-live="polite"></p>
</form>
</section>
<div class="ga-spalte">
<h3 class="ga-rubrik">Geöffnet</h3>
<dl><dt>Mi bis Sa</dt><dd>17 bis 23 Uhr</dd><dt>Sonntag</dt><dd>12 bis 21 Uhr</dd><dt>Mo, Di</dt><dd>Ruhetag</dd></dl>
<p style="margin-top:12px">Küche bis 21.30 Uhr.<br>24. bis 26. Dezember geöffnet, Reservierung nötig.</p>
</div>
<div class="ga-spalte">
<h3 class="ga-rubrik">Lindenwirt</h3>
<p>Talstraße 9<br>79199 Kirchzarten<br><a href="tel:+497661904120">07661 904 120</a><br><a href="mailto:tisch@lindenwirt-kirchzarten.de">tisch@lindenwirt-kirchzarten.de</a></p>
<p style="margin-top:12px">Höllentalbahn bis Kirchzarten, dann vier Minuten zu Fuß.</p>
</div>
</div>
<div class="ga-unten"><span>© 2026 Gasthaus Lindenwirt · Familie Ruesch</span><nav aria-label="Rechtliches"><a href="#">Impressum</a><a href="#">Datenschutz</a><a href="#">Gutscheine</a></nav></div>
</footer>
</div>
<dialog class="ueberlage" data-ueberlage aria-labelledby="post-titel"></dialog>
<div class="zeiger" data-zeiger aria-hidden="true"></div>
<script>
(() => {
const html = document.documentElement;
const $ = s => document.querySelector(s);
const reduziert = () => html.classList.contains('reduziert');
/* Werte: Status-Roll 300 ms (Messwert), Kurven-Paar aus dem Rezept d2c-produkt-zuerst */
const W = {
auf: 'cubic-bezier(0, 0, 0.2, 1)', zu: 'cubic-bezier(0.4, 0, 1, 1)',
rollRein: 300, rollRaus: 200, // neuer Text kommt von unten, alter geht nach oben
oeffnen: 300, schliessen: 200, // Overlay
senden: 900, // gedachte Serverantwort
maschen: 300, maschenVersatz: 24,
};
const TEXTE = {
marke: {
claim: 'Post von der Förde. <em>Einmal im Monat, nie öfter.</em>',
unterzeile: 'Maschenpost · erscheint am 1. Dezember',
vorteile: '<li>Neue Farben drei Tage, bevor sie im Shop sind</li><li>Jede Ausgabe eine Strickanleitung zum Selbermachen</li><li>10 % auf deine nächste Bestellung</li>',
feldlabel: 'Deine E-Mail-Adresse für die Maschenpost',
hinweis: 'Abmelden geht mit einem Klick. <a href="#">Datenschutz</a>',
knopf: 'Anschlagen', laeuft: 'Wird gestrickt …', fertig: 'Angeschlagen ✓',
leer: 'Ohne Adresse kein Brief. Trag sie oben ein.',
falsch: 'Da fehlt noch eine Masche. Prüf bitte die Adresse.',
ok: 'Fast geschafft: Wir haben dir eine Mail geschickt. Ein Klick darin, und die nächste Maschenpost gehört dir.',
nochmal: 'Andere Adresse eintragen',
anstoss: 'Maschenpost: <em>einmal im Monat Post von der Förde.</em>', anstossKnopf: 'Maschenpost abonnieren',
},
neutral: {
claim: 'Newsletter abonnieren',
unterzeile: 'Newsletter',
vorteile: '<li>Neuigkeiten und Angebote</li><li>Informationen zu neuen Produkten</li>',
feldlabel: 'E-Mail-Adresse',
hinweis: 'Mit der Anmeldung akzeptieren Sie unsere <a href="#">Datenschutzerklärung</a>.',
knopf: 'Abonnieren', laeuft: 'Wird gesendet …', fertig: 'Angemeldet',
leer: 'Bitte geben Sie eine E-Mail-Adresse ein.',
falsch: 'Bitte geben Sie eine gültige E-Mail-Adresse ein.',
ok: 'Vielen Dank. Bitte bestätigen Sie Ihre Anmeldung über den Link in der E-Mail.',
nochmal: 'Zurücksetzen',
anstoss: 'Newsletter', anstossKnopf: 'Jetzt anmelden',
},
};
let ton = 'marke', variante = 'footer', zustand = 'leer', beruehrt = false;
const post = $('[data-post]'), form = $('[data-form]'), feld = $('#post-mail'), zeile = $('[data-feldzeile]');
const senden = $('[data-senden]'), rolle = $('[data-rolle]'), fehler = $('[data-fehler]'), status = $('[data-status]');
const maschen = $('[data-maschen]'), ueberlage = $('[data-ueberlage]'), werte = $('[data-werte]'), zeiger = $('[data-zeiger]');
const T = () => TEXTE[ton];
/* Maschenreihe aus kleinen Vs */
const V = '<svg viewBox="0 0 12 14"><path d="M1.5 2 L6 12 L10.5 2" fill="none" stroke="#eb5c9d" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>';
function strickeReihe() {
const n = Math.max(8, Math.floor((zeile.offsetWidth - 40) / 14));
if (maschen.childElementCount !== n) maschen.innerHTML = Array.from({ length: n }, (_, i) => `<i style="--n:${i}">${V}</i>`).join('');
}
function zeigeWerte() {
if (welt !== 'basis') return werteZeigen();
werte.innerHTML = reduziert() ? 'Roll <b>aus</b>' : `Status-Roll <b>${W.rollRein} / ${W.rollRaus} ms</b>`;
}
/* ---------- Status-Roll im Knopf ---------- */
let rollAnim = [];
function roll(text, sofort) {
const alt = rolle.lastElementChild;
if (alt && alt.textContent === text) return;
rollAnim.forEach(a => a.finish()); rollAnim = [];
const neu = document.createElement('span'); neu.textContent = text;
if (sofort || reduziert() || !alt) { rolle.replaceChildren(neu); return; }
rolle.appendChild(neu);
rollAnim = [
alt.animate([{ transform: 'translateY(0)' }, { transform: 'translateY(-110%)' }], { duration: W.rollRaus, easing: W.zu, fill: 'forwards' }),
neu.animate([{ transform: 'translateY(110%)' }, { transform: 'translateY(0)' }], { duration: W.rollRein, easing: W.auf }),
];
rollAnim[1].finished.then(() => alt.remove()).catch(() => {});
}
// Knopfbreite auf den längsten Text festlegen, damit nichts springt
function knopfbreite() {
const m = document.createElement('span'); m.style.cssText = 'position:absolute;visibility:hidden;white-space:nowrap;font-weight:600';
senden.appendChild(m);
let b = 0; ['knopf', 'laeuft', 'fertig'].forEach(k => { m.textContent = T()[k]; b = Math.max(b, m.offsetWidth); });
m.remove(); senden.style.minWidth = (b + 44) + 'px';
}
/* ---------- Validierung erst nach Blur ---------- */
const gueltig = v => /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v.trim());
function pruefe(zeigen) {
const v = feld.value, ok = gueltig(v);
const meldung = ok ? '' : (v.trim() ? T().falsch : T().leer);
if (zeigen) {
fehler.textContent = meldung; fehler.hidden = !meldung;
zeile.classList.toggle('fehlerhaft', !!meldung);
feld.setAttribute('aria-invalid', String(!!meldung));
}
return ok;
}
feld.addEventListener('blur', () => { if (feld.value) { beruehrt = true; pruefe(true); } });
feld.addEventListener('input', () => { if (beruehrt) pruefe(true); });
let sendeUhr = 0;
function absenden() {
if (zustand !== 'leer') return;
beruehrt = true;
if (!pruefe(true)) { feld.focus({ preventScroll: true }); return; }
zustand = 'laeuft'; senden.setAttribute('aria-busy', 'true');
roll(T().laeuft);
sendeUhr = setTimeout(() => {
zustand = 'fertig'; senden.removeAttribute('aria-busy');
roll(T().fertig); senden.classList.add('fertig');
post.classList.add('erfolg');
strickeReihe(); maschen.classList.remove('an'); void maschen.offsetWidth; maschen.classList.add('an');
status.innerHTML = `${T().ok} <button type="button" class="nochmal" data-nochmal>${T().nochmal}</button>`;
$('[data-nochmal]').addEventListener('click', () => zuruecksetzen());
}, W.senden);
}
form.addEventListener('submit', e => { e.preventDefault(); stoppe(); absenden(); });
function zuruecksetzen(leeren = true) {
clearTimeout(sendeUhr); zustand = 'leer'; beruehrt = false;
if (leeren) feld.value = '';
senden.classList.remove('fertig', 'druck'); senden.removeAttribute('aria-busy');
post.classList.remove('erfolg'); maschen.classList.remove('an');
fehler.hidden = true; fehler.textContent = ''; zeile.classList.remove('fehlerhaft', 'fokus'); feld.removeAttribute('aria-invalid');
status.textContent = '';
roll(T().knopf, true);
}
/* ---------- Ton (Mikrotexte) ---------- */
function setzeTon(t) {
ton = t;
document.querySelectorAll('[data-t]').forEach(el => { el.innerHTML = T()[el.dataset.t]; });
document.querySelectorAll('[data-ton]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.ton === ton)));
$('[data-nr]').hidden = ton === 'neutral';
knopfbreite();
zuruecksetzen();
}
/* ---------- Variante Overlay: Dialog geht aus dem Footer auf ---------- */
const heimat = { eltern: post.parentNode, vor: post.nextSibling };
let schliessUhr = 0;
function oeffne() {
clearTimeout(schliessUhr);
if (!ueberlage.open) { ueberlage.appendChild(post); ueberlage.showModal(); }
requestAnimationFrame(() => requestAnimationFrame(() => ueberlage.classList.add('offen')));
}
function schliesse(sofort) {
if (!ueberlage.open) return;
ueberlage.classList.remove('offen');
const fertig = () => { ueberlage.close(); heimat.eltern.insertBefore(post, heimat.vor); };
clearTimeout(schliessUhr);
if (sofort || reduziert()) fertig(); else schliessUhr = setTimeout(fertig, W.schliessen);
}
$('[data-oeffnen]').addEventListener('click', () => { stoppe(); oeffne(); });
$('[data-schliessen]').addEventListener('click', () => { stoppe(); schliesse(); });
ueberlage.addEventListener('cancel', e => { e.preventDefault(); stoppe(); schliesse(); });
ueberlage.addEventListener('click', e => { if (e.target === ueberlage) { stoppe(); schliesse(); } });
function setzeVariante(v) {
variante = v;
html.classList.toggle('mit-overlay', v === 'overlay');
document.querySelectorAll('[data-variante]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.variante === v)));
if (v === 'footer') schliesse(true);
}
document.querySelectorAll('[data-variante]').forEach(b => b.addEventListener('click', () => { stoppe(); zuruecksetzen(); setzeVariante(b.dataset.variante); }));
document.querySelectorAll('[data-ton]').forEach(b => b.addEventListener('click', () => { stoppe(); setzeTon(b.dataset.ton); }));
/* ---------- Vorführung: tippen, Fehler nach Blur, korrigieren, absenden ---------- */
let uhren = [], laeuft = false, angehalten = false;
const nach = (ms, f) => uhren.push(setTimeout(f, ms));
function zeigerAuf(el, dx = .5, dy = .5) {
const r = el.getBoundingClientRect();
zeiger.style.transform = `translate(${r.left + r.width * dx}px, ${r.top + r.height * dy}px)`;
}
function tippe(text, start, schritt = 55) {
[...text].forEach((z, i) => nach(start + i * schritt, () => { feld.value += z; feld.dispatchEvent(new Event('input')); }));
return start + text.length * schritt;
}
function runde() {
if (welt !== 'basis') return rundeWelt();
uhren.forEach(clearTimeout); uhren = [];
zuruecksetzen();
let t = 300;
if (variante === 'overlay') { nach(t, () => { zeiger.classList.add('an'); zeigerAuf($('[data-oeffnen]')); }); t += 700; nach(t, oeffne); t += 700; }
nach(t, () => { zeiger.classList.add('an'); zeigerAuf(feld, .3); zeile.classList.add('fokus'); });
t = tippe('frauke.jensen@posteo', t + 400);
// Blur: Validierung greift erst jetzt
nach(t + 300, () => { zeile.classList.remove('fokus'); beruehrt = true; pruefe(true); zeigerAuf(senden, .5, .5); });
t += 1900;
nach(t, () => { zeile.classList.add('fokus'); zeigerAuf(feld, .7); });
t = tippe('.de', t + 300, 90);
nach(t + 400, () => { zeile.classList.remove('fokus'); zeigerAuf(senden); });
nach(t + 900, () => senden.classList.add('druck'));
nach(t + 1050, () => { senden.classList.remove('druck'); absenden(); });
t += 1050 + W.senden + 3000;
nach(t, () => { zeiger.classList.remove('an'); if (variante === 'overlay') schliesse(); });
nach(t + 600, () => { if (laeuft && !angehalten) runde(); });
}
function stoppe() { if (!laeuft) return; laeuft = false; uhren.forEach(clearTimeout); uhren = []; zeiger.classList.remove('an'); zeile.classList.remove('fokus'); }
function vorfuehren() { stoppe(); laeuft = true; angehalten = false; runde(); }
function pause() { angehalten = true; uhren.forEach(clearTimeout); uhren = []; zeiger.classList.remove('an'); }
function weiter() { if (laeuft && angehalten) { angehalten = false; runde(); } }
function neustart() { stoppe(); schliesse(true); zuruecksetzen(); kultur.reset(); saison.reset(); }
/* =====================================================================
Varianten nach Regel 13: ?v=basis | kultur | saison
Kultur und Gastro teilen sich ein schlankes Modul: Validierung nach Blur, Status-Roll im Knopf.
===================================================================== */
const WELTEN = ['basis', 'kultur', 'saison'];
let welt = 'basis';
const WERTE_WELT = {
kultur: 'Roll <b>250 / 200 ms</b> · Liste <b>300 ms</b> · cubic-bezier(0.215, 0.61, 0.355, 1)',
saison: 'Roll <b>400 / 200 ms</b> · Haken <b>700 ms, 120 ms versetzt</b> · expo.out',
};
function nlModul(form, t, o) {
const feld = form.querySelector('input'), zeileM = form.querySelector('[data-nl-zeile]'), knopf = form.querySelector('[data-nl-knopf]');
const rolleM = form.querySelector('[data-nl-rolle]'), fehlerM = form.querySelector('[data-nl-fehler]'), statusM = form.querySelector('[data-nl-status]');
let z = 'leer', ber = false, uhr = 0, anim = [];
function rollen(text, sofort) {
const alt = rolleM.lastElementChild;
if (alt && alt.textContent === text) return;
anim.forEach(a => a.finish()); anim = [];
const neu = document.createElement('span'); neu.textContent = text;
if (sofort || reduziert() || !alt) { rolleM.replaceChildren(neu); return; }
rolleM.appendChild(neu);
anim = [alt.animate([{ transform: 'translateY(0)' }, { transform: 'translateY(-110%)' }], { duration: o.raus, easing: o.kurve, fill: 'forwards' }),
neu.animate([{ transform: 'translateY(110%)' }, { transform: 'translateY(0)' }], { duration: o.rein, easing: o.kurve })];
anim[1].finished.then(() => alt.remove()).catch(() => {});
}
function breite() {
const m = document.createElement('span'); m.style.cssText = 'position:absolute;visibility:hidden;white-space:nowrap';
knopf.appendChild(m); let b = 0;
[t.knopf, t.laeuft, t.fertig].forEach(x => { m.textContent = x; b = Math.max(b, m.offsetWidth); });
m.remove(); knopf.style.minWidth = (b + 44) + 'px';
}
function pruefen(zeigen) {
const v = feld.value, ok = gueltig(v), extra = ok && o.extra ? o.extra() : '';
const meldung = !ok ? (v.trim() ? t.falsch : t.leer) : extra;
if (zeigen) { fehlerM.textContent = meldung; fehlerM.hidden = !meldung; zeileM.classList.toggle('fehlerhaft', !ok); feld.setAttribute('aria-invalid', String(!ok)); }
return !meldung;
}
feld.addEventListener('blur', () => { if (feld.value) { ber = true; pruefen(true); } });
feld.addEventListener('input', () => { if (ber) pruefen(true); });
function absenden() {
if (z !== 'leer') return;
ber = true;
if (!pruefen(true)) { if (!gueltig(feld.value)) feld.focus({ preventScroll: true }); return; }
z = 'laeuft'; knopf.setAttribute('aria-busy', 'true'); rollen(t.laeuft);
uhr = setTimeout(() => {
z = 'fertig'; knopf.removeAttribute('aria-busy'); knopf.classList.add('fertig'); rollen(t.fertig);
statusM.innerHTML = `${o.ok()} <button type="button">${t.nochmal}</button>`;
statusM.querySelector('button').addEventListener('click', () => reset());
if (o.erfolg) o.erfolg();
}, 900);
}
form.addEventListener('submit', e => { e.preventDefault(); stoppe(); absenden(); });
function reset(leeren = true) {
clearTimeout(uhr); z = 'leer'; ber = false; if (leeren) feld.value = '';
knopf.classList.remove('fertig', 'druck'); knopf.removeAttribute('aria-busy');
fehlerM.hidden = true; fehlerM.textContent = ''; zeileM.classList.remove('fehlerhaft'); feld.removeAttribute('aria-invalid');
statusM.textContent = ''; rollen(t.knopf, true); if (o.zurueck) o.zurueck();
}
reset(); document.fonts.ready.then(breite);
return { feld, knopf, zeile: zeileM, absenden, reset, pruefen: () => { ber = true; pruefen(true); }, setzeBer: () => { ber = true; } };
}
// ---- Kultur: Sparten filtern die Vorschau (FLIP nur auf y, 300 ms)
const kuKurve = 'cubic-bezier(0.215, 0.61, 0.355, 1)';
const sparten = [...document.querySelectorAll('[data-sparte]')], kuListe = $('[data-ku-liste]'), kuZeilen = [...kuListe.children];
const kuVorschau = $('[data-ku-vorschau]'), kuZahl = $('[data-ku-zahl]'), kuLeer = $('[data-ku-leer]'), kuTitel = $('[data-ku-titel]'), kuFuss = $('[data-ku-fuss]');
const SPARTE_NAME = { musik: 'Musik', buehne: 'Theater & Tanz', kunst: 'Kunst', film: 'Film', kinder: 'Für Kinder' };
let oft = 'woche';
sparten.forEach(b => { b.querySelector('[data-anzahl]').textContent = kuZeilen.filter(z => z.dataset.s === b.dataset.sparte).length; });
const gewaehlt = () => sparten.filter(b => b.getAttribute('aria-pressed') === 'true').map(b => b.dataset.sparte);
function kuFiltern(sanft = true) {
const g = gewaehlt();
const vorher = new Map(kuZeilen.filter(z => !z.classList.contains('weg')).map(z => [z, z.getBoundingClientRect().top]));
kuZeilen.forEach(z => z.classList.toggle('weg', !g.includes(z.dataset.s)));
const n = kuZeilen.filter(z => !z.classList.contains('weg')).length;
kuZahl.textContent = n === 1 ? '1 Termin' : `${n} Termine`;
kuLeer.hidden = n > 0;
if (!sanft || reduziert()) return;
kuZeilen.forEach(z => {
if (z.classList.contains('weg')) return;
const alt = vorher.get(z), neu = z.getBoundingClientRect().top;
if (alt === undefined) z.animate([{ opacity: 0, transform: 'translateY(8px)' }, { opacity: 1, transform: 'none' }], { duration: 300, easing: kuKurve });
else if (Math.abs(alt - neu) > 1) z.animate([{ transform: `translateY(${alt - neu}px)` }, { transform: 'none' }], { duration: 300, easing: kuKurve });
});
}
sparten.forEach(b => b.addEventListener('click', () => { stoppe(); b.setAttribute('aria-pressed', String(b.getAttribute('aria-pressed') !== 'true')); kuFiltern(); }));
const taktKnoepfe = [...document.querySelectorAll('[data-oft]')];
function setzeOft(w) {
oft = w; taktKnoepfe.forEach(b => b.setAttribute('aria-pressed', String(b.dataset.oft === w)));
kuTitel.textContent = w === 'woche' ? 'Ihr Zettel am Do, 5. November' : 'Ihr Monatszettel am 1. Dezember';
kuFuss.textContent = w === 'woche' ? 'Vorschau, Stand Sonntag' : 'Vorschau: Auszug, Dezember folgt';
}
taktKnoepfe.forEach(b => b.addEventListener('click', () => { stoppe(); setzeOft(b.dataset.oft); }));
const listeText = () => { const g = gewaehlt().map(x => SPARTE_NAME[x]); return g.length > 1 ? g.slice(0, -1).join(', ') + ' und ' + g[g.length - 1] : g[0]; };
const kultur = nlModul($('[data-nl="kultur"]'), {
knopf: 'Eintragen', laeuft: 'Wird eingetragen …', fertig: 'Eingetragen ✓', nochmal: 'Andere Adresse',
leer: 'Bitte tragen Sie eine E-Mail-Adresse ein.', falsch: 'Diese Adresse sieht unvollständig aus. Bitte prüfen.',
}, {
rein: 250, raus: 200, kurve: kuKurve,
extra: () => gewaehlt().length ? '' : 'Bitte wählen Sie mindestens eine Sparte.',
ok: () => `Bitte bestätigen Sie den Link in Ihrem Postfach. Der erste ${oft === 'woche' ? 'Wochenzettel kommt am Donnerstag, 5. November' : 'Monatszettel kommt am 1. Dezember'}, mit ${listeText()}.`,
erfolg: () => kuVorschau.classList.add('abonniert'),
zurueck: () => kuVorschau.classList.remove('abonniert'),
});
// ---- Gastro: Saisonbrief, nach der Anmeldung bekommen alle vier Briefe einen Haken
const gaFuss = $('[data-ga-fuss]');
const saison = nlModul($('[data-nl="saison"]'), {
knopf: 'Vormerken', laeuft: 'Wird notiert …', fertig: 'Vorgemerkt ✓', nochmal: 'Andere Adresse',
leer: 'Ohne Adresse kommt kein Brief. Bitte eintragen.', falsch: 'Da fehlt noch etwas an der Adresse.',
}, {
rein: 400, raus: 200, kurve: 'cubic-bezier(0.16, 1, 0.3, 1)',
ok: () => 'Fast geschafft: Bitte bestätigen Sie die Mail von uns. Der Winterbrief kommt am 1. Dezember, mit der Gänsekarte.',
erfolg: () => gaFuss.classList.add('vorgemerkt'),
zurueck: () => gaFuss.classList.remove('vorgemerkt'),
});
function setzeWelt(w, url = true) {
welt = w; html.dataset.v = w;
document.querySelectorAll('[data-welt-wahl]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.weltWahl === w)));
if (w !== 'basis') schliesse(true);
if (url) { const u = new URL(location.href); u.searchParams.set('v', w); history.replaceState(history.state, '', u); }
werteZeigen();
}
function werteZeigen() {
if (welt === 'basis') return zeigeWerte();
werte.innerHTML = reduziert() ? 'Roll <b>aus</b>' : WERTE_WELT[welt];
}
document.querySelectorAll('[data-welt-wahl]').forEach(b => b.addEventListener('click', () => { stoppe(); kultur.reset(); saison.reset(); setzeWelt(b.dataset.weltWahl); }));
// ---- Vorführung der Varianten: Kultur schaltet eine Sparte zu, dann tippen, Fehler, Korrektur, Absenden
function rundeWelt() {
uhren.forEach(clearTimeout); uhren = [];
const m = welt === 'kultur' ? kultur : saison;
m.reset();
let t = 400;
if (welt === 'kultur') {
sparten.forEach(b => b.setAttribute('aria-pressed', String(b.dataset.sparte === 'musik' || b.dataset.sparte === 'film'))); kuFiltern(false); setzeOft('woche');
const kunst = $('[data-sparte="kunst"]');
nach(t, () => { zeiger.classList.add('an'); zeigerAuf(kunst); }); t += 800;
nach(t, () => { kunst.setAttribute('aria-pressed', 'true'); kuFiltern(); }); t += 1400;
const kinder = $('[data-sparte="kinder"]');
nach(t, () => zeigerAuf(kinder)); t += 800;
nach(t, () => { kinder.setAttribute('aria-pressed', 'true'); kuFiltern(); }); t += 1200;
nach(t, () => zeigerAuf($('[data-sparte="musik"]'))); t += 800;
nach(t, () => { $('[data-sparte="musik"]').setAttribute('aria-pressed', 'false'); kuFiltern(); }); t += 1300;
}
nach(t, () => { zeiger.classList.add('an'); zeigerAuf(m.feld, .3); m.zeile.classList.add('fokus'); m.feld.scrollIntoView({ block: 'nearest' }); });
const tipp = (text, start, schritt = 55) => { [...text].forEach((z, i) => nach(start + i * schritt, () => { m.feld.value += z; m.feld.dispatchEvent(new Event('input')); })); return start + text.length * schritt; };
t = tipp(welt === 'kultur' ? 'jonas.brendel@web' : 'helga.ruf@gmx', t + 400);
nach(t + 300, () => { m.zeile.classList.remove('fokus'); m.pruefen(); zeigerAuf(m.knopf); });
t += 1900;
nach(t, () => zeigerAuf(m.feld, .7));
t = tipp('.de', t + 300, 90);
nach(t + 400, () => zeigerAuf(m.knopf));
nach(t + 900, () => m.knopf.classList.add('druck'));
nach(t + 1050, () => { m.knopf.classList.remove('druck'); m.absenden(); });
t += 1050 + 900 + 3600;
nach(t, () => zeiger.classList.remove('an'));
nach(t + 600, () => { if (laeuft && !angehalten) rundeWelt(); });
}
zeigeWerte(); setzeTon('marke');
const ausUrl = new URLSearchParams(location.search).get('v');
setzeWelt(WELTEN.includes(ausUrl) ? ausUrl : WELTEN[0], false);
kuFiltern(false); setzeOft('woche');
document.fonts.ready.then(knopfbreite);
})();
</script>
</body>
</html>