Nutzung: Diese Datei ist der Brand Guide
Die Referenz für alles, was nach TFCZ aussehen und klingen soll. Daran halten sich alle, die TFCZ-Material erstellen, Menschen wie KI. Bei Werten gilt system/tokens.css: Farben, Radien, Schatten und Zeiten stehen dort, und der Guide liest sie von dort, statt sie selbst hinzuschreiben. Bei Regeln gilt dieser Guide. Findest du einen Widerspruch, meldest du ihn, statt still einer Seite zu folgen.
Anleitung für KI · zuerst lesen
Wer eine KI auf diesen Guide schickt, braucht nur den Link. Die KI liest diesen Kasten zuerst und arbeitet danach. Du kannst den Text auch kopieren und direkt einfügen.
Blau oben · Gold unten
Die Brand-Line ist das Markenzeichen. Print/Social = harter Schnitt, keine Verläufe. Screen-UI = Rahmen-Signatur an Fenstern & sticky Bars (blau #5ca7dc oben / gold #cda857 unten, ~3px am abgerundeten Container).
Gold ist knapp
Gold = Auswahl/Aktiv im UI und die zentrale Handlung der Seite: echte Conversion („Mitglied werden") oder Lead-Anfrage („Verfügbarkeit prüfen"). Nie Grossfläche, nie für Info-/Navigations-Buttons. So bleibt Gold wertvoll.
Dunkle, frosted Boxen
Cards, Fenster & Bausteine im Screen-UI immer als DarkNavy-Gradient, leicht transparent + backdrop-blur. Helle Boxen haben dort keinen Platz.
Nunito Sans
Einzige Schrift der Marke. Im UI: 400 / 500 (Fliesstext) / 700 / 900. Fallback: moderne, geometrische Sans-Serif.
Öffentlich hell · intern dunkel
Öffentliche Seiten = Hellblau-Verlauf, ohne Dark-Mode-Umschalter. Login-geschützte Tools (POS, Livestudio, Cockpit …) = Navy.
Responsive, ohne Ausnahme
Breakpoints 640 / 1024 / 1400. Nie ein horizontaler Scrollbalken. Touch: dense but tappable (iPad-first).
Sprache
Deutsch (Schweiz), CHF und Du-Form. Wir gendern nie. Emotion & Vibe kommen vor der Information.
Ein Logo, klare Regeln
Nur Originaldateien, richtige Variante pro Hintergrund, Schutzzone respektieren. Details stehen in Sektion 13.
Strategie & Stimme
Das Fundament: wer wir sind, wofür wir stehen, wie wir klingen. Wer eine Seite, einen Post, eine Mail oder einen Flyer macht, startet hier. Die Farbe kommt später.
Positionierung
Der eine Satz, der uns einordnet und unterscheidet. Alles, was wir sagen, muss dazu passen.
Kategorie
Ein Tischfussball-Verein in Zürich, der Sportverein, Community und Erlebnisort zugleich ist.
Für wen
Alle, die in Zürich Tischfussball erleben wollen, vom allerersten Mal an der Stange bis zur Weltspitze.
Anders als …
… eine Bar mit Töggelikasten (kein echter Sport) und anders als ein reiner Elite-Club (nicht für alle). Wir sind beides: nahbar wie ein Treffpunkt, ernsthaft wie eine Nati.
Warum glaubwürdig
100+ Mitglieder, WM-/SM-/Nati-/Bundesliga-Sieger im Verein, 10 Leonhart-Turniertische und das Zürich Open mit YouTube-Stream. Dazu über 30 Jahre am selben Ort.
Mission, Vision & Werte
Warum es uns gibt, wohin wir wollen, und die Werte, die jede Entscheidung tragen.
Mission: warum
Tischfussball in Zürich für alle zugänglich machen und gleichzeitig Spitzensport auf Weltniveau fördern.
Vision: wohin
Zürichs lebendigste Tischfussball-Community und das sichtbare Aushängeschild des Schweizer Tischfussballs.
Werte
- Alle willkommen
- Offenheit meinen wir wörtlich. Am selben Tisch stehen alle Levels und jede Herkunft.
- Gemeinschaft
- Getragen von Ehrenamt und Herzblut; wir fangen jeden Neuen auf.
- Spielfreude
- Spass zuerst, der Rest kommt von selbst.
- Exzellenz
- Echter Wettkampf, Weltklasse-Können, ohne Angeberei.
- Beständigkeit
- Seit 1990 am selben Ort. Diese Tradition schafft Vertrauen.
Markenpersönlichkeit & Archetyp
So wäre der TFCZ, wenn er ein Mensch wäre. Dieser Charakter muss in Text, Bild und Ton durchscheinen.
5 Eigenschaften
Wenn TFCZ eine Person wäre
Der Kollege, der dich am ersten Abend abholt, dir alles zeigt und dich zum Lachen bringt. Nebenbei ist er Schweizermeister und hängt das nicht an die grosse Glocke.
Wie es klingt / aussieht
Du-Form, kurze warme Sätze, konkret. Visuell: Blau/Gold, dunkel & edel, echte Fotos, viel Bewegung.
Markenversprechen & Geschichte
Unser Versprechen an alle und die Geschichte, die wir immer wieder erzählen. Dazu fertige Textbausteine zum Übernehmen.
Boilerplate · 1 Satz
Der Tischfussball Club Zürich ist der grösste Profi-Tischfussballverein der Schweiz. Seit 1990 stehen bei uns Anfänger und Weltmeister am selben Tisch.
Boilerplate · 3 Sätze
Der TFCZ ist der grösste Profi-Tischfussballverein der Schweiz und seit 1990 in Zürich zuhause. Bei uns spielen Anfänger und Weltmeister am selben Tisch, und unsere Community fängt jeden auf. Ob offenes Training, Turnier, Käsekick oder Firmenevent, willkommen ist jeder.
Boilerplate · 5 Sätze
Der Tischfussball Club Zürich (TFCZ) ist der grösste Profi-Tischfussballverein der Schweiz. Seit 1990 sind wir im selben Clublokal zuhause. Auf 10 Leonhart-Turniertischen trainieren Nationalspieler neben Leuten, die zum ersten Mal an der Stange stehen. Wir tragen Turniere aus, streamen das Zürich Open auf YouTube und feiern beim Käsekick genauso ausgelassen. Komm vorbei, der erste Abend ist gratis.
Zielgruppen & Ansprache
Für wen wir schreiben. Je Zielgruppe stehen hier Bedürfnis, Kanal und der passende Handlungsaufruf (CTA).
Einsteiger / Neugierige
Bedürfnis: Unverbindlich reinschnuppern, keine Hürde. Ton: warm, einladend. Kanal: Instagram, Website. CTA: „Erster Abend gratis".
Aktive Mitglieder
Bedürfnis: Termine, spontane Updates, dazugehören. Ton: kurz, direkt, menschlich. Kanal: WhatsApp. CTA: „Komm Dienstag".
Turnier- / Pro-Spieler
Bedürfnis: Ranglisten, Formate, Ernst. Ton: energetisch, sport-fokussiert. Kanal: Website, STF/Coral. CTA: „Jetzt anmelden".
Firmen / HR
Bedürfnis: Teamevent, das hängen bleibt, ohne Aufwand. Ton: professionell-herzlich, lösungsorientiert. Kanal: Website, E-Mail. CTA: „Firmenevent anfragen".
Familien / Kinder
Bedürfnis: Betreute, sichere Freizeit mit Spass. Ton: leicht, herzlich. Kanal: Website, Instagram. CTA: „Kindertraining & Ferienplausch".
Tone of Voice
Vier Eigenschaften bestimmen jeden Text, in jedem Kontext. Sportlich-herzlich, keine Corporate-Sprache. Die Tonlage passt sich an (Fun locker, Turnier energetisch, Firma professionell-herzlich).
1 · Einladend
2 · Echt
3 · Spielfreudig
4 · Stolz & nahbar
Botschaften & Messaging
Vier Kernbotschaften (Pillars). Jede Seite und jeder Post trägt klar eine davon. Ein Beleg sorgt dafür, dass es glaubwürdig bleibt.
1 · Alle willkommen
Botschaft: Anfänger bis Profi, am selben Tisch. Beleg: offenes Training, Schnuppern gratis, Community fängt auf. Wofür: Home, Mitglied-werden, Insta-Reels.
2 · Echter Spitzensport
Botschaft: Weltklasse spielt hier. Beleg: WM/SM/Nati, Zürich Open, YouTube-Stream, 10 Leonhart-Tische. Wofür: Turnier-Seiten, Stream, Presse.
3 · Community & Tradition
Botschaft: Seit 1990, getragen von Menschen. Beleg: dasselbe Clublokal in Zürich, Ehrenamt, Herzblut. Wofür: Über uns, Geschichte, Gönner/Sponsoring.
4 · Erlebnis & Spass
Botschaft: Bei uns wird gelacht. Beleg: Käsekick, Mittwochs DYP Challenge, Firmenevents. Wofür: Events, Firmenevent-Landingpage, Social.
Sprache & Lexikon
Sprachregeln und Wortschatz, damit alles wie aus einem Guss klingt.
Regeln
- Sprache
- Deutsch (Schweiz), Du-Form. ss statt ß, CHF, Apostroph bei Tausendern (1'000).
- Nie gendern
- Kein :in / *innen / Binnen-I, keine Doppelnennungen. Generisches Maskulinum oder neutral („alle", „Leute", „das Team"). Beispiel: «Werd einer von uns» statt «Werd eine:r von uns».
- Satzbau
- Kurze Sätze, aktiv, konkret. Emotion vor Information, zuerst muss der Vibe rüberkommen.
- Fakten
- Nie erfinden. Zahlen/Adressen/Preise immer aus dem Steckbrief (Sektion 10) übernehmen. Fehlt dort etwas, frag den Marketingverantwortlichen.
- Ort
- Immer «Zürich», nie «Wipkingen». Die Adresse lautet «Landenbergstrasse 10, 8037 Zürich». Das gilt für alles, was nach aussen geht.
- Schreibstil
- Wir schreiben, wie wir an der Bar reden. Keine Gedankenstriche als Stilmittel, kein «nicht X, sondern Y» und keine angekündigten Pointen wie «Das Ergebnis:». Kurze und lange Sätze mischen, und lieber eine echte Zahl als «viele». Texte, die mit KI entstehen, vor dem Veröffentlichen einmal laut lesen.
- Kategorien
- Die Kategorie heisst «Fun» (statt «Plausch»), «Pro Turniere» (statt «Ranglisten-Turniere»), «Elite» umfasst alles ITSF inkl. SM. Als Event-Name bleibt «Plausch-Turnier» erlaubt.
Schreiben ohne KI-Handschrift
Gilt für alles Geschriebene: Seitentexte, Knopfbeschriftungen, Instagram, WhatsApp, Mails an Mitglieder, Flyer. Ein Sprachmodell sagt das wahrscheinlichste nächste Wort voraus und steuert damit in die Mitte aller Trainingstexte. Die Mitte hat keinen Akzent. Verräterisch sind darum Satzmuster, nicht einzelne Wörter: Wortlisten veralten in Monaten, Muster bleiben. Der Club darf mit einer KI schreiben, solange am Ende der Club spricht.
| Messung | Befund |
|---|---|
| Kobak et al., Science Advances, Juli 2025: 15 Mio. Fachtexte, 2010 bis 2024 | Mindestens 13.5 % der Texte von 2024 maschinell überarbeitet, in Teilbereichen 40 % |
| Washington Post, geteilte ChatGPT-Verläufe, Mai 2024 bis Juli 2025 | Der Gedankenstrich stand im Sommer 2025 in über der Hälfte aller Antworten |
| Pangram Labs, rund 1 Mio. gescannte Beiträge, 2026 | 40.5 % der langen Beiträge auf LinkedIn vollständig maschinell, vom Anbieter als Untergrenze bezeichnet |
Eine erkennbar menschliche Stimme fällt damit auf. Erkennungswerkzeuge taugen dafür wenig: In einer Untersuchung mit 63 Dozierenden lagen die Trefferquoten beim Raten, und sieben Werkzeuge stuften im Schnitt 61 % der Aufsätze von Nicht-Muttersprachlern falsch als maschinell ein. Das Ziel dieser Regeln ist darum kein bestandener Test, sondern ein Text, der nach Club klingt.
Die drei, die am schnellsten auffallen- Gedankenstrich als Stilmittel. Der Satz wird umgebaut, nicht mit einem Komma geflickt. Bereiche behalten ihren Strich («4 - 100 Personen», «1921 - 1923»).
Vorher Der erste Abend ist gratis - stell dich einfach an den Tisch.
Nachher Der erste Abend ist gratis. Stell dich einfach an den Tisch. - «Nicht X, sondern Y», auch als «nicht nur … sondern auch» oder in zwei getrennten Sätzen. Zusammenziehen zu einem Komma-Satz löst es nicht, die Struktur muss weg.
Vorher Nicht jede Karriere beginnt im Verein, sondern in der Beiz.
Nachher Viele Karrieren beginnen in der Beiz. - Doppelpunkt-Ankündigung. «Was uns ausmacht:», «Das Ergebnis:». Auch getarnt ohne Doppelpunkt: «Schauen wir uns an, was den Club ausmacht.»
Vorher Was den TFCZ ausmacht: Freiwillige, die jede Woche aufschliessen.
Nachher Den TFCZ tragen Freiwillige, die jede Woche aufschliessen.
| Konstruktion | Beispiel | Auflösung |
|---|---|---|
| Negativ-Parallelismus | «Kein Zufall, sondern Training» | Die Positivaussage allein stehen lassen |
| Doppelpunkt-Ankündigung | «Was du wissen musst:» | Ankündigung streichen, mit der Aussage beginnen |
| Substantivierte Einleitung | «Das Schöne am Töggelen ist» | Mit Verb oder Adjektiv ansetzen |
| Trikolon in Parallelstruktur | «Training, Turniere, Gemeinschaft» | Ein Glied streichen, die übrigen ungleich gewichten |
| Anapher «Wer X / Wer Y» | «Wer neu ist, schaut zu. Wer dabei ist, spielt.» | Zweite Hälfte anders bauen |
| Perfekte Parallelstruktur | «Der erste Punkt … Der zweite Punkt …» | Nach Gewicht ordnen statt nach Zählung |
| Muster | Beispiel | Auflösung |
|---|---|---|
| Spiegel-Pointe | «Das ist kein Sport. Das ist Zuhause.» | Streichen, die Aussage steht schon davor |
| Mini-Satz als Effekt | «War es nicht.» | In den Satz davor holen |
| Einzeiler zwischen Absätzen | «Genau darum geht es hier.» | Weg, er zerhackt den Lesefluss |
| Inszenierte Erkenntnis | «Dass mich das packt, überrascht mich nicht.» | Die Beobachtung ohne Rahmen hinschreiben |
| Doppelter Schluss | Pointe und Frage am Ende | Nur eine Schlussbewegung |
| Alliteration als Pointe | «Technik statt Talent» | Ein Wort ersetzen |
| Zwangs-Zusammenfassung | «Zusammenfassend», «Insgesamt» | Mit einem konkreten nächsten Schritt enden |
Der letzte Absatz ist die schwächste Stelle jedes maschinellen Textes, weil er nach Schema gebaut wird. Beim TFCZ endet ein Text mit dem, was jemand tun kann: vorbeikommen, sich vormerken lassen, in der Gruppe fragen.
Wiederholung und Leerlauf| Muster | Beispiel | Auflösung |
|---|---|---|
| Angehängte Bedeutungsfloskel | «…, was die Bedeutung des Vereins unterstreicht» | Streichen oder die konkrete Folge nennen |
| Schein-Spektrum | «vom Einsteiger bis zum Weltmeister» als Floskel | Nur schreiben, wenn beide Enden wirklich am Tisch stehen |
| Paarige Abstrakta | «Leidenschaft und Gemeinschaft» | Eine Sache benennen, die man sehen kann |
| Wortstamm-Wiederholung | viermal «spielen / Spiel / Spieler» | Synonym oder Satzumbau |
| Inhaltliche Redundanz | Derselbe Gedanke in Absatz 2 und 5 | Den schwächeren Absatz löschen, nicht umschreiben |
| Übergangsfloskel | «Dabei ist längst bekannt …» | Direkt zur nächsten Aussage |
| Kommentar-Eröffnung | «Guter Punkt», «Spannende Frage» | Nie als Einstieg, auch nicht in Antworten |
| Stelle | Floskel |
|---|---|
| Einstieg | «In der heutigen schnelllebigen Welt …», «Immer mehr Menschen fragen sich …» |
| Brücke | «Es ist wichtig zu beachten, dass …», «Dabei ist zu berücksichtigen …» |
| Schluss | «Zusammenfassend lässt sich sagen …», «Abschliessend sei angemerkt …» |
| Pathos ohne Inhalt | «spielt eine entscheidende Rolle», «vor diesem Hintergrund» |
Wer nur diese drei Stellen prüft, findet den grössten Teil. Die Reparatur ist immer dieselbe: Phrase löschen, Satz mit der eigentlichen Aussage beginnen.
Verdächtige Wörterganzheitlich · nahtlos · massgeschneidert · facettenreich · zukunftsorientiert · essenziell · bahnbrechend · transformativ · dynamisch · innovativ · hochwertig · vielfältig · kuratieren · entfesseln · befähigen · eintauchen · beleuchten · unterstreichen · navigieren · gewährleisten · optimieren · revolutionieren.
Einzeln beweisen sie nichts, Menschen benutzen diese Wörter auch. Erst drei oder mehr in einem Absatz plus dünner Inhalt ist ein Befund, und dann wird der Absatz neu geschrieben statt geglättet. Solche Listen verfallen: «delve» war 2025 praktisch verschwunden, während der Gedankenstrich gleichzeitig in über der Hälfte aller Antworten stand. Die Struktur ist das Hauptsignal, die Wörter sind ein Nebenindiz.
Gleichmass, Haltung, Formatierung
- Satzlängen wechseln. Menschen schreiben in Schüben, eine Maschine legt sich eng um zwölf bis achtzehn Wörter. In jedem Abschnitt steht mindestens ein sehr kurzer und ein langer Satz. Fragmente sind erlaubt. Fünf tadellose Sätze von je vierzehn Wörtern sind zusammen ein Befund.
- Position beziehen. Der Club hat eine Meinung, etwa dass der erste Abend gratis ist und dass am Tisch nur zählt, was du mit dem Ball machst. Absicherung in jede Richtung liest sich wie ein Protokoll. «Aus meiner Sicht» ist in Ordnung, Dauer-Abwägung nicht.
- Fett ist knapp. Ein Absatz trägt höchstens eine Hervorhebung. Wenn alles hervorgehoben ist, ist nichts hervorgehoben.
- Kein Stichwort mit Doppelpunkt in der Liste, wenn die Erklärung danach dasselbe noch einmal sagt. Entweder Stichwort oder Satz.
- Keine Aufzählung, wo ein Satz reicht. Drei Punkte à vier Wörter sind ein Satz mit Kommas.
- Kein Zeilenumbruch nach jedem Satz und keine Symbole am Zeilenanfang. Die Liste hat ihre Markierung schon.
| Vage | Konkret |
|---|---|
| «moderne Ausstattung» | «zehn Leonhart-Turniertische, dazu ein Ullrich» |
| «regelmässig geöffnet» | «Dienstag ab 18:00, Mittwoch ab 19:00» |
| «viele Mitglieder» | «über 100 Mitglieder» |
| «zentral gelegen» | «2 Minuten vom Bahnhof Wipkingen» |
| «schon lange» | «seit 1990 im selben Clublokal» |
Lässt sich eine Angabe nicht konkret machen, wird sie gestrichen. Tausender mit Hochkomma: 3'000, 12'500, 1'240.50. Jahreszahlen (2026), Postleitzahlen (8037) und Hausnummern bleiben ohne, das sind Kennungen und keine Mengen. Zahlen, die untereinander stehen, laufen mit font-variant-numeric: tabular-nums. Fakten kommen aus dem Steckbrief, nichts wird erfunden.
| Kanal | Woran man die Maschine erkennt | Was TFCZ macht |
|---|---|---|
| Website | Gleich lange Absätze, Überschriften im Bauplan «Adjektiv plus Substantiv» | Kurze Kicker, ein Gedanke pro Abschnitt, Zahlen aus dem Steckbrief |
| Mail an Mitglieder | «Ich hoffe, diese Mail erreicht dich gut», höfliche Polsterung, auffällig fehlerfreie Absätze | Mit dem Anlass beginnen, um ein Drittel kürzen, laut mitlesen |
| Behauptung im ersten Satz, Umbruch nach jedem Satz, generische Frage am Schluss | Ein Bild, ein Satz, eine Einladung. Keine Emojis | |
| WhatsApp-Gruppe | Vollständige Sätze mit Marketing-Ton | So schreiben, wie am Tisch geredet wird, kurz und direkt |
| Knöpfe und Beschriftungen | Füllwörter wie «Jetzt» oder «Einfach» | Das Verb zuerst: «Mitglied werden», «Verfügbarkeit prüfen» |
So entsteht ein Text
- Eigene Stimme einspeisen. Zwei, drei bestehende TFCZ-Texte in den Auftrag geben. Ein Beispiel wirkt stärker als jede Stilbeschreibung.
- Wenige Verbote gleichzeitig. Gedankenstrich und «nicht X, sondern Y» reichen beim Schreiben. Zu viele Regeln auf einmal machen den Text steif und damit künstlicher.
- Rohbau von der Maschine, Schluss von Hand. Vor allem der erste und der letzte Satz.
- Vorlesetest. Würdest du das jemandem am Tisch so sagen? Wenn nein, diesen einen Satz neu.
| Befund | Vorgehen |
|---|---|
| Drei oder mehr Floskeln in einem Absatz bei dünnem Inhalt | Absatz komplett neu schreiben, nicht glätten |
| Mehrere Strukturmuster zusammen, etwa Trikolon plus Gedankenstrich plus Spiegel-Pointe | Rohbau wegwerfen und aus den Fakten neu schreiben |
Was beim TFCZ zusätzlich gilt
- Du-Form und Schweizer Hochdeutsch, also immer «ss», nie «ß».
- Wir gendern nie und benutzen das Lexikon aus Sektion 08: Stange, Schnuppern, Mitglied, Gast, Clublokal.
- Emotion vor Information. Zuerst der Satz, der jemanden abholt, dann die Details.
- Keine Emojis, auch nicht als Ersatz für ein Icon, und keine Kursive ausser im Zitat (Sektion 14).
- Der Ort heisst Zürich. «Wipkingen» kennt auswärts kaum jemand, im Text steht es nur, wenn es um den Weg geht.
Steht ein Gedankenstrich mitten im Satz? Gibt es ein «nicht X, sondern Y»? Kündigt ein Doppelpunkt eine Erkenntnis an? Sind alle Sätze gleich lang? Steht im Text mindestens eine Zahl, ein Name oder ein Ort, den es wirklich gibt? Endet der Text mit einer Zusammenfassung statt mit einem nächsten Schritt? Trägt ein Absatz mehr als eine Hervorhebung?
Was diese Regeln nicht behaupten
- Kein einzelnes Merkmal ist ein Beweis. Menschen schreiben Gedankenstriche und Dreiklänge völlig legitim. Erst Häufung plus dünner Inhalt zählt.
- Wortlisten veralten, Strukturmuster halten länger. Was hier als Wort steht, gehört in einem Jahr überprüft.
- Deutsch ist nicht Englisch. Viele veröffentlichte Marker stammen aus englischen Texten, im Deutschen sind Gedankenstriche ohnehin seltener.
- Keine Kosmetik. Absichtliche Tippfehler oder eingestreute Mundart machen einen Text nicht menschlicher, nur schlechter.
Fakten & Steckbrief
Die belastbaren Fakten für jede Seite. Was hier nicht steht, klärst du mit dem Marketingverantwortlichen.
| Verein | Tischfussball Club Zürich (TFCZ) |
| Ort | Landenbergstrasse 10, 8037 Zürich (seit 1990 dasselbe Clublokal) |
| Grösse | Grösster Profi-Tischfussballverein der Schweiz · 100+ Mitglieder · 20+ pro Abend |
| Tische | Leonhart-Verein: 10 Leonhart + 1 Ullrich permanent; weitere (Garlando, Bonzini, Tornado, Roberto) je Anlass |
| Öffnung | Di ab 18:00 (Offenes Training) · Mi ab 19:00 (Mittwochs DYP Challenge, Start 20:15) · Events gemäss Ausschreibung |
| Training mit Philipp | Di 18:45–20:15, 14 Einheiten/Semester · gratis für Mitglieder, CHF 100 für Nicht-Mitglieder |
| Mitgliedschaft | Schnuppern gratis (danach CHF 5/Eintritt) · Aktiv CHF 200/Jahr · Key Player CHF 250 + Schlüssel · Gönner auf Anfrage |
| Kanäle | Instagram @tischfussballclubzuerich · YouTube @tischfussballclubzuerich · WhatsApp (intern) · tfcz.ch |
| Kontakt | info@tfcz.ch |
| Sport-Kontext | WM/SM/Nati/Bundesliga-Sieger im Verein · Zürich Open (Heim-Turnier) · Pro Turniere STF Regio/STRT · Elite = ITSF |
Interne GV-/Finanzzahlen (Umsatz, Zahlungsarten) sind nicht öffentlich. Kommuniziert werden nur Teilnahmezahlen.
Playbook: neue Seite in 7 Schritten
Der rote Faden, der Teil 1 (Marke) und Teil 2 (Design) verbindet. Damit baut jeder ohne Spezialist eine markenkonforme Landingpage.
- Ziel & Zielgruppe wählen (Sektion 05): Für wen ist die Seite, was soll sie auslösen?
- Kernbotschaft festlegen (Sektion 07). Genau eine der vier Pillars trägt die Seite.
- Ton wählen (Sektion 06), passend zum Kontext.
- Fakten einsetzen (Sektion 10): Zahlen/Adresse/Preise übernehmen, nichts erfinden.
- Textbausteine nutzen (Sektion 04): Claim, Boilerplate, Signatur-CTA.
- Design bauen (Teil 2): Farben, Typo, Signatur, Buttons, Nav/Burger, Karten, Hintergrund-Modul.
- Gegen den Guide prüfen: Kommt jedes Element aus einem bestehenden Baustein, stimmen Farben, Brand-Line und Ton? Erst dann ausliefern.
Visuelles System
Die Regeln, mit denen die Marke sichtbar wird: Farben, Logo, Schrift, Tokens, Brand-Line, Hover und Auswahl, Icons, Hintergründe und Print/Social. Wer nur einen Flyer macht, braucht Teil 1 und diesen Teil.
Farben & Gradients
Kanonische Palette mit Primär- und Neutralfarben, Sekundär-Akzenten und den vier erlaubten 2-Farb-Gradients. Klicken kopiert.
Nur in den Stream-Grafiken (Spielstatistik, Scoreboard, Ballbesitz). Die zwei Teams spielen in Grün und Schwarz, darum tragen die Zahlen die Teamfarbe. Überall sonst gelten die Farben oben.
Das Blau der Marke: Brand-Line oben, Kategorie «Fun», Akzente
- HEX
- #5ca7dc
- RGB
- 92 · 167 · 220
Logo im Druck, Icon-Ringe, Aktions-Buttons
- HEX
- #4489c7
- RGB
- 68 · 137 · 199
- CMYK
- 73 · 38 · 0 · 0
Kategorie «Pro», dunkle Blauflächen
- HEX
- #005a94
- RGB
- 0 · 90 · 148
Dunkler Grund (intern), Text auf Gold und Sand
- HEX
- #0d273d
- RGB
- 13 · 39 · 61
Brand-Line unten, Auswahl, Conversion-CTA
- HEX
- #cda857
- RGB
- 205 · 168 · 87
- CMYK
- 21 · 32 · 78 · 0
- Pantone
- 7563 C
Gold-Hover, goldene Schrift auf dunklem Grund
- HEX
- #e9c475
- RGB
- 233 · 196 · 117
Helle Flächen: Print, Social und Präsentationen. Im Screen-UI nicht verwenden.
- HEX
- #e6e3da
- RGB
- 230 · 227 · 218
- CMYK
- 9 · 7 · 12 · 0
Klick auf eine Karte kopiert den HEX-Wert. Wo kein CMYK steht, gibt es die Farbe nur am Bildschirm.
So setzt du die Farben einBlau und Gold zusammen. Sie sind das Duo der Marke und treten immer gemeinsam auf.
Akzente höchstens 5 %. Grün, Orange und Rot nur neben Blau und Gold, nie allein.
Verlauf aus höchstens zwei Farben, nur die vier gezeigten, für Hintergründe. Nie im Logo, nie in Print- und Social-Brand-Lines.
Schwarz und Weiss für Schrift. Flächen sind Primärfarbe oder Neutral.
Kleine Schrift auf Gold ist immer voll deckendes Navy, nie mit opacity abgeschwächt. Vor dem Ausliefern die Farben gegen diese Werte prüfen.
Logos & Verwendung
Logo-Regeln nach TFCZ Brand Manual 2026. Nur die Originaldateien verwenden. Jede Variante lässt sich direkt hier herunterladen.
Primärlogo (Symbol und Wortmarke) ist der Standard. Das Wappen mit «TFCZ» nur bei wenig Platz und in Absprache mit dem TFCZ-Team. Das Symbol allein nur für Favicon und App-Icon. Jede Zeile zeigt eine Farbfassung, gewählt nach dem Hintergrund.
Blau, für hellen Grund
Farbig, für dunklen Grund
Schwarz, einfarbig auf hellem Grund
Schwarz-weiss, für dunklen Grund







Freiraum-Regeln
1Horizontal: Freiraum = Höhe des Z aus der Wortmarke.
2Vertikal: Freiraum = Breite des Z.
3Symbol solo: Freiraum = Abstand zwischen Schildrand und Figur (linke Seite).
Innerhalb der Schutzzone stehen keine Texte, Bilder oder fremden Logos. Im Web ist das Kopf-Logo 38.8px hoch (Mobile 35.2px) und wird nie gestaucht (flex:none, Klick-Verhalten in Sektion 37).




Immer die Originaldatei nehmen und nur proportional skalieren. Keine andere Schrift in der Wortmarke, Symbol und Wortmarke nie gegeneinander verschieben. Genug Kontrast zum Hintergrund: siehe «Welche Version auf welchem Hintergrund» oben.


Typografie
Nunito Sans ist die einzige Schrift der Marke (Extralight bis Black verfügbar; im UI: 400 / 500 / 700 / 900). Fallback ohne Nunito Sans: moderne, geometrische Sans-Serif. Letterspacing nur bei UPPERCASE-Labels (.12–.22em). Kicker-Signature als Section-Eyebrow. Kursiv nur für Zitate, sonst nie: weder in Titeln noch in Buttons, Labels oder Fliesstext. 600 (Semibold) gibt es in der Familie, im Web nutzen wir es nicht; an der Kasse steht es für den normalen Text (Sektion 16).
Nunito Sans ist frei nutzbar (SIL Open Font License). Das Paket enthält Regular, Bold und Black als TTF. Medium (500) für Fliesstext gibt es mit der ganzen Familie bei Google Fonts.
Tokens: Radius · Spacing · Elevation
Konsistente Skalen statt Zufallswerte. Andock-Elemente runden nur die oberen Ecken (6px 6px 0 0). Elevation nur für Schwebendes: e-2/e-3 gehören zu Modals, Popups, sticky Bars und Hover-Lift. Grosse ruhende Container (Sektionen, Inhalts-Boxen, Grids) tragen keine schweren Drop-Shadows (nur 1px-Border + inset-Highlight, max. e-1). Sonst fluten die Schatten die Lücken und decken das animierte Punktfeld zu.
Technik: die Tokens-Datei
Farben, Radien, Schatten, Zeiten und Easing stehen an einer Stelle, in system/tokens.css. Die Datei lädt vor dem seiten-eigenen CSS. So sieht dieselbe Karte auf jeder Seite gleich aus.
Ladereihenfolge
<head>
<link rel="stylesheet" href="system/tokens.css"> <!-- 1. Tokens -->
<style> /* 2. nur noch Seiten-Spezifisches */ </style>
</head>
Kein :root in einer Seite. Referenz ist die Home. Ein geänderter Wert in tokens.css gilt sofort auf allen Seiten.
Regeln
- Neue Tokens gehören in
system/tokens.css, nie in eine Seite. - Eine bewusste Ausnahme pro Seite ist erlaubt (die Seite lädt zuletzt). Sie steht dort explizit, damit sie sichtbar und dokumentiert ist.
- Vor dem Ausliefern zählen: Seiten mit eigenem
:root, Soll 0. - Noch offen: die geteilte Klassen-Bibliothek (
tfcz-base.css). Nur Klasse für Klasse mit Bildvergleich, ein Umbau in einem Schritt hat die Seiten zerlegt.
Grössen & Touch
Wie hoch ein Knopf ist, wie gross die Schrift, wie viel Platz dazwischen: je nach Gerät. Sichtbare Höhe und Tippfläche sind zwei Dinge. Ein Knopf darf kleiner aussehen, als er sich antippen lässt. Die Werte stehen als Tokens in system/tokens.css (--tap-min, --ctl-h…, --pos-…, --fs-…, --fw-…).
| Web (Maus) | Handy & iPad | Kasse (iPad 11" quer) | |
|---|---|---|---|
| Kleinste Tippfläche | 36 px | 44 px | mind. 44 px, unsichtbar erweitert |
| Bedienelement sichtbar | 36 (klein) · 44 | 48 | 34, alle gleich hoch |
| Listenzeile | frei | 44 | 44 |
| Hauptaktion | 52 | 56 | 56 bis 64, volle Spaltenbreite |
| Schrift | 12 · 14 · 16 · 20 · 24 · 32 | wie Web | 14, Nebenzeilen 12, nie unter 11 · Beträge 24 |
| Schriftstärke | 500 bis 700, Titel 900 | wie Web | 500 bis 600 · Hervorhebung 700 · Beträge 800 · nie 900 |
| Abstände | 4 · 8 · 12 · 16 · 24 · 32 | ||
Die Kategorien sind sichtbar 34 px hoch, tippen lässt sich jede auf 44 px. Die Zeilen sind 44 px, die Hauptaktion 64 px über die ganze Breite.
Regeln für alle Geräte
- Tippfläche nie unter 44 px auf Touch-Geräten. Ist ein Knopf sichtbar kleiner, wird die Fläche unsichtbar erweitert (
::beforemit negativeminset). - Alle Bedienelemente in derselben Leiste sind gleich hoch.
- Schrift und Abstände nur von der Skala. Zwischenwerte wie 11, 12.5, 13.5 oder 26 werden beim Umbau auf den nächsten Wert gerundet.
.btn-smnur mit Maus. Auf Touch-Geräten gibt es ihn nicht.- Was man antippt, markiert man nicht. Knöpfe, Chips, Reiter, Schalter und Kacheln tragen
user-select: noneund-webkit-touch-callout: none. Sonst markiert ein Doppelklick die Beschriftung, und auf dem iPad öffnet langes Drücken die Lupe mit «Kopieren».touch-action: manipulationnimmt zusätzlich die 300 ms Pause des Doppeltipp-Zooms. Text, Zahlen, Codes und Adressen bleiben markierbar — sie will man kopieren.
Zusätzlich an der Kasse
- Sichtbar 34 px, Listenzeile 44 px, Tippfläche immer mindestens 44 px. Grössere sichtbare Knöpfe (48 bis 64 px) wirken an der Kasse überdimensioniert. Einzige Ausnahme ist die Hauptaktion (Karte, Twint).
- Schrift höchstens 14 px, Nebenzeilen 12 px, nie unter 11 px. Nur Beträge (Total) sind 24 px.
- Nie Schriftstärke 900. Normal 500 bis 600, Hervorhebung 700, Beträge 800.
- Nichts verschiebt sich beim Umschalten: feste Breiten, derselbe Rand in jeder Ansicht.
- Lucide-Icons nie fett: die Strichstärke bleibt fest (
vector-effect: non-scaling-stroke) und skaliert nicht mit. - Kein Hover: nur
:active, keine Dropdowns, keine schwebenden Overlays. - Dialoge folgen denselben Massen wie die Oberfläche.
- Nur Listen scrollen. Jede Aktion ist ohne Scrollen erreichbar.
- Nur zwei Layouts: hoch und quer, keine weiteren Breakpoints.
Code für Entwickler
/* Kasse: sichtbar 34 px, tippbar 44 px */
.pos-ctl{height:var(--pos-ctl-h); position:relative; font-size:var(--fs-14); font-weight:var(--fw-normal)}
.pos-ctl::before{content:""; position:absolute; left:0; right:0;
top:calc((var(--pos-ctl-h) - var(--tap-min)) / 2); bottom:calc((var(--pos-ctl-h) - var(--tap-min)) / 2)}
.pos-ctl:active{transform:scale(.96)} /* kein :hover an der Kasse */
.pos-row{min-height:var(--pos-row-h)}
.pos-cta{height:var(--pos-cta-h); width:100%}
.pos-amount{font-size:var(--fs-24); font-weight:var(--fw-amount)}
/* Was man antippt, markiert man nicht (Text bleibt markierbar) */
.pos-ctl, .pos-cta, button, [role="button"]{
-webkit-user-select:none; user-select:none; -webkit-touch-callout:none; touch-action:manipulation}
/* Lucide: Strich bleibt fein, egal wie gross das Icon ist */
.pos-ctl svg *{vector-effect:non-scaling-stroke; stroke-width:2}Fenster-Signatur / Brand-Line
Blau oben (#5ca7dc) / Gold unten (#cda857) als border-top/-bottom am abgerundeten Container. An den Ecken läuft die Linie schmaler aus. Bei genau einem Fenster oder sticky Bars: direkt umranden. Bei mehreren Boxen: nur einmal als Seitenrahmen, Boxen dezent.
Einzelnes Fenster
Trägt die Signatur direkt (border-top blau / border-bottom gold, ~3px).
Signatur nur einmal am Seitenrahmen.
Hover vs. Auswahl
Hover = Brand-Rahmen (blau oben / gold unten). Auswahl = Gold-Rahmen. Nie beide gleichzeitig am selben Element. „Heute" = goldene Zahl.
Hover-Brand-Rahmen · nie mit Layout-Sprung
Der Brand-Hover (blaue Linie oben / goldene unten) darf die Box-Grösse niemals verändern. Umgesetzt wird er deshalb mit inset box-shadow.
Auf dem Handy: die Karte antippen.
Code für Entwickler
/* RICHTIG — nur box-shadow, Layout bleibt stabil, folgt der border-radius */ .el{ border:1px solid var(--card-brd); border-radius:8px; } .el:hover{ border-color:transparent; box-shadow: inset 0 2px 0 var(--blue), inset 0 -2px 0 var(--gold); } /* hat das Element schon eine Elevation-Shadow, kombinieren: box-shadow: inset 0 2px 0 var(--blue), inset 0 -2px 0 var(--gold), var(--e-1); */ /* VERBOTEN — border-Dicke im Hover ändert die Boxgrösse, Inhalt springt */ .el:hover{ border-top:2px solid var(--blue); border-bottom:2px solid var(--gold); } /* ebenfalls verboten: border:0 im Grundzustand → border:2px im Hover (auch mit Padding-Kompensation tabu — fehleranfällig) */
Merksatz
Im :hover steht nie ein border-top/border-bottom mit px-Wert. Erlaubt ist nur box-shadow: inset … (plus optional border-color:transparent). Das gilt für den Zurück-Button, Accordion-Header, Chips, Tabs und Cards. Hover-Brand-Rahmen und Gold-Auswahlrahmen nie gleichzeitig am selben Element (Sektion 18). Vor dem Ausliefern jeden neuen Hover gegen Layout-Sprung testen.
Icons · Lucide
Keine Emojis, nirgends im Projekt. Jedes Icon kommt aus dieser Bibliothek. Auch Symbol-Glyphen wie Kreuz, Häkchen, Stern oder Pfeil-runter sind Emoji-Ersatz und verboten. Dafür gibt es x, check, star, download. War ein Zeichen reine Dekoration, wird es ersatzlos gestrichen. SVG braucht immer eine Grössenregel: ersetzt du einen Text-Glyph durch ein Lucide-SVG, muss der Button eine ausdrückliche Regel bekommen, etwa .mein-button svg{width:17px;height:17px;display:block;flex:none}. Ohne sie wird das Icon im Flex-Container auf 0 gequetscht und ist unsichtbar. Prüf deshalb nach jedem Icon-Tausch im Browser, ob das Icon sichtbar ist, das Markup allein sagt das nicht. Alle Lucide-Icons (ISC), geladen aus assets/icons/lucide/. Klick kopiert das SVG (stroke = currentColor).
system/components/icons.js automatisch in das Lucide-Icon um.assets/icons/lucide/<name>.svg, der Maschinen-Index (auch für KI) ist assets/icons/lucide-index.json mit name, tags, use.Diese Icons tragen im TFCZ eine feste Bedeutung (Kasse, Okapi, Website). Wer eines davon braucht, meint genau das. Für eine andere Bedeutung ein anderes Icon wählen. Neue feste Zuordnungen werden hier eingetragen, bevor sie gebaut werden.
Personen & Status
user-keyKeyplayeruser-starMitglied (auch der Artikel «Mitgliedschaft»)user-plusGastusersHelfer (Zahlungsart «Helfer» am Event)
Kassen
TFCZ-WappenClubkasse (TFCZ-Wappen als Outline, kein Lucide)hand-coinsExpresskassecalendarEventkassecalendar-checkgeöffnetes Eventlog-inEvent öffnen
Zustand eines Töggelers (Kachel & Filter)
banknote-xEintritt fehlttrophyTurnier (spielt mit, Turnier-Artikel, Gruppe «Turniere»)shopping-cartBetrag offen / Warenkorbdoor-openAnsicht «Offen» (noch nicht bezahlt)banknote-checkAnsicht «Bezahlt»
Bezahlen
credit-cardKarte (SumUp)smartphoneTwintreceiptAuf Rechnung / Eventabrechnungcircle-checkAls bezahlt markieren (Betrag 0.–)ticket-percentGutscheinhand-heartTrinkgeld-Hinweisfrownkein Trinkgeld
Aktionen
plushinzufügen (immer goldener Knopf)minuseins wenigerxschliessen, entfernen, Suche löschensearchsuchenarrow-leftzurückstarFavoritlayout-gridalle Kategorienlockabschliessen (Club, Event), gesperrtsettingsEinstellungenlog-outabmeldenrotate-cwneu ladentriangle-alertWarnungwifi-offVerbindung weghistoryInaktivität («Noch da?»)
Sortiment · Kategorien
cup-sodaAlkoholfreibeerBierwineWein & Spirituosenutensils-crossedEssen & SnackspackageMaterialtrophyTurniereuser-plusEintritt & Mitgliedschaft
Sortiment · Artikel
faucetWassercitrusPanachécoffeeKaffee/TeebeerBierbottle-wineFlasche WeinwineWeinmartiniSchnapssandwichSandwichhand-platterRaclettecroissantGipfelicake-sliceGebäckcandySchoggiappleFrüchtevolleyballBallribbonGriffbandflameFeuerzeugdoor-openEintrittglass-waterAlkoholfrei / Shot
user-plusGast und Kategorie «Eintritt & Mitgliedschaft».door-openArtikel «Eintritt» und Ansicht «Offen» (noch nicht bezahlt).glass-waterAlkoholfrei oder Shot, unklar. Die Kategorie «Alkoholfrei» ist schon cup-soda.
Bis das entschieden ist, kein weiteres Element mit diesen Icons bauen.
Darstellung: Lucide, Strich fest (nicht mitskalierend), nie fett, Farbe = Textfarbe.
Hintergrund-Animationen & Modi
Zwei Seiten-Hintergründe: Hellblau (öffentlich) & Dunkel-Navy (login-intern). Standard-Effekt ist der Schwerkraft-Wirbel aus dem globalen Modul assets/js/bg-swirl.js. Idle-Verhalten: der Wirbel wirbelt nur bei Mausbewegung. Stoppt die Maus (~140ms), kehren die Punkte langsam & smooth per Genie-Feder (0.010 / 0.91) zurück, und der Loop hält an. Einen Dauer-Idle-Wirbel gibt es nicht. prefers-reduced-motion respektieren. Floating Lines (~116°) bleiben als Alternative erlaubt (Snippets: 01_Web/hintergrund-spielwiese.html). Umschalter oben im Kopf.
Modus (strikt)
Navy = nur interne, login-geschützte Tools (POS, Bar-Display, Livestudio, Design Studio, Cockpit, Admin). Hellblau = jede öffentliche Seite, dort gibt es keinen Umschalter und keinen Dark Mode.
Einbindung (Pflicht)
Jede Seite bindet nur <script defer src="assets/js/bg-swirl.js"></script> ein. Das Modul erstellt Canvas + CSS selbst. Nie inline kopieren, Inline-Kopien veralten. Boxen bleiben dunkel & frosted, ohne schwere Drop-Shadows (Sektion 15).
Print, Social & Icons
Flyer, Stories, Posts und Schilder haben ihren eigenen Look. Er kommt aus den echten Vorlagen des Clubs: dem KäseKick-Flyer und den Schildern im Clublokal.
tfcz.ch

Hellblau-Verlauf als Grund für Flyer, Story und Post
Blau-Verlauf als Grund für Schilder
Foto läuft unten in Navy aus

Wappen als Wasserzeichen, rund 20 %
Brand-Line: abgerundete Balken, oben blau, unten gold
Gold-Verlauf nur auf dem wichtigsten Element
tfcz.chSchilder: Navy-Fuss mit Goldlinie, Logo weiss
Titel in Versalien, zweites Wort in Gold
Farbwerte für Gestalter
| Was | Werte |
|---|---|
| Hellblau-Verlauf (Flyer, Story, Post) | #d3eafa → #a8d3f1 → #7fbbe6 → #5ca7dc → #4489c7, von oben nach unten |
| Blau-Verlauf (Schilder) | #005a94 → #2f73b0 → #4489c7, schräg |
| Gold-Verlauf (Badge) | #f3dea4 → #e9c475 → #cda857, mit weissem Ring |
| Navy (Foto-Auslauf, Schild-Fuss) | #0d273d |
| Farbraum | Druck in CMYK, digital in RGB |
Fotos & Galerie
Alle Vereinsfotos liegen in der Galerie unter design.tfcz.ch/galerie. Dort filterst du nach Kategorie, Turnier, Ort und Trikot und lädst jedes Bild direkt herunter.
Schlumpf zuerst
Ein Schlumpf-Foto zeigt das blaue TFCZ-Trikot mit erkennbarem Logo oder Schriftzug. Ein blaues Shirt ohne Vereinslogo zählt nicht. Für alles, was den Verein nach aussen zeigt, also Website, Posts und Flyer, suchst du zuerst ein Schlumpf-Foto. In der Galerie findest du sie über den Filter «Shirt: Schlumpf».
Ein Bild ohne Trikot nimmst du, wenn es kein passendes Schlumpf-Foto gibt oder das Motiv keins braucht, etwa Location, Detail oder Publikum.
Nie nach aussen
Fotos mit dem Tag schwach: Sie sind unscharf oder zu dunkel.
Bilder, auf denen Spieler anderer Clubs gross im Fokus stehen und nichts auf den TFCZ verweist.
| Zweck | Kategorie in der Galerie | Worauf achten |
|---|---|---|
| Flyer-Hintergrund | Detail & Equipment (Tisch-Nahaufnahme, spielende Hände) | Bei 40 bis 60 % Deckkraft unter den Text legen, damit er lesbar bleibt. |
| Event-Ankündigung | Atmosphäre & Publikum, Location & Bar | Zeigt, wie sich der Abend anfühlt. Ein volles Lokal wirkt stärker als ein leeres. |
| Ergebnis-Post | Mannschaftsfotos, Tag siegerehrung | Schlumpf-Foto zuerst, Gesichter gut erkennbar. |
| Einladung ins Training | Turnier & Match, Team & Emotionen | Echte Spielszenen, Schlumpf-Foto zuerst. |
| Website-Hero | Nur eigene Nikon-Fotos | Immer in der Auflösung full. |
Jedes Foto gibt es zweimal. full (rund 1600 px) nimmst du für grosse Flächen wie Hero, Flyer und Story, thumb für kleine Kacheln, Feeds und Vorschauen. Neue Fotos kommen nur über den Foto-Ablauf in die Galerie, dabei werden Kategorie, Anlass und Ort gesetzt (Sektion 24, «Technik: Bilder auf der Website»).
Technik: Bilder auf der Website und Foto-Manager
Grundregel: Inhaltsbilder werden nie fest im HTML/CSS verdrahtet. Jedes Bild lebt im Galerie-CMS als WebP in zwei Auflösungen: assets/fotos/galerie/full/… (grosse Darstellung, ~1600px: Hero, Sektionsbilder) und assets/fotos/galerie/thumb/… (klein: Strips, Feeds, Karussell-Kacheln). Roh-JPG/PNG gehören nicht direkt in Seiten. Ausnahme: Logos/Embleme/Muster = geteilte Assets in assets/img/.
Wie Bilder auf die Seite kommen (Slots)
<!-- src wird beim Veröffentlichen aus mapping.json ins HTML gebacken (echtes Bild, kein Platzhalter) -->
<img data-slot="index.hero" class="hc-img" src="assets/fotos/galerie/full/795.webp" alt="…">
<div data-slot="index.bg-bar"></div> <!-- Hintergrund-Slot -->
system/tfcz-images.js setzt zur Laufzeit Bild bzw. Background aus assets/fotos/galerie/mapping.json; system/tfcz-carousel.js macht dasselbe für Karussells. mapping.json ist die Laufzeit-Quelle der Wahrheit (Slot → Bild), slots.json hält die Slot-Definitionen, galerie.json den Foto-Katalog. Beim Veröffentlichen bäckt _tools/bake-slots.mjs das gemappte Bild direkt ins src jeder Slot-<img>. So lädt sofort das richtige Bild, ohne Flash und ohne Doppel-Laden; tfcz-images.js ist dann nur noch Fallback.
Bilder ändern nur über den Foto-Manager
- Foto-Manager öffnen:
assets/fotos/galerie/foto-manager.html. - Pro Slot ein Galerie-Foto wählen, in der Auflösung full oder thumb.
- Speichern → schreibt
mapping.json(oder „mapping.json exportieren“ und inassets/fotos/galerie/ersetzen). node _tools/bake-slots.mjslaufen lassen. Das bäckt die Bilder inssrc.- Veröffentlichen, damit es live erscheint.
Neue Fotos kommen ausschliesslich über den tfcz-fotos-Skill in die Galerie: er erzeugt full+thumb-WebP, katalogisiert in galerie.json und fragt vorher nach Event/Kategorie.
Nie
Bausteine für Bildschirme
Alle 120 Bausteine der Website und des Design Studios, live, mit ihren Regeln und «HTML kopieren» oben rechts. Sie stehen in acht Kapiteln; ein Klick öffnet ein Kapitel, das Inhaltsverzeichnis links springt direkt hinein. Aussehen und Verhalten kommen aus system/ui.css, ui.js, charts.js und effects.js, maschinenlesbar stehen alle in system/komponenten.json.
Aktionen & ButtonsKnöpfe, Links und alles, was eine Handlung auslöst.12 Bausteine
CTA-System · Buttons
Wenige Typen, mehr braucht die Seite nicht. Jeder hat genau eine Bedeutung, damit jeder (auch Dritte) weiss, welcher Button wohin gehört und Gold sich nie abnutzt. Alle Buttons kommen aus einem Baustein system/buttons.css; Grösse und Radius stehen als Token in system/tokens.css (siehe unten). Der Text bleibt im HTML.
Hover: Gold, Blau, Rot und Grün heben sich leicht an und leuchten in ihrer Farbe. Ghost-Buttons zeigen den Brand-Rahmen (Sektion 19), Text-Links einen Gold-Unterstrich.
.btn-goldConversion & Lead: die eine wichtigste Handlung, also jemanden in den Verein holen oder eine qualifizierte Anfrage auslösen. Nie für reine Navigation („Alle Regeln", „Komm vorbei") oder Formular-Absenden.
.btn-bluePrimär-Aktion: die normale „Jetzt machen"-Handlung wie absenden, öffnen, erstellen, weiter. Darf mehrfach vorkommen.
.btn-ghostNavigation & Sekundär: weiterführende oder Nebenhandlungen. Steht oft neben Gold/Blau und tritt bewusst zurück.
.btn-blueZurück-Führung: zur Website, eine Ebene zurück oder nach oben. In Blau, damit sie sofort als Knopf erkennbar ist. «Zur Website» steht im Kopf ganz rechts.
.btn-textDas Training ist am Dienstag, weitere Infos stehen auf der Trainingsseite.
Unterstrich .btn-text: im Fliesstext und überall, wo der Link mitten im Satz steht. Der Unterstrich ist immer da, damit man den Link auch auf dem Handy ohne Hover erkennt; beim Hover wird er voll.
Pfeil im Kreis .btn-text.t-arrow: allein stehend als Schluss unter einer Karte oder einem Abschnitt. Beim Hover füllt sich der Kreis und der Pfeil rückt nach rechts. Nie im Fliesstext.
.btn-dangerGefahr & Löschen: nur unumkehrbare/verwerfende Handlungen. Selten, damit Rot ein echtes Warnsignal bleibt.
.btn-successBestätigen & Hinzufügen: positive Bestätigung (nutzt das Brand-Grün --green).
.btn-iconNur-Icon-Button, immer rund. Feste Farb-Semantik: Schliessen/X = Rot (.i-close), Hinzufügen/+ = Grün (.i-add). Sonst neutral (Ghost) oder Blau.
.btn-sm = kompakter (Toolbars, „mehr laden"). .btn-icon = rund, nur Icon. Beide an jeden Typ anhängbar.
.btn-socialSocial-Buttons sind im Ruhezustand immer neutral (frosted) und nehmen ihre Netzwerk-Farbe erst beim Hover an. Fremdfarben passen nicht als Grundfläche ins Brand. Klassen .s-wa / .s-ig / .s-yt / .s-li.
Chips & Toggles sind keine Buttons. Filter-Chips, Sprach-/Ansicht-Umschalter und Profil-Chips behalten bewusst die Pill-Form und tragen kein .btn. Sie folgen --btn-radius nicht. So bleibt der Unterschied „Handlung" (Button) vs. „Auswahl/Filter" (Chip) sichtbar.
system/tokens.css → --btn-radius (Standard 12px), --btn-pad-y/x, --btn-font. Eine Änderung dort gilt auf allen Seiten und hier im Brand Guide. Der Icon-Button bleibt rund.Social-Icons (rund)
Die kleine Form von .btn-social, wenn nur das Icon Platz hat (Footer, Über uns). Weisser Kreis mit Tooltip, beim Hover in der Plattform-Farbe.
Button Group
Zusammengehörige Aktionen in einer Leiste. Die Buttons teilen sich den Rahmen.
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Button Group -->
<div class="k-bgroup" role="group" aria-label="Event bearbeiten">
<button class="btn btn-ghost btn-sm" type="button"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
<path d="m15 5 4 4" /></svg> Bearbeiten</button>
<button class="btn btn-ghost btn-sm" type="button"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg> Duplizieren</button>
<button class="btn btn-ghost btn-sm btn-icon" type="button" aria-label="Weitere"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1" />
<circle cx="19" cy="12" r="1" />
<circle cx="5" cy="12" r="1" /></svg></button>
</div>
Toggle
Ein Knopf, der an oder aus ist. Aktiv trägt er den goldenen Rahmen wie jede Auswahl.
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Toggle -->
<button class="k-toggle" type="button" aria-pressed="false"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg> Nur Favoriten</button>
Kbd
Tastenkürzel im Text, als kleine Tasten gesetzt.
Suche öffnen mit Cmd K
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Kbd -->
<p class="k-p">Suche öffnen mit <kbd class="k-kbd">Cmd</kbd> <kbd class="k-kbd">K</kbd></p>
Rolling Text
Beim Überfahren oder Fokussieren rollt die Beschriftung nach oben weg, eine Kopie rollt von unten nach. Für die wichtigste Aktion einer Seite.
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Rolling Text -->
<button class="btn btn-blue k-roll" type="button" aria-label="Fürs Training anmelden"><span class="k-roll-in" aria-hidden="true"><span>Fürs Training anmelden</span><span>Fürs Training anmelden</span></span></button>
Staggered Rolling Text
Wie Rolling Text, aber Buchstabe für Buchstabe mit kleiner Verzögerung. Gold, weil es hier um die Mitgliedschaft geht.
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Staggered Rolling Text -->
<button class="btn btn-gold k-roll-st" type="button" data-stagger="Mitglied werden" aria-label="Mitglied werden">Mitglied werden</button>
Add to Basket
Nach dem Klick fliegt ein Ball im Bogen zum Zähler, der Zähler federt, der Knopf bestätigt in Grün.
Freitag, 9. Oktober
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Add to Basket -->
<div class="k-atb k-w400">
<div class="k-item k-grow"><span class="k-item-ic"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 14.66v1.626a2 2 0 0 1-.976 1.696A5 5 0 0 0 7 21.978" />
<path d="M14 14.66v1.626a2 2 0 0 0 .976 1.696A5 5 0 0 1 17 21.978" />
<path d="M18 9h1.5a1 1 0 0 0 0-5H18" />
<path d="M4 22h16" />
<path d="M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z" />
<path d="M6 9H4.5a1 1 0 0 1 0-5H6" /></svg></span><div class="k-grow"><b>Plausch-Turnier</b><p class="k-muted">Freitag, 9. Oktober</p></div>
<button class="btn btn-blue btn-sm k-atb-btn" type="button" data-done="Angemeldet"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14" />
<path d="M12 5v14" /></svg><span>Anmelden</span></button></div>
<span class="k-atb-bag" aria-live="polite"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
<path d="M12 11h4" />
<path d="M12 16h4" />
<path d="M8 11h.01" />
<path d="M8 16h.01" /></svg><b class="k-atb-n">0</b><span class="sr-only">Anmeldungen</span></span>
</div>
Hold to Confirm
Für Handlungen, die sich nicht rückgängig machen lassen: gedrückt halten, bis der Balken voll ist. Loslassen bricht ab. Geht auch mit Leertaste oder Enter.
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Hold to Confirm -->
<button class="btn btn-ghost k-hold" type="button" data-done="Gelöscht" data-done-toast="Eintrag gelöscht"><span class="k-hold-fill" aria-hidden="true"></span><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 11v6" />
<path d="M14 11v6" />
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" />
<path d="M3 6h18" />
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg><span class="k-hold-t">Gedrückt halten zum Löschen</span></button>
Anziehungs-Button
Beim Überfahren ziehen sich kleine Punkte in die Mitte des Knopfs, beim Verlassen treiben sie wieder weg.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Anziehungs-Button -->
<button class="btn btn-blue fx-attract" type="button" data-fx="attract"><span class="fx-attract-dots" aria-hidden="true"></span><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 15 4 4" />
<path d="M2.352 10.648a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l6.029-6.029a1 1 0 1 1 3 3l-6.029 6.029a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l6.365-6.367A1 1 0 0 0 8.716 4.282z" />
<path d="m5 8 4 4" /></svg> Mitmachen</button>
Partikel-Button
Ein Klick lässt Punkte in Blau und Gold aus dem Knopf springen, der Zähler steigt.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Partikel-Button -->
<button class="btn btn-ghost fx-part" type="button" data-fx="particles"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5.8 11.3 2 22l10.7-3.79" />
<path d="M4 3h.01" />
<path d="M22 8h.01" />
<path d="M15 2h.01" />
<path d="M22 20h.01" />
<path d="m22 2-2.24.75a2.9 2.9 0 0 0-1.96 3.12c.1.86-.57 1.63-1.45 1.63h-.38c-.86 0-1.6.6-1.76 1.44L14 10" />
<path d="m22 13-.82-.33c-.86-.34-1.82.2-1.98 1.11c-.11.7-.72 1.22-1.43 1.22H17" />
<path d="m11 2 .33.82c.34.86-.2 1.82-1.11 1.98C9.52 4.9 9 5.52 9 6.23V7" />
<path d="M11 13c1.93 1.93 2.83 4.17 2 5-.83.83-3.07-.07-5-2-1.93-1.93-2.83-4.17-2-5 .83-.83 3.07.07 5 2Z" /></svg> Tor! <b class="fx-count">12</b></button>
Tastenkürzel-Button
Knopf mit Tastenkürzel und Glanz beim Überfahren. Cmd K oder Ctrl K löst ihn auch aus.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Tastenkürzel-Button -->
<button class="btn btn-ghost fx-cmd" type="button" data-fx="cmd"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21 21-4.34-4.34" />
<circle cx="11" cy="11" r="8" /></svg> Suchen <kbd class="k-kbd">Cmd</kbd><kbd class="k-kbd">K</kbd></button>
Teilen-Button
Beim Überfahren klappen die Kanäle nacheinander aus dem Knopf. Auf dem Handy per Antippen.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Teilen-Button -->
<div class="fx-share" data-fx="share"><button class="btn btn-ghost" type="button"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="18" cy="5" r="3" />
<circle cx="6" cy="12" r="3" />
<circle cx="18" cy="19" r="3" />
<line x1="8.59" x2="15.42" y1="13.51" y2="17.49" />
<line x1="15.41" x2="8.59" y1="6.51" y2="10.49" /></svg> Teilen</button><span class="fx-share-row"><button type="button" aria-label="Per WhatsApp teilen" data-done="WhatsApp geöffnet"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719" /></svg></button><button type="button" aria-label="Per E-Mail teilen" data-done="E-Mail geöffnet"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" />
<rect x="2" y="4" width="20" height="16" rx="2" /></svg></button><button type="button" aria-label="Link kopieren" data-done="Link kopiert"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></svg></button></span></div>
Formulare & EingabenFelder, Auswahl und alles, was jemand ausfüllt.12 Bausteine
Formular-Elemente
Fokus = Gold-Rahmen (nicht Browser-Blau). Dropdowns: jede Auswahl-Liste nutzt das Custom-Dropdown .csel, eine dunkle Navy-Liste mit Brand-Line (blau oben / gold unten), Drop-Cascade beim Öffnen und Gold-Haken an der gewählten Option; geschlossen wird per Klick daneben oder Esc. Native Aufklapp-Menüs lassen sich nicht im Brand gestalten und sind darum verboten. Umsetzung: tfczEnhanceSelects() (Code in dieser Datei). Der native <select> bleibt unsichtbar im DOM, Formular-Wert und Validierung funktionieren weiter. Chips, Tabs, Toggle.
Technik: der Formular-Baustein
Ein Baustein für alle Formulare. Felder und Mail-Texte stehen in
system/forms.js, das Verhalten in system/components/form.js: rendern,
Pflichtfelder prüfen, Mail bauen, „Mail geöffnet?"-Fallback, Text kopieren. Acht Formulare auf vier
Seiten (fünf Mitglied-Pakete, Training, Firmenevents, Medien).
Live: dasselbe Bauteil wie auf der Website
Die Felder unten kommen aus system/forms.js, nicht aus dieser Datei. Wähle ein Formular, um denselben Wechsel zu sehen, den „Mitglied" bei der Paketwahl macht.
So baut man es ein
<form data-tfcz="form" data-form="training" novalidate>
<div class="fgrid" id="fFields"></div> <!-- Felder kommen vom Baustein -->
<button class="btn btn-gold" type="submit">Als Mail senden</button>
<span class="formerr" id="fErr">Bitte fülle die markierten Pflichtfelder aus.</span>
</form>
<script src="system/forms.js"></script>
<script defer src="system/components/form.js"></script>
Wechselt die Seite das Formular (Mitglied: Paketwahl), setzt sie
data-form um und ruft TFCZ.formUI.neu(form). Neu gerendert werden nur die Felder,
die Verdrahtung bleibt.
Regeln
- Der Absender ist der Kunde. Schlusssätze nie aus Vereinssicht, also „Bitte meldet euch mit Verfügbarkeit und Ablauf". Ein „Wir melden uns …" gehört nicht hinein.
- Nichts darf still verschwinden. Ein optionales Feld, dessen Fehlen eine Aussage ist
(Wunschzeit), bekommt
leerTextund steht dann als „Wunschzeit: noch offen" im Text. - Felder nur in
system/forms.js, nie im HTML. Empfänger kommt aussystem/content.js. - Backend: ist
window.TFCZ_APIgesetzt, geht die Anfrage zusätzlich perPOST /api/forms/<id>an den Server. Der mailto-Weg läuft unabhängig weiter, das Formular funktioniert also immer. Die Schnittstelle beschreibtdocs/API.mdim Website-Repository. - Die Farbe des Absenden-Buttons folgt dem Zweck. Ist das Absenden die
Conversion/Lead (Mitglied, Training, Firmenevent) →
.btn .btn-gold. Ist es eine normale Anfrage (Medien) →.btn .btn-blue. Darum steht in der Live-Demo oben Blau (Medien) und im Einbau-Beispiel Gold (Training). - Toast ist global:
TFCZ.toast('Text kopiert').
Login-Feld (nur App)
Nur im Login der App (app.tfcz.ch/login). Das Label wandert beim Fokus hoch und wird gold, die Unterlinie wird zum Blau→Gold-Verlauf. Auf der Website gelten die Felder oben.
Eingabe-Controls
Die Standard-Controls im TFCZ-Stil (Basis-Satz nach Material, Bootstrap und shadcn). Semantik überall gleich: Häkchen/Bestätigung = Grün · Auswahl = Gold · Aktion = Blau · Fokus = Gold-Rahmen · Hover = Brand-Rahmen nur bei nicht gewählten Elementen (Sektion 18/19).
Umschalter (2–3 Optionen)
Glas-Umschalter mit gleitendem Marker, wenn genau eine von zwei oder drei Optionen gilt.
Tabs / Reiter (Ansichten)
Für den Wechsel ganzer Ansichten/Inhalte. Aktiver Reiter = Frost + 3px blaue Oberkante, sitzt auf der goldenen Grundlinie (Blau oben / Gold unten). Für zwei oder drei Optionen innerhalb einer Ansicht nimmst du den Umschalter daneben.
Field
Label, Feld und Hilfetext als Einheit. Der Hilfetext steht unter dem Feld.
Wir rufen nur an, wenn sich am Termin etwas ändert.
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Field -->
<div class="field k-w400"><label for="k-fd">Telefonnummer</label><input id="k-fd" type="tel" placeholder="079 123 45 67"><p class="k-help">Wir rufen nur an, wenn sich am Termin etwas ändert.</p></div>
Input Group
Feld mit Icon davor und Knopf dahinter, als ein Block.
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Input Group -->
<div class="k-igroup k-w400"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21 21-4.34-4.34" />
<circle cx="11" cy="11" r="8" /></svg><input type="search" placeholder="Mitglied suchen" aria-label="Mitglied suchen"><button class="btn btn-blue btn-sm" type="button">Suchen</button></div>
Input OTP
Code aus sechs Ziffern, je ein Feld. Der Fokus springt beim Tippen weiter.
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Input OTP -->
<div class="k-otp" role="group" aria-label="Bestätigungscode"><input inputmode="numeric" maxlength="1" aria-label="Ziffer 1"><input inputmode="numeric" maxlength="1" aria-label="Ziffer 2"><input inputmode="numeric" maxlength="1" aria-label="Ziffer 3"><span class="k-otp-dash"></span><input inputmode="numeric" maxlength="1" aria-label="Ziffer 4"><input inputmode="numeric" maxlength="1" aria-label="Ziffer 5"><input inputmode="numeric" maxlength="1" aria-label="Ziffer 6"></div>
Combobox
Auswahl mit Suche: tippen filtert die Liste, ein Klick übernimmt den Namen.
Niemand gefunden.
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Combobox -->
<div class="k-pop-wrap k-combo k-w280">
<div class="k-igroup"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21 21-4.34-4.34" />
<circle cx="11" cy="11" r="8" /></svg><input type="text" class="k-combo-in" placeholder="Spieler suchen" autocomplete="off" aria-label="Spieler suchen"></div>
<div class="k-pop k-list" role="listbox">
<button type="button" role="option" class="k-opt" data-value="Anna Keller">Anna Keller<svg class="ki k-opt-ck" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></button><button type="button" role="option" class="k-opt" data-value="Ben Frei">Ben Frei<svg class="ki k-opt-ck" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></button><button type="button" role="option" class="k-opt" data-value="Luca Jost">Luca Jost<svg class="ki k-opt-ck" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></button><button type="button" role="option" class="k-opt" data-value="Mia Brunner">Mia Brunner<svg class="ki k-opt-ck" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></button><button type="button" role="option" class="k-opt" data-value="Nora Egli">Nora Egli<svg class="ki k-opt-ck" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></button><button type="button" role="option" class="k-opt" data-value="Timo Graf">Timo Graf<svg class="ki k-opt-ck" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></button>
<p class="k-empty-line" hidden>Niemand gefunden.</p>
</div>
</div>
Date Picker
Datum über einen kleinen Kalender wählen. Das gewählte Datum steht danach im Knopf.
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Date Picker -->
<div class="k-pop-wrap k-datepick">
<button class="k-selbtn" type="button" data-pop><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 2v4" />
<path d="M16 2v4" />
<rect width="18" height="18" x="3" y="4" rx="2" />
<path d="M3 10h18" /></svg><span class="k-dp-val">Datum wählen</span></button>
<div class="k-pop k-pop-cal"><div class="k-cal"></div></div>
</div>
Questionnaire
Fragen Schritt für Schritt: eine Wahl, mehrere Wahlen und Freitext, der sich überspringen lässt.
Frage 1 von 3
Wie oft spielst du?
Frage 2 von 3
Was interessiert dich?
Frage 3 von 3 · freiwillig
Möchtest du uns noch etwas sagen?
Danke für deine Antworten!
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Questionnaire -->
<div class="k-quest k-w400">
<div class="k-q-prog"><span class="k-q-bar"></span></div>
<div class="k-q-step on" data-q="1"><p class="k-q-n">Frage 1 von 3</p><h4>Wie oft spielst du?</h4>
<div class="k-col" role="radiogroup"><label class="ck rd"><input type="radio" name="k-q1"><span class="bx"><span class="dot"></span></span>Zum ersten Mal</label><label class="ck rd"><input type="radio" name="k-q1" checked><span class="bx"><span class="dot"></span></span>Ab und zu</label><label class="ck rd"><input type="radio" name="k-q1"><span class="bx"><span class="dot"></span></span>Jede Woche</label></div></div>
<div class="k-q-step" data-q="2"><p class="k-q-n">Frage 2 von 3</p><h4>Was interessiert dich?</h4>
<div class="k-chips"><button type="button" class="chip-f on" data-multi>Offenes Training</button><button type="button" class="chip-f" data-multi>Plausch-Turniere</button><button type="button" class="chip-f" data-multi>Kindertraining</button><button type="button" class="chip-f" data-multi>Liga</button></div></div>
<div class="k-q-step" data-q="3"><p class="k-q-n">Frage 3 von 3 · freiwillig</p><h4>Möchtest du uns noch etwas sagen?</h4>
<div class="field"><textarea placeholder="Deine Antwort" aria-label="Deine Antwort"></textarea></div></div>
<div class="k-q-done" hidden><svg class="ki k-q-ok" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" />
<path d="m9 12 2 2 4-4" /></svg><h4>Danke für deine Antworten!</h4></div>
<div class="k-q-nav"><button class="btn btn-ghost btn-sm k-q-back" type="button"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 19-7-7 7-7" />
<path d="M19 12H5" /></svg> Zurück</button><span class="k-grow"></span><button class="btn btn-ghost btn-sm k-q-skip" type="button" hidden>Überspringen</button><button class="btn btn-blue btn-sm k-q-next" type="button">Weiter</button></div>
</div>
Avatar-Wahl
Profilbild in Emblem-Form aus Farben wählen, die Initialen folgen dem eingetippten Namen.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Avatar-Wahl -->
<div class="fx-av" data-fx="avatar-pick">
<div class="fx-av-big" style="background:linear-gradient(155deg,var(--blue-mid),var(--blue-dk))">LJ</div>
<div class="fx-av-opts" role="radiogroup" aria-label="Farbe"><button class="fx-av-opt" type="button" role="radio" aria-checked="true" style="--c:linear-gradient(155deg,var(--blue-mid),var(--blue-dk))">LJ</button><button class="fx-av-opt" type="button" role="radio" aria-checked="false" style="--c:linear-gradient(155deg,var(--gold),#a88635)">LJ</button><button class="fx-av-opt" type="button" role="radio" aria-checked="false" style="--c:linear-gradient(155deg,var(--green),var(--green-dk))">LJ</button><button class="fx-av-opt" type="button" role="radio" aria-checked="false" style="--c:linear-gradient(155deg,#6b7f93,#33475a)">LJ</button></div>
<div class="field"><label for="fx-av-n">Name</label><input id="fx-av-n" type="text" placeholder="Luca Jost"></div>
<button class="btn btn-success btn-sm fx-av-save" type="button"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg> Speichern</button>
</div>
Datei-Upload
Dateien hineinziehen oder anklicken. PDF, JPG und PNG bis 5 MB laden mit Fortschritt hoch, alles andere wird mit Grund abgewiesen.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Datei-Upload -->
<div class="fx-up" data-fx="upload"><div class="fx-drop" role="button" tabindex="0"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v12" />
<path d="m17 8-5-5-5 5" />
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></svg><b>Beleg hierher ziehen</b><span>oder klicken · PDF, JPG, PNG bis 5 MB</span></div><input type="file" multiple hidden accept=".pdf,.jpg,.jpeg,.png"><ul class="fx-files"></ul></div>
Team-Auswahl
Anzahl Personen mit Plus und Minus, die Avatare stapeln sich überlappend und springen federnd dazu.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Team-Auswahl -->
<div class="fx-team" data-fx="team" data-start="2" data-names='["Luca Jost","Mia Brunner","Timo Graf","Nora Egli"]'><span class="fx-team-stack"></span><span class="fx-team-n"></span><button class="btn btn-ghost btn-icon btn-sm fx-team-minus" type="button" aria-label="Eine Person weniger"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14" /></svg></button><button class="btn btn-ghost btn-icon btn-sm fx-team-plus" type="button" aria-label="Eine Person mehr"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14" />
<path d="M12 5v14" /></svg></button></div>
Collapsible
Ein Bereich, der sich auf- und zuklappt, hier für weitere Termine.
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Collapsible -->
<div class="k-coll k-w400">
<div class="k-coll-row"><span><b>Dienstag, 29. September</b> · Offenes Training</span></div>
<div class="k-coll-more" hidden><div class="k-coll-row"><span><b>Samstag, 3. Oktober</b> · Kindertraining</span></div><div class="k-coll-row"><span><b>Freitag, 9. Oktober</b> · Plausch-Turnier</span></div></div>
<button class="btn btn-ghost btn-sm" type="button" data-coll aria-expanded="false"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 15 5 5 5-5" />
<path d="m7 9 5-5 5 5" /></svg> <span>2 weitere Termine</span></button>
</div>
Fenster & RückmeldungenMeldungen, Dialoge und was nach einer Handlung passiert.9 Bausteine
Feedback & Status
Alerts nutzen die Sekundär-Akzente (sparsam, max. ~5% Fläche): Blau = Info · Grün = Erfolg · Orange = Warnung · Rot = Fehler/ungespeichert. Der Spinner trägt die Brand-Line (blau oben / gold unten). Skeletons statt leerer Flächen beim Laden.
Portal-Schliessen
Fenster öffnen und schliessen sofort, ohne Auf-/Zu-Animation, auch bei den Easter Eggs. Baustein: system/components/portal.js. Das Schliessen läuft zentral darüber, damit alle Fenster gleich reagieren. Nur der X-Button federt noch (Hover-Quetschung).
So benutzt man ihn
Code für Entwickler
<script defer src="system/components/portal.js"></script>
// Statt das Fenster selbst zu verstecken — zentral schliessen (sofort, ohne Animation):
TFCZ.portal.close(fensterEl, function () {
overlay.classList.remove('on'); // aufräumen
});fensterEl = die Box (nicht der Backdrop).
Der Callback läuft sofort, ohne Animationsdauer und ohne Zustand.
Live: öffnen und schliessen
Schliessen per X, Esc oder Klick daneben.
Regeln
- Nie ein Fenster selbst ausblenden (
display:none, Klasse entfernen, wegschieben). Jede Schliess-Aktion läuft überTFCZ.portal.close(). - Keine Auf-/Zu-Animation für Fenster: kein Zusammensinken, kein Verblassen, kein Skalieren aus dem Klickpunkt.
- Verdrahtet: Menü-Drawer, Tages-Maske, Video-Modal, Bild-Player, Barometer-Sheet, dieses Inhaltsverzeichnis, das Demo-Popup, die Easter-Egg-Popups und das Design Studio.
- Der X-Button bleibt der Standard-X: runder Kreis, Hover rot, Quetsch-Animation.
Alert Dialog
Rückfrage vor einer Handlung, die sich nicht rückgängig machen lässt. Der gefährliche Knopf ist rot.
Turnier wirklich löschen?
Alle Resultate und Anmeldungen dieses Turniers gehen verloren.
Code für Entwickler
Braucht die drei Stylesheets und portal.js und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/portal.js"></script>
<script defer src="system/components/ui.js"></script>
<!-- Alert Dialog -->
<button class="btn btn-danger" type="button" data-open="k-adlg"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 11v6" />
<path d="M14 11v6" />
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" />
<path d="M3 6h18" />
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg> Turnier löschen</button>
<div class="k-ov" id="k-adlg" role="alertdialog" aria-modal="true" aria-labelledby="k-adlg-t"><div class="modal k-modal">
<h3 id="k-adlg-t">Turnier wirklich löschen?</h3>
<p class="k-muted">Alle Resultate und Anmeldungen dieses Turniers gehen verloren.</p>
<div class="k-mf"><button class="btn btn-ghost btn-sm" type="button" data-close>Abbrechen</button><button class="btn btn-danger btn-sm" type="button" data-close data-toast="Turnier gelöscht">Löschen</button></div>
</div></div>
Sheet
Panel, das von rechts hereinkommt, für Filter oder Details neben dem Inhalt.
Filter
Kategorie
Code für Entwickler
Braucht die drei Stylesheets und portal.js und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/portal.js"></script>
<script defer src="system/components/ui.js"></script>
<!-- Sheet -->
<button class="btn btn-ghost" type="button" data-open="k-sheet"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 5H3" />
<path d="M12 19H3" />
<path d="M14 3v4" />
<path d="M16 17v4" />
<path d="M21 12h-9" />
<path d="M21 19h-5" />
<path d="M21 5h-7" />
<path d="M8 10v4" />
<path d="M8 12H3" /></svg> Filter</button>
<div class="k-ov k-ov-side" id="k-sheet" role="dialog" aria-modal="true" aria-labelledby="k-sheet-t"><div class="k-sheet">
<div class="k-mh"><h3 id="k-sheet-t">Filter</h3><button class="xclose" type="button" data-close aria-label="Schliessen"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18" />
<path d="m6 6 12 12" /></svg></button></div>
<p class="k-lbl">Kategorie</p>
<div class="k-chips" data-single><button type="button" class="chip-f on">Alle</button><button type="button" class="chip-f">Fun</button><button type="button" class="chip-f">Pro</button><button type="button" class="chip-f">Elite</button></div>
<div class="k-grow"></div>
<button class="btn btn-blue" type="button" data-close>Filter anwenden</button>
</div></div>
Drawer
Schublade von unten, vor allem auf dem Handy. Der Griff oben zeigt, dass sie sich wegziehen lässt.
Öffnungszeiten
Code für Entwickler
Braucht die drei Stylesheets und portal.js und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/portal.js"></script>
<script defer src="system/components/ui.js"></script>
<!-- Drawer -->
<button class="btn btn-ghost" type="button" data-open="k-drawer"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="18" x="3" y="3" rx="2" />
<path d="M3 15h18" />
<path d="m9 10 3-3 3 3" /></svg> Öffnungszeiten</button>
<div class="k-ov k-ov-bottom" id="k-drawer" role="dialog" aria-modal="true" aria-labelledby="k-dr-t"><div class="k-drawer">
<span class="k-grip"></span>
<h3 id="k-dr-t">Öffnungszeiten</h3>
<div class="k-kv"><span>Dienstag</span><b>19:00 bis 23:00</b></div><div class="k-kv"><span>Donnerstag</span><b>19:00 bis 23:00</b></div><div class="k-kv"><span>Samstag</span><b>14:00 bis 18:00</b></div>
<button class="btn btn-ghost" type="button" data-close>Schliessen</button>
</div></div>
Popover
Kleiner Kasten am Knopf, für eine kurze Eingabe. Schliesst beim Klick daneben.
Erinnere mich
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Popover -->
<div class="k-pop-wrap">
<button class="btn btn-ghost" type="button" data-pop><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.268 21a2 2 0 0 0 3.464 0" />
<path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></svg> Erinnerung</button>
<div class="k-pop k-pop-pad"><p class="k-lbl">Erinnere mich</p><div class="tabs" data-single><button type="button" class="on">1 Std. vorher</button><button type="button">1 Tag vorher</button></div><button class="btn btn-blue btn-sm" type="button" data-toast="Erinnerung gesetzt" data-popclose>Speichern</button></div>
</div>
Hover Card
Vorschau beim Überfahren eines Namens, mit Emblem-Avatar und Kurzinfo.
Organisiert von @lucajost Luca JostAktivmitglied seit 2019 · Turnierleitung
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Hover Card -->
<p class="k-p">Organisiert von <span class="k-hc-wrap"><a class="k-mention" href="#" onclick="return false">@lucajost</a>
<span class="k-hc" role="tooltip"><span class="k-shield" style="background-image:url(assets/fotos/galerie/full/781.webp)"></span><span><b>Luca Jost</b><span class="k-muted">Aktivmitglied seit 2019 · Turnierleitung</span></span></span></span></p>
Unlock- & Feedback-Animationen
Wiederverwendbare Game-Style-Animationen. Level-Up (z. B. Achievement / Fortschritt) und Sterne-Rating (z. B. Feedback-Bewertung). Keine Emojis, alles SVG/CSS. Klick „Abspielen" zum erneuten Triggern.
Level-Up
Sterne-Rating
Easter-Egg · Emblem-Pop
Belohnungs-Popup der Eiersuche. Klasse .eegg: Ei fällt rein, wackelt zunehmend (Spannung) und knackt dann auf. Das blaue TFCZ-Emblem springt mit Bounce heraus, und Käse regnet herum. Wiederverwendbar für jeden Egg-Fund.
Emblem-Pop

Fund-Logik & Belohnung
Nach dem ersten Fund erscheint der Hinweis, dass ein Login nötig ist, um den Fortschritt zu speichern. Der Hinweis bleibt, bis man angemeldet ist. Ohne Login wird ein Ei beim Refresh wieder aktiv. Ab Login wird der Fortschritt im Profil gespeichert und jeder neue Fund zeigt, wie viele noch offen sind. Motivation (immer sichtbar): 10 gefundene Eier = 1 Gratis-Raclette an einem KäseKick der Wahl.
Käse-Feier & Performance
Die Feier lässt Käse-Emoji (thematisch zum Käsekick) über den Bildschirm regnen. Physik: die Käse fallen und bleiben liegen, ohne Aufspringen und ohne Eigendreh. Anklicken/Wischen schleudert sie hoch (zum Wegspicken/Lesen). Quelle: assets/js/tfcz-fx.js → startCheese().
Performance-Knöpfe (Pflicht bei geräteschwachen Nutzern): Partikel gedeckelt (Mobile ≤600px 120, sonst 200) hält die O(n²)-Kollision günstig · Fallflächen-Canvas dpr 1.5 statt 2 (~1.8× weniger Füllrate) · prefers-reduced-motion überspringt die Feier komplett · Selbst-Stopp: Canvas werden beim Schliessen entfernt (kein Leck, Heap ~1–2 MB). Gemessen 22.07.2026 (Chromium/CDP): Mobile 4× CPU 28→34 fps, 6× 23→30 fps.
Karten & InhalteKarten, Kacheln und wie Inhalte gerahmt werden.18 Bausteine
Cards, Tags & Datenreihen
Immer dunkler Navy-Gradient, leicht transparent + backdrop-blur, nie hell. Angedockte Eyebrow-Tags runden nur oben. Datenreihen: Sieger gold, Verlierer gedimmt.
Standard-Card
--card + 1px Border + e-2. Basis-Baustein.
Akzent-Card
Kategorie-Streifen links (Fun).
Profil-Avatar · Emblem-Form
Profilbilder haben die Form des TFCZ-Emblems: das Foto füllt die echte Emblem-Silhouette, nie rund und ohne Rahmen. Ohne Foto stehen die Initialen auf dunkelblauem Grund.
Kategorie-Badges
So erscheint die Kategorie eines Anlasses im Kalender und bei Turnieren. Die Farben stehen in Sektion 12, die Namen im Lexikon (Sektion 08).
Paket-Karten
pcard mit „Beliebt"-Ribbon & Gold-Check-Bullets. Gold-Rahmen = gewählt.
- 1 Abend testen
- Leihmaterial
- Freies Training
- Alle Turniere
- Eigener Schlüssel
- 24/7 Zugang
Item
Zeile mit Icon, Titel, Beschreibung und einer Aktion rechts.
Freitag, 9. Oktober · 16 Plätze frei
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Item -->
<div class="k-item k-w400"><span class="k-item-ic"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 14.66v1.626a2 2 0 0 1-.976 1.696A5 5 0 0 0 7 21.978" />
<path d="M14 14.66v1.626a2 2 0 0 0 .976 1.696A5 5 0 0 1 17 21.978" />
<path d="M18 9h1.5a1 1 0 0 0 0-5H18" />
<path d="M4 22h16" />
<path d="M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z" />
<path d="M6 9H4.5a1 1 0 0 1 0-5H6" /></svg></span><div class="k-grow"><b>Plausch-Turnier</b><p class="k-muted">Freitag, 9. Oktober · 16 Plätze frei</p></div><button class="btn btn-blue btn-sm" type="button">Anmelden</button></div>
Separator
Trennlinie zwischen Gruppen, waagrecht oder senkrecht. Neutral, nie in Gold.
Tischfussball Club Zürich
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Separator -->
<div class="k-sep-demo">
<div><b>TFCZ</b><p class="k-muted">Tischfussball Club Zürich</p></div>
<hr class="k-sep">
<div class="k-sep-row"><span>Training</span><span class="k-vsep"></span><span>Turniere</span><span class="k-vsep"></span><span>Verein</span></div>
</div>
Aspect Ratio
Rahmen mit festem Seitenverhältnis, hier 16:9. Das Bild füllt ihn, ohne sich zu verzerren.

Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Aspect Ratio -->
<div class="k-ratio"><img src="assets/fotos/galerie/full/795.webp" alt="Spielszene am Tisch im Clublokal" loading="lazy"></div>
Attachment
Datei im Anhang: Typ, Name, Grösse und Upload-Stand, dazu Herunterladen und Entfernen.
240 KB · hochgeladen
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Attachment -->
<div class="k-att k-w400"><span class="k-att-ic"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" />
<path d="M14 2v5a1 1 0 0 0 1 1h5" />
<path d="M10 9H8" />
<path d="M16 13H8" />
<path d="M16 17H8" /></svg></span><div class="k-grow"><b>Anmeldung-Plauschturnier.pdf</b><p class="k-muted k-small">240 KB · hochgeladen</p><div class="k-prog-tr k-thin"><span style="transform:scaleX(1)" class="ok"></span></div></div><button class="btn btn-ghost btn-icon btn-sm" type="button" aria-label="Herunterladen"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 15V3" />
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<path d="m7 10 5 5 5-5" /></svg></button><button class="btn btn-ghost btn-icon btn-sm k-x-red" type="button" aria-label="Entfernen"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18" />
<path d="m6 6 12 12" /></svg></button></div>
Scroll Area
Kasten mit eigener schmaler Bildlaufleiste, wenn der Inhalt höher ist als der Platz.
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Scroll Area -->
<div class="k-scroll k-w280" tabindex="0" aria-label="Turnierliste"><div class="k-scroll-row"><span>Plausch-Turnier</span><span class="k-muted">03.10.</span></div><div class="k-scroll-row"><span>Kindertraining</span><span class="k-muted">04.10.</span></div><div class="k-scroll-row"><span>Clubmeisterschaft</span><span class="k-muted">05.10.</span></div><div class="k-scroll-row"><span>Liga Runde 4</span><span class="k-muted">06.10.</span></div><div class="k-scroll-row"><span>Doppel-Nacht</span><span class="k-muted">07.10.</span></div><div class="k-scroll-row"><span>Sommerfest</span><span class="k-muted">08.10.</span></div><div class="k-scroll-row"><span>Liga Runde 5</span><span class="k-muted">09.10.</span></div><div class="k-scroll-row"><span>Crazy DYP</span><span class="k-muted">10.10.</span></div><div class="k-scroll-row"><span>Jahresabschluss</span><span class="k-muted">11.10.</span></div><div class="k-scroll-row"><span>Weihnachtsturnier</span><span class="k-muted">12.10.</span></div></div>
Resizable
Zwei Bereiche, deren Grenze sich ziehen lässt, auch mit den Pfeiltasten.
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Resizable -->
<div class="k-res k-w400"><div class="k-res-a"><b>Spielplan</b></div><div class="k-res-h" role="separator" aria-orientation="vertical" tabindex="0" aria-label="Grösse ändern" aria-valuenow="50"><span></span></div><div class="k-res-b"><b>Rangliste</b></div></div>
Bento-Raster
Raster aus Kacheln unterschiedlicher Grösse. Beim Sichtbarwerden zählen die Zahlen hoch und die Balken wachsen.
- Di · Offenes Training
- Fr · Plausch-Turnier
- Sa · Kindertraining
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Bento-Raster -->
<div class="fx-bento" data-fx="bento">
<div class="big"><span class="k-kick">Saison 2026</span><span class="num" data-count="108">0</span><span class="lbl2">Mitglieder</span><div class="bars"><i style="--h:0.35"></i><i style="--h:0.5"></i><i style="--h:0.42"></i><i style="--h:0.65"></i><i style="--h:0.58"></i><i style="--h:0.9"></i><i style="--h:0.7"></i></div></div>
<div><span class="num" data-count="64">0</span><span class="lbl2">Turniere dieses Jahr</span></div>
<div><span class="num" data-count="10">0</span><span class="lbl2">Leonhart-Tische</span></div>
<div class="big" style="grid-column:span 3; grid-row:span 1"><span class="lbl2">Diese Woche</span><ul class="tl"><li>Di · Offenes Training</li><li>Fr · Plausch-Turnier</li><li>Sa · Kindertraining</li></ul></div>
</div>
Wende-Karte
Dreht sich beim Überfahren oder Antippen um und zeigt auf der Rückseite, was dazugehört.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Wende-Karte -->
<button class="fx-flip" type="button" data-fx="flip" aria-label="Offenes Training, Details zeigen"><div class="fx-flip-in">
<div class="fx-flip-f" style="background-image:url(assets/fotos/galerie/full/795.webp)"><span class="k-kick">Jeden Dienstag</span><b style="font-size:22px">Offenes Training</b></div>
<div class="fx-flip-b"><b style="color:#fff; font-size:18px">Was dich erwartet</b><ul><li>Tische ab 19 Uhr offen</li><li>Tipps von Nationalspielern</li><li>Keine Anmeldung nötig</li></ul><span class="btn btn-blue btn-sm">Vorbeikommen</span></div>
</div></button>
Karten-Stapel
Drei Karten liegen gestapelt. Ein Klick fächert sie untereinander auf, ein zweiter legt sie wieder zusammen.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Karten-Stapel -->
<button class="fx-stack" type="button" data-fx="stack" aria-expanded="false" aria-label="Mitgliedschaften aufklappen">
<span class="fx-stack-c"><span class="k-kick">Beliebt</span><b>Aktivmitglied</b><span class="k-muted">Training, Turniere, Stimmrecht</span><span class="price">CHF 240</span></span>
<span class="fx-stack-c"><span class="k-kick">Für Junge</span><b>Junior</b><span class="k-muted">Bis 18 Jahre, Kindertraining inklusive</span><span class="price">CHF 90</span></span>
<span class="fx-stack-c"><span class="k-kick">Unterstützen</span><b>Gönner</b><span class="k-muted">Für alle, die den Club tragen wollen</span><span class="price">ab CHF 50</span></span>
</button>
Karten-Karussell
Waagrecht scrollbare Karten mit Bild, Kategorie und Bewertung. Die Pfeile springen je eine Karte weiter.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Karten-Karussell -->
<div class="fx-cc" data-fx="carousel-cards">
<div class="fx-cc-track"><article class="fx-cc-card"><div class="ph" style="background-image:url(assets/fotos/galerie/full/795.webp)"><span class="badge fun">Fun</span></div><div class="bd"><b>Plausch-Turnier</b><span class="rate"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg> 4,8</span></div></article><article class="fx-cc-card"><div class="ph" style="background-image:url(assets/fotos/galerie/full/1050.webp)"><span class="badge elite">Elite</span></div><div class="bd"><b>Clubmeisterschaft</b><span class="rate"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg> 4,9</span></div></article><article class="fx-cc-card"><div class="ph" style="background-image:url(assets/fotos/galerie/full/1051.webp)"><span class="badge event">Event</span></div><div class="bd"><b>Kindertraining</b><span class="rate"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg> 5,0</span></div></article><article class="fx-cc-card"><div class="ph" style="background-image:url(assets/fotos/galerie/full/781.webp)"><span class="badge pro">Pro</span></div><div class="bd"><b>Liga-Abend</b><span class="rate"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg> 4,7</span></div></article><article class="fx-cc-card"><div class="ph" style="background-image:url(assets/fotos/galerie/full/795.webp)"><span class="badge fun">Fun</span></div><div class="bd"><b>Doppel-Nacht</b><span class="rate"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg> 4,6</span></div></article></div>
<div class="fx-cc-nav"><button class="btn btn-ghost btn-icon btn-sm fx-cc-prev" type="button" aria-label="Zurück"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6" /></svg></button><button class="btn btn-ghost btn-icon btn-sm fx-cc-next" type="button" aria-label="Weiter"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6" /></svg></button></div>
</div>
Ring-Karussell
Karten auf einem drehbaren 3D-Ring, für eine kleine Auswahl gleichwertiger Dinge, etwa die Turnierformate. Gedreht wird mit den Pfeilen, den Chips, durch Ziehen oder mit den Pfeiltasten. Ein Klick auf eine seitliche Karte holt sie nach vorn, erst die vordere öffnet ihren Link. Die vordere Karte trägt den Goldrand.
Zwei Blickwinkel: gerade (data-blick="gerade") zeigt den Ring auf Augenhöhe, von oben (data-blick="oben") kippt ihn leicht, damit man sieht, wie viele Karten dazugehören. Gut sind 5 bis 9 Karten. Für lange Listen oder Inhalte, die man vergleichen muss, gilt das Karten-Karussell (82) oder ein Raster.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>. Die Karten bekommen ihre Lage selbst, der Radius richtet sich nach Kartenbreite und Anzahl.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Ring-Karussell · data-blick="gerade" oder "oben" · data-schalter="blick" zeigt den Umschalter · data-picks="aus" blendet die Chips aus -->
<div class="fx-ring" data-fx="ring" data-blick="gerade" data-schalter="blick" aria-label="Turnierformate">
<div class="fx-ring-ring">
<a class="fx-ring-card" href="#k-ring-karussell" data-label="Käsekick"><div class="ph"><img src="assets/fotos/galerie/full/dsc_7265.webp" alt="" loading="lazy"><span class="badge fun">Fun</span></div><div class="bd"><b>Käsekick</b><span>Letzter Freitag im Monat</span></div></a>
<a class="fx-ring-card" href="#k-ring-karussell" data-label="Plausch-Turnier"><div class="ph"><img src="assets/fotos/galerie/full/dsc_7250.webp" alt="" loading="lazy"><span class="badge fun">Fun</span></div><div class="bd"><b>Plausch-Turnier</b><span>Jeden Mittwoch, 20:15</span></div></a>
<a class="fx-ring-card" href="#k-ring-karussell" data-label="Regio Tour Einzel"><div class="ph"><img src="assets/fotos/galerie/full/dsc_6776.webp" alt="" loading="lazy"><span class="badge pro">Pro</span></div><div class="bd"><b>Regio Tour Einzel</b><span>Abends, CHF 10</span></div></a>
<a class="fx-ring-card" href="#k-ring-karussell" data-label="Regio Tour Doppel"><div class="ph"><img src="assets/fotos/galerie/full/dsc_6890.webp" alt="" loading="lazy"><span class="badge pro">Pro</span></div><div class="bd"><b>Regio Tour Doppel</b><span>Abends, CHF 10 pro Person</span></div></a>
<a class="fx-ring-card" href="#k-ring-karussell" data-label="Zürich Open"><div class="ph"><img src="assets/fotos/galerie/full/dsc_6815.webp" alt="" loading="lazy"><span class="badge pro">Pro</span></div><div class="bd"><b>Zürich Open</b><span>Samstags, CHF 1'050 Preisgeld</span></div></a>
<a class="fx-ring-card" href="#k-ring-karussell" data-label="STS · ITSF 250"><div class="ph"><img src="assets/fotos/galerie/full/dsc_6831.webp" alt="" loading="lazy"><span class="badge elite">Elite</span></div><div class="bd"><b>STS · ITSF 250</b><span>Swiss Tablesoccer Series</span></div></a>
<a class="fx-ring-card" href="#k-ring-karussell" data-label="Swiss Open P4P"><div class="ph"><img src="assets/fotos/galerie/full/708.webp" alt="" loading="lazy"><span class="badge elite">Elite</span></div><div class="bd"><b>Swiss Open P4P</b><span>Uitikon Waldegg</span></div></a>
</div>
</div>
Auszahlung
Mehrstufiger Ablauf mit Fortschritt: jeder Schritt dreht, bis er grün abgehakt ist, am Ende zeichnet sich ein Häkchen.
- Beleg geprüft
- Freigegeben
- Überwiesen
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Auszahlung -->
<div class="fx-tr" data-fx="transfer">
<span class="k-kick">Spesenabrechnung</span>
<span class="fx-tr-amt">CHF 184.50</span>
<ol class="fx-tr-steps"><li class="fx-tr-step"><i></i>Beleg geprüft</li><li class="fx-tr-step"><i></i>Freigegeben</li><li class="fx-tr-step"><i></i>Überwiesen</li></ol>
<div class="fx-tr-done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>Auf dem Weg zu dir</div>
<button class="btn btn-success btn-sm fx-tr-btn" type="button">Auszahlung freigeben</button>
</div>
Glas-Karte
Glasfläche über einem Foto: der Hintergrund bricht sich leicht wie hinter echtem Glas. Die Brechung zeigen nur Chromium-Browser, alle anderen zeigen mattes Glas.
Tisch 1 · Beginn 20:30 · Eintritt frei
Zum LivestreamCode für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Glas-Karte -->
<div class="fx-glass-wrap" data-fx="glass" style="background-image:url(assets/fotos/galerie/full/1050.webp)">
<svg width="0" height="0" style="position:absolute" aria-hidden="true"><filter id="fx-liquid"><feTurbulence type="fractalNoise" baseFrequency=".012" numOctaves="2" seed="4"/><feDisplacementMap in="SourceGraphic" scale="28"/></filter></svg>
<div class="fx-glass"><b>Final heute Abend</b><p>Tisch 1 · Beginn 20:30 · Eintritt frei</p><span class="btn btn-blue btn-sm">Zum Livestream</span></div>
</div>
Punkte-Karte
Ein Punkteraster weicht dem Mauszeiger aus und leuchtet in seiner Nähe auf.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Punkte-Karte -->
<div class="fx-panel fx-dots" data-fx="dots"><span class="k-kick">Interaktiv</span><b>Fahr mit der Maus darüber</b><span class="k-muted">Die Punkte weichen aus und kehren zurück.</span></div>
Spotlight-Karten
Ein Lichtkegel folgt dem Zeiger, die Karte kippt leicht mit, die anderen treten zurück.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Spotlight-Karten -->
<div class="fx-spots" data-fx="spotlight"><div class="fx-spot"><span class="k-item-ic"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 2v4" />
<path d="M16 2v4" />
<rect width="18" height="18" x="3" y="4" rx="2" />
<path d="M3 10h18" /></svg></span><b>Training</b><span class="k-muted">Jeden Dienstag offen für alle</span></div><div class="fx-spot"><span class="k-item-ic"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 14.66v1.626a2 2 0 0 1-.976 1.696A5 5 0 0 0 7 21.978" />
<path d="M14 14.66v1.626a2 2 0 0 0 .976 1.696A5 5 0 0 1 17 21.978" />
<path d="M18 9h1.5a1 1 0 0 0 0-5H18" />
<path d="M4 22h16" />
<path d="M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z" />
<path d="M6 9H4.5a1 1 0 0 1 0-5H6" /></svg></span><b>Turniere</b><span class="k-muted">Vom Plausch bis zur Liga</span></div><div class="fx-spot"><span class="k-item-ic"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
<path d="M16 3.128a4 4 0 0 1 0 7.744" />
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
<circle cx="9" cy="7" r="4" /></svg></span><b>Community</b><span class="k-muted">Über 100 Mitglieder</span></div></div>
Beitrag-Karte
Ein Beitrag mit Absender, Text, Foto und Antwort. Das Herz zählt mit und wird rot.
Was für ein Final! Gratulation an Luca und Jan zur Clubmeisterschaft 2026.
Mia B. Verdient, starkes Spiel!
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Beitrag-Karte -->
<article class="fx-post" data-fx="post">
<div class="fx-post-h"><span class="k-shield" style="background-image:url(assets/img/emblem-blue.png); background-size:contain; background-repeat:no-repeat"></span><div><b>TFCZ <svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z" />
<path d="m9 12 2 2 4-4" /></svg></b><span class="k-muted k-small">vor 2 Stunden</span></div></div>
<p>Was für ein Final! Gratulation an Luca und Jan zur Clubmeisterschaft 2026.</p>
<div class="fx-post-ph" style="background-image:url(assets/fotos/galerie/full/1050.webp)"></div>
<div class="fx-post-f"><button class="fx-post-like" type="button" aria-pressed="false" aria-label="Gefällt mir"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5" /></svg><b>38</b></button><span><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719" /></svg> 4</span></div>
<div class="fx-post-reply"><span class="k-shield" style="background-image:url(assets/fotos/galerie/full/781.webp)"></span><p><b style="color:#fff">Mia B.</b> Verdient, starkes Spiel!</p></div>
</article>
Daten & ChartsTabellen, Listen, Kalender und Diagramme.21 Bausteine
Daten & Listen
Tabellen, Avatare und Akkordeons. Akkordeon-Hover: Brand-Rahmen per inset box-shadow (.acc-head), ohne Layout-Sprung (Sektion 19).
| Spieler | Siege | Turnier | Kategorie |
|---|---|---|---|
| O. Grieder | 5 | Zürich Open | Pro |
| M. Sauter | 4 | Käsekick | Fun |
| S. Schöb | 7 | Schweizermeisterschaft | Elite |
Schnuppern gratis, Aktivmitglied CHF 200, Key Player CHF 250 (mit eigenem Schlüssel, 24/7).
Nein, bei uns ist jeder willkommen, vom Anfänger bis zum Weltmeister.
Landenbergstrasse 10, 8037 Zürich. Seit 1990 am selben Ort.
Kalender & Dock-Lupe
Woche = macOS-Dock-Lupe (gehoverter Tag am grössten, Nachbarn geschoben). Monat = Fokuszelle (Gold-Rahmen) zeigt pro Event nur den Kategorie-Badge. Monats-Default = 6 Wochen (aktueller Monat); jeder Klick auf „Mehr laden" hängt +2 Monate an. „Heute" = goldene Zahl.
Der Dock-Effekt steckt in system/components/dock-magnify.js und passt auf jede Reihe mit gleich breiten Kindern: Wochentage, Kacheln, Foto-Streifen, Filter-Chips. Er läuft auf der GPU und ist bei reduzierter Bewegung aus.
Code für Entwickler
<div class="myrow">…gleich breite Kinder…</div>
<script defer src="system/components/dock-magnify.js"></script>
<script>
addEventListener('DOMContentLoaded', function(){
TFCZ.dockMagnify(document.querySelector('.myrow'), {
amp: 0.34, // max. Vergrösserung (+34 %)
sigma: 1.4, // Reichweite in Zellen
spread: true // Nachbarn schieben (Dock) statt nur skalieren
});
});
</script>Kind-CSS: transform-origin:center bottom; will-change:transform;
transition:transform .16s ease. Optionen: fade (entfernte dimmen),
scrollFocus (Touch/Strips: beim Scrollen die Mitte vergrössern). Rückgabe:
.destroy() / .refresh().
Fun-Barometer + Kennwert-Balken
Unten läuft system/components/baro.js, dieselbe Datei wie auf der Home. Web-Pflicht: die vertikale Tube (Spass oben / Wettkampf unten) steht immer zusammen mit dem Beschreibungs-Panel und den Kennwert-Balken. 5 Stufen: Käsekick · Offenes Training · Plausch-Turnier · Pro Turniere · Elite. Auswahl (Hover/Klick) steuert Tube-Level, Kategorie-Akzent und Panel. Die SM gehört zu Elite, ein eigenes „SM-Quali" gibt es nicht. Icons: party-popper / trophy (Lucide, Sektion 20).
So baut man ihn ein
Code für Entwickler
<div data-tfcz="baro"></div>
<script defer src="system/components/baro.js"></script>Die 5 Stufen samt Texten und Kennwerten stehen nur im Baustein, nie ein zweites Barometer bauen. Hover und Auswahl nach Sektion 18. Käsekick (voller Spass) zündet Konfetti; der Info-Knopf öffnet den ausführlichen Text.
Live: der echte Baustein
Data Table
Tabelle zum Sortieren und Auswählen. Klick auf eine Spalte sortiert, die sortierte Spalte ist gold.
| Anna Keller | 14 | 31 | |
| Ben Frei | 9 | 18 | |
| Luca Jost | 17 | 40 | |
| Mia Brunner | 12 | 27 | |
| Nora Egli | 6 | 11 |
0 von 5 ausgewählt · Beispieldaten
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Data Table -->
<div class="k-col k-dt-wrap"><table class="btable k-table k-dt"><thead><tr><th><label class="ck"><input type="checkbox" class="k-dt-all" aria-label="Alle auswählen"><span class="bx"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></span></label></th><th><button type="button" data-sort="1">Name <svg class="ki k-sort-ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21 16-4 4-4-4" />
<path d="M17 20V4" />
<path d="m3 8 4-4 4 4" />
<path d="M7 4v16" /></svg></button></th><th><button type="button" data-sort="2">Spiele <svg class="ki k-sort-ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21 16-4 4-4-4" />
<path d="M17 20V4" />
<path d="m3 8 4-4 4 4" />
<path d="M7 4v16" /></svg></button></th><th class="k-num"><button type="button" data-sort="3">Punkte <svg class="ki k-sort-ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21 16-4 4-4-4" />
<path d="M17 20V4" />
<path d="m3 8 4-4 4 4" />
<path d="M7 4v16" /></svg></button></th></tr></thead>
<tbody><tr><td><label class="ck"><input type="checkbox" aria-label="Anna Keller auswählen"><span class="bx"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></span></label></td><td>Anna Keller</td><td>14</td><td class="k-num">31</td></tr><tr><td><label class="ck"><input type="checkbox" aria-label="Ben Frei auswählen"><span class="bx"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></span></label></td><td>Ben Frei</td><td>9</td><td class="k-num">18</td></tr><tr><td><label class="ck"><input type="checkbox" aria-label="Luca Jost auswählen"><span class="bx"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></span></label></td><td>Luca Jost</td><td>17</td><td class="k-num">40</td></tr><tr><td><label class="ck"><input type="checkbox" aria-label="Mia Brunner auswählen"><span class="bx"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></span></label></td><td>Mia Brunner</td><td>12</td><td class="k-num">27</td></tr><tr><td><label class="ck"><input type="checkbox" aria-label="Nora Egli auswählen"><span class="bx"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></span></label></td><td>Nora Egli</td><td>6</td><td class="k-num">11</td></tr></tbody></table>
<p class="k-muted k-small k-dt-count" data-suffix=" · Beispieldaten">0 von 5 ausgewählt · Beispieldaten</p></div>
Area Chart
Fläche unter der Linie mit Verlauf nach unten. Beim Überfahren zeigt eine Hilfslinie alle Werte des Tages. Beim ersten Sichtbarwerden wischt sich der Chart von links ein.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Area Chart -->
<figure class="kc" data-kc="area"><script type="application/json">{"title": "Besuche im Clublokal", "note": "Beispieldaten", "x": ["KW 27", "KW 28", "KW 29", "KW 30", "KW 31", "KW 32", "KW 33", "KW 34", "KW 35", "KW 36", "KW 37", "KW 38"], "series": [{"name": "Training", "values": [42, 48, 45, 51, 58, 55, 61, 66, 63, 70, 74, 79]}, {"name": "Turniere", "values": [18, 22, 15, 26, 24, 30, 28, 34, 31, 36, 33, 41]}]}</script></figure>
Bar Chart
Balken wachsen gestaffelt von unten. Der höchste Wert ist gold, alle anderen blau.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Bar Chart -->
<figure class="kc" data-kc="bar"><script type="application/json">{"title": "Neue Anmeldungen pro Monat", "note": "Beispieldaten", "x": ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep"], "series": [{"name": "Anmeldungen", "values": [6, 8, 11, 9, 14, 19, 12, 16, 13]}]}</script></figure>
Candlestick Chart
Start, Hoch, Tief und Ende je Zeitraum. Grün ist gestiegen, Rot gesunken; die Kerzen wachsen aus der Mitte.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Candlestick Chart -->
<figure class="kc" data-kc="candlestick"><script type="application/json">{"title": "Rating-Verlauf pro Woche", "note": "Beispieldaten", "x": ["KW 27", "KW 28", "KW 29", "KW 30", "KW 31", "KW 32", "KW 33", "KW 34", "KW 35", "KW 36", "KW 37", "KW 38"], "ohlc": [[1480, 1502, 1471, 1495], [1495, 1510, 1482, 1488], [1488, 1499, 1460, 1466], [1466, 1490, 1458, 1486], [1486, 1528, 1480, 1521], [1521, 1540, 1512, 1517], [1517, 1523, 1494, 1500], [1500, 1531, 1498, 1529], [1529, 1560, 1522, 1552], [1552, 1558, 1530, 1536], [1536, 1570, 1533, 1566], [1566, 1590, 1558, 1581]]}</script></figure>
Choropleth Chart
Fläche nach Wert eingefärbt. Die Kantone stehen als Kacheln in ungefährer Lage, je dunkler, desto mehr.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Choropleth Chart -->
<figure class="kc" data-kc="choropleth"><script type="application/json">{"title": "Mitglieder nach Wohnkanton", "note": "Beispieldaten", "unit": " Mitglieder", "tiles": [{"id": "BS", "label": "Basel-Stadt", "col": 3, "row": 0, "value": 4}, {"id": "BL", "label": "Basel-Land", "col": 3, "row": 1, "value": 3}, {"id": "SH", "label": "Schaffhausen", "col": 5, "row": 0, "value": 2}, {"id": "TG", "label": "Thurgau", "col": 6, "row": 1, "value": 3}, {"id": "ZH", "label": "Zürich", "col": 5, "row": 1, "value": 71}, {"id": "AG", "label": "Aargau", "col": 4, "row": 1, "value": 9}, {"id": "SO", "label": "Solothurn", "col": 2, "row": 1, "value": 1}, {"id": "JU", "label": "Jura", "col": 1, "row": 1, "value": 0}, {"id": "SG", "label": "St. Gallen", "col": 6, "row": 2, "value": 4}, {"id": "AR", "label": "Appenzell", "col": 7, "row": 2, "value": 1}, {"id": "ZG", "label": "Zug", "col": 5, "row": 2, "value": 5}, {"id": "LU", "label": "Luzern", "col": 4, "row": 2, "value": 3}, {"id": "BE", "label": "Bern", "col": 3, "row": 2, "value": 4}, {"id": "NE", "label": "Neuenburg", "col": 1, "row": 2, "value": 1}, {"id": "SZ", "label": "Schwyz", "col": 6, "row": 3, "value": 3}, {"id": "NW", "label": "Nidwalden", "col": 4, "row": 3, "value": 0}, {"id": "OW", "label": "Obwalden", "col": 3, "row": 3, "value": 1}, {"id": "FR", "label": "Freiburg", "col": 2, "row": 3, "value": 1}, {"id": "VD", "label": "Waadt", "col": 1, "row": 3, "value": 2}, {"id": "GL", "label": "Glarus", "col": 7, "row": 3, "value": 1}, {"id": "UR", "label": "Uri", "col": 5, "row": 3, "value": 0}, {"id": "GR", "label": "Graubünden", "col": 7, "row": 4, "value": 2}, {"id": "TI", "label": "Tessin", "col": 6, "row": 4, "value": 1}, {"id": "VS", "label": "Wallis", "col": 3, "row": 4, "value": 1}, {"id": "GE", "label": "Genf", "col": 0, "row": 4, "value": 2}]}</script></figure>
Composed Chart
Balken und Linie auf einer Zeitachse, mit eigener Skala links und rechts.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Composed Chart -->
<figure class="kc" data-kc="composed"><script type="application/json">{"title": "Spiele und Tore pro Abend", "note": "Beispieldaten", "x": ["1.9.", "3.9.", "8.9.", "10.9.", "15.9.", "17.9.", "22.9.", "24.9."], "bars": {"name": "Gespielte Partien", "values": [38, 22, 44, 27, 51, 30, 47, 35]}, "line": {"name": "Tore pro Partie", "values": [8.2, 7.4, 8.9, 7.9, 9.3, 8.1, 8.8, 8.4]}}</script></figure>
Funnel Chart
Stufen, die schmaler werden. Der Tooltip nennt den Anteil der Stufe davor.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Funnel Chart -->
<figure class="kc" data-kc="funnel"><script type="application/json">{"title": "Vom Besuch zur Mitgliedschaft", "note": "Beispieldaten", "items": [{"label": "Website besucht", "value": 1800}, {"label": "Anfrage gesendet", "value": 240}, {"label": "Probetraining", "value": 96}, {"label": "Mitglied geworden", "value": 31}]}</script></figure>
Gauge
Ein Wert auf einer Skala, mit Zeiger. Der Bogen läuft von Blau nach Gold.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Gauge -->
<figure class="kc" data-kc="gauge"><script type="application/json">{"title": "Helfer für das Sommerfest", "note": "Beispieldaten", "value": 27, "max": 36, "label": "eingeteilt"}</script></figure>
Heatmap Chart
Raster aus Wochentag und Uhrzeit, je dunkler, desto mehr los. Die Felder erscheinen diagonal.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Heatmap Chart -->
<figure class="kc" data-kc="heatmap"><script type="application/json">{"title": "Wann ist im Clublokal am meisten los?", "note": "Beispieldaten", "unit": " Personen", "rows": ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"], "cols": ["16 Uhr", "17 Uhr", "18 Uhr", "19 Uhr", "20 Uhr", "21 Uhr", "22 Uhr", "23 Uhr"], "values": [[0, 0, 1, 2, 2, 1, 0, 0], [1, 3, 9, 18, 24, 22, 15, 6], [0, 1, 2, 3, 4, 3, 1, 0], [1, 2, 6, 14, 19, 17, 10, 3], [0, 1, 3, 6, 9, 11, 9, 5], [4, 8, 11, 12, 10, 6, 3, 1], [2, 3, 4, 3, 2, 1, 0, 0]]}</script></figure>
Line Chart
Mehrere Verläufe ohne Fläche. Die Hilfslinie zeigt beide Werte am selben Punkt.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Line Chart -->
<figure class="kc" data-kc="line"><script type="application/json">{"title": "Ranglistenpunkte im Vergleich", "note": "Beispieldaten", "x": ["KW 27", "KW 28", "KW 29", "KW 30", "KW 31", "KW 32", "KW 33", "KW 34", "KW 35", "KW 36", "KW 37", "KW 38"], "series": [{"name": "Luca J.", "values": [112, 118, 121, 119, 127, 134, 131, 138, 142, 140, 149, 153]}, {"name": "Mia B.", "values": [98, 104, 111, 116, 115, 121, 127, 133, 131, 138, 141, 147]}]}</script></figure>
Live Line Chart
Neue Werte laufen jede Sekunde von rechts ein. Der Chart pausiert, sobald er nicht mehr zu sehen ist.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Live Line Chart -->
<figure class="kc" data-kc="live"><script type="application/json">{"title": "Livestream: Zuschauer", "note": "simuliert", "name": "Zuschauer", "start": 60, "min": 20, "max": 120}</script></figure>
Pie Chart
Anteile als Ring mit der Summe in der Mitte. Die Stücke laufen im Uhrzeigersinn ein.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Pie Chart -->
<figure class="kc" data-kc="pie"><script type="application/json">{"title": "Mitglieder nach Art", "note": "Beispieldaten", "centerLabel": "Mitglieder", "items": [{"label": "Aktivmitglied", "value": 64}, {"label": "Passivmitglied", "value": 21}, {"label": "Key Player", "value": 12}, {"label": "Gönner", "value": 11}]}</script></figure>
Profit/Loss Line
Eine Linie um die Nulllinie: darüber grün, darunter rot, jeweils mit Fläche.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Profit/Loss Line -->
<figure class="kc" data-kc="pl"><script type="application/json">{"title": "Kasse: Ergebnis pro Abend", "note": "Beispieldaten", "name": "Ergebnis", "unit": " CHF", "x": ["1.9.", "3.9.", "8.9.", "10.9.", "15.9.", "17.9.", "22.9.", "24.9.", "29.9."], "values": [180, 95, -60, -120, 40, 210, 150, -35, 120]}</script></figure>
Radar Chart
Stärken auf mehreren Achsen, zwei Profile übereinander.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Radar Chart -->
<figure class="kc" data-kc="radar"><script type="application/json">{"title": "Spielerprofil", "note": "Beispieldaten", "axes": ["Pass", "Schuss", "Abwehr", "Ballkontrolle", "Taktik", "Ausdauer"], "series": [{"name": "Luca J.", "values": [82, 91, 64, 77, 70, 58]}, {"name": "Mia B.", "values": [74, 66, 88, 81, 79, 72]}]}</script></figure>
Ring Chart
Mehrere Fortschritte als Ringe ineinander. Die Zahl in der Mitte gehört zum äussersten Ring.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Ring Chart -->
<figure class="kc" data-kc="ring"><script type="application/json">{"title": "Saisonziele", "note": "Beispieldaten", "items": [{"label": "Neue Mitglieder", "value": 31, "max": 40}, {"label": "Turniere", "value": 48, "max": 60}, {"label": "Helferstunden", "value": 410, "max": 800}]}</script></figure>
Sankey Chart
Ströme zwischen Stufen, die Breite zeigt die Menge. Wischt sich von links ein.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Sankey Chart -->
<figure class="kc" data-kc="sankey"><script type="application/json">{"title": "Wie neue Leute zu uns finden", "note": "Beispieldaten", "unit": " Personen", "nodes": [{"id": "web", "label": "Website", "col": 0}, {"id": "fr", "label": "Freunde", "col": 0}, {"id": "tu", "label": "Turnier", "col": 0}, {"id": "pt", "label": "Probetraining", "col": 1}, {"id": "mg", "label": "Mitglied", "col": 2}, {"id": "ns", "label": "Nicht geblieben", "col": 2}], "links": [{"from": "web", "to": "pt", "value": 40}, {"from": "fr", "to": "pt", "value": 38}, {"from": "tu", "to": "pt", "value": 18}, {"from": "pt", "to": "mg", "value": 31}, {"from": "pt", "to": "ns", "value": 65}]}</script></figure>
Scatter Chart
Punkte in zwei Dimensionen. Sie erscheinen nacheinander, der Tooltip nennt Name und Werte.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Scatter Chart -->
<figure class="kc" data-kc="scatter"><script type="application/json">{"title": "Spiele und Siegquote", "note": "Beispieldaten", "xLabel": "Spiele", "yLabel": "Siegquote", "yUnit": " %", "points": [{"label": "Anna K.", "x": 14, "y": 57}, {"label": "Ben F.", "x": 9, "y": 44}, {"label": "Luca J.", "x": 38, "y": 71}, {"label": "Mia B.", "x": 27, "y": 63}, {"label": "Nora E.", "x": 6, "y": 33}, {"label": "Timo G.", "x": 22, "y": 50}, {"label": "Sara L.", "x": 31, "y": 58}, {"label": "Jan M.", "x": 17, "y": 47}, {"label": "Eva R.", "x": 12, "y": 38}, {"label": "Noah S.", "x": 35, "y": 66}]}</script></figure>
Sunburst Chart
Hierarchie als Ringe: innen die Gruppe, aussen ihre Teile.
Code für Entwickler
Braucht die drei Stylesheets und charts.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/charts.js"></script>
<!-- Sunburst Chart -->
<figure class="kc" data-kc="sunburst"><script type="application/json">{"title": "Verkauf an der Bar", "note": "Beispieldaten", "unit": " Stück", "children": [{"label": "Bier", "children": [{"label": "Lager", "value": 180}, {"label": "Weizen", "value": 60}, {"label": "Alkoholfrei", "value": 35}]}, {"label": "Softdrinks", "children": [{"label": "Cola", "value": 120}, {"label": "Eistee", "value": 90}, {"label": "Wasser", "value": 70}]}, {"label": "Snacks", "children": [{"label": "Chips", "value": 65}, {"label": "Nüsse", "value": 30}]}, {"label": "Kaffee", "children": [{"label": "Espresso", "value": 40}, {"label": "Café crème", "value": 25}]}]}</script></figure>
Medien, Chat & KIBilder, Stream, Karte, Unterhaltungen und der Assistent.16 Bausteine
Bild-Player
Klickt man ein Bild einer Galerie an, öffnet sich der Player: grosses Bild, Pfeile, Vorschaubilder unten und das X zum Schliessen. Er kommt aus system/components/lightbox.js, derselben Datei wie auf Home und Training. Eine Bilderstrecke hat immer den Player.




Schliessen per X, Esc oder Klick neben das Bild (Sektion 62).
Code für Entwickler
<!-- 1. Bilder-Gruppe markieren -->
<section data-lightbox>
<img src="assets/fotos/action/action-01.jpg" alt="">
</section>
<!-- 2. Baustein laden -->
<script defer src="system/components/lightbox.js"></script>
<!-- Eigene Drag-Logik (Home-Marquee): data-lightbox-manual setzen und TFCZ.lightbox.openFrom(imgEl) aufrufen -->Getränkeliste
Ein Baustein, eine Liste. Die Struktur steht in system/drinks.js, die Darstellung in system/components/drinks.js. So gibt es vom Sortiment nur eine Fassung.
So baut man sie ein
Code für Entwickler
<!-- Platzhalter — mehr schreibt die Seite nicht -->
<div data-tfcz="drinks" data-snacks="0" data-preis="vor Ort"></div>
<script src="system/drinks.js"></script>
<script defer src="system/components/drinks.js"></script>Optionen: data-snacks="0" (Snacks weglassen)data-preis (Text für noch offene Preise)data-spaltendata-zahlung="1" (Zahlungshinweis darunter).
Live: der echte Baustein
Regeln
- Preise kommen aus der API über
GET {TFCZ_API}/api/drinks. Sie stehen nie im HTML und nie insystem/drinks.js. Dort steht nur die Struktur;preis: nullheisst „kommt vom Server". - Sobald
window.TFCZ_APIgesetzt ist (auch nachträglich), holt der Baustein die Live-Liste und rendert neu. Antwortet der Server nicht, bleibt der Platzhalter stehen. Die Seite bricht nie. - Sortiment ändern = eine Datei:
system/drinks.js. Nie eine Liste in eine Seite kopieren. - Icons = Lucide (
beer,cup-soda,coffee,cookie, Sektion 20). Kategorie-Zeile in Gold, Preis in Gold, offener Preis gedämpft. - Später lesen Bar-Display und Kassensystem dieselbe Quelle.
Stream · Spielstatistik-Widget
Vollbild-Grafik (1920×1080), die im Livestream zwischen den Spielen erscheint: eine Statistik pro Screen, Auto-Wechsel alle 5 Sek. Nur die Zahlen tragen Team-Farbe (Grün vs. Schwarz). Die Tisch-Grafik mit Pfeilen zeigt, wo auf dem Tisch die Aktion passiert. Das verstehen auch Neulinge.
spielstatistik-widget.html · engine.js · pitch.js
Sonst strikt Brand: Navy-Hintergrund, Gold nur Marke/Akzent, Blau für Fenster-Signatur & Feldlinien.
Endstand → Aufbau (Pass 5→3) → Abschluss (Schuss) → Torwartzone → Tor aus dem Mittelfeld → Ballverluste. Ein Screen erscheint nur, wenn Daten da sind.
1 Statistik pro Screen · grosse Zahlen (mobil lesbar) · Tisch + Pfeile zeigen wo · immer beide Richtungen · nur Zahlen grün/schwarz · Namen voll auf dem Screen, Kurzform im Kopf.
Mehrere Stats auf einen Screen · viel Text · Gold für Teams · schwarze Zahl ohne Rand · Pfeil/Highlight in Gold statt Team-Farbe · Assets extern verlinken.
Scoreboard · Ecken-Overlay
Kompaktes Live-Board (Sätze · Tore · Timeouts · Set-/Match-Ball · Turnier-Runde), im Stream unten links. Fenster-Signatur (blau oben / gold unten) auf frosted Navy, Nunito Sans. Team-Identität wie beim Stat-Widget: Grün #57A639 vs. Schwarz #0c0f14 + heller Rand. Team-Farbe tragen nur Name & Tore. Sätze bleiben neutral, Gold nur als Akzent (Turnier-Zeile), Blau für Sätze & Set-Ball. Leader = dezenter Glow, nie Gold.
sop/public/widgets/scoreboard.html · Position per ?position=bottom-left · Daten via WebSocket//state
Possession-Timer · Ballbesitz
Zeigt oben rechts, wie lange ein Stab den Ball hält (3er / 5er / Torwart). Die Anzeige zählt live hoch, friert beim Limit ein und wird rot über dem Limit (3er/Torwart 15s, 5er 10s). Fenster-Signatur (blau oben / gold unten) auf frosted Navy, Nunito Sans, goldenes Eyebrow. Gold nur Akzent, nie für Teams.
sop/public/widgets/possession.html · Position per ?position=top-right · Trigger POSSESSION_TIMER
Kamera-Rahmen · Einzel & Split
Transparentes Overlay für die Kamera-Einblendungen (Player L/R, Rally, Defensive). Es ersetzt die Vollfarbe hinter der Kamera und trägt die Fenster-Signatur blau oben / gold unten, dazu Navy-Rahmen und runde Ecken; die Mitte bleibt durchsichtig für die Kamera. Split = zwei gerahmte Fenster mit Navy-Trenner (neue OBS-Szene). In OBS als Browser-Quelle über die Kamera legen, Grösse = Kamera-Bereich.
sop/public/widgets/camera-frame.html · ?mode=single|split · Feintuning ?lw= ?r= ?m=
Karte · Google Maps
Ein Baustein, beide Seiten. Home und Firmenevents zeigen dieselbe Karte über
system/components/map.js. Es ist Google Maps, keyless (klassischer
output=embed, kein API-Key, keine Kosten), hell, mit Pin direkt an der
Vereinsadresse. Den Pin setzt die Adresse als Suchbegriff (q=), die
Google selbst geocodiert; mit Koordinaten sass er daneben. Die Adresse kommt aus system/content.js
(TFCZ.content.ort.label). loading="lazy" lädt
die Karte erst beim Hinscrollen. Kein Dark Mode, denn dunkles
Google Maps bräuchte die JavaScript-API mit API-Key.
So baut man sie ein
<div data-tfcz="map"></div>
<script src="system/content.js"></script>
<script defer src="system/components/map.js"></script>
Optional pro Platzhalter:
data-q="Strasse 1, 8000 Ort" (Adresse überschreiben) · data-zoom="16".
Zieht der Verein um, ändert man eine Zeile in system/content.js, und beide
Karten folgen.
Live: der echte Baustein
Now playing
Ein Mini-Player öffnet sich zum grossen Player. Bild, Titel und Untertitel wandern dabei an ihre neue Stelle, der Rest blendet ein. Für laufende Inhalte wie den Livestream.
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Now playing -->
<div class="k-np">
<div class="k-np-full" hidden role="region" aria-label="Livestream">
<div class="k-np-top k-np-rest"><span class="k-np-live">Live</span><button class="k-np-close" type="button" aria-label="Verkleinern"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg></button></div>
<span class="k-np-cover" style="background-image:url(assets/fotos/galerie/full/1050.webp)"></span>
<span class="k-np-t">Clubmeisterschaft · Final</span>
<span class="k-np-s">Tisch 1 · 412 schauen zu</span>
<div class="k-np-score k-np-rest"><span>Luca J. & Jan M.</span><b>4 : 3</b><span>Timo G. & Ramon K.</span></div>
<div class="k-prog-tr k-thin k-np-rest"><span style="transform:scaleX(.62)"></span></div>
<a class="btn btn-blue btn-sm k-np-rest" href="#" onclick="return false"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m17 2-5 5-5-5" />
<rect width="20" height="15" x="2" y="7" rx="2" /></svg> Zum Stream</a>
</div>
<button class="k-np-mini" type="button" aria-label="Livestream öffnen">
<span class="k-np-cover" style="background-image:url(assets/fotos/galerie/full/1050.webp)"></span>
<span><span class="k-np-t">Clubmeisterschaft · Final</span><span class="k-np-s">Tisch 1 · 412 schauen zu</span></span>
<svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16.247 7.761a6 6 0 0 1 0 8.478" />
<path d="M19.075 4.933a10 10 0 0 1 0 14.134" />
<path d="M4.925 19.067a10 10 0 0 1 0-14.134" />
<path d="M7.753 16.239a6 6 0 0 1 0-8.478" />
<circle cx="12" cy="12" r="2" /></svg>
</button>
</div>
Karte öffnet sich
Eine Karte wächst aus dem Raster zur Detailansicht und beim Schliessen wieder zurück an ihren Platz. Nur für Inhalte, die sich aus einer Kachel öffnen; Dialoge und Popups schliessen weiterhin sofort.
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Karte öffnet sich -->
<div class="k-xc">
<button class="k-xc-card" type="button"><span class="k-xc-img" style="background-image:url(assets/fotos/galerie/full/1051.webp)"></span><span class="k-xc-k">Samstag, 3. Oktober</span><span class="k-xc-t">Kindertraining</span><template><p>Jeden Samstag von 14 bis 16 Uhr betreuen zwei Trainer eine Gruppe von höchstens zwölf Kindern. Bälle und Tische stellen wir.</p><p>Die ersten zwei Trainings sind gratis, danach gilt der Jahresbeitrag für Junioren.</p><a class="btn btn-blue btn-sm" href="#" onclick="return false">Anmelden</a></template></button><button class="k-xc-card" type="button"><span class="k-xc-img" style="background-image:url(assets/fotos/galerie/full/795.webp)"></span><span class="k-xc-k">Freitag, 9. Oktober</span><span class="k-xc-t">Plausch-Turnier</span><template><p>Doppel mit gelosten Partnern, gespielt wird im Schweizer System und danach im KO. Anmeldung vor Ort bis 19:30.</p><p>Startgeld zehn Franken, für Mitglieder die Hälfte.</p><a class="btn btn-blue btn-sm" href="#" onclick="return false">Anmelden</a></template></button><button class="k-xc-card" type="button"><span class="k-xc-img" style="background-image:url(assets/fotos/galerie/full/781.webp)"></span><span class="k-xc-k">Dienstag, jede Woche</span><span class="k-xc-t">Offenes Training</span><template><p>Ab 19 Uhr sind die Tische für alle offen. Du brauchst keine Anmeldung und keine eigene Ausrüstung.</p><p>Wer mag, bekommt Tipps von Spielerinnen und Spielern aus der Nationalliga.</p><a class="btn btn-blue btn-sm" href="#" onclick="return false">Anmelden</a></template></button>
<div class="k-xc-ov" hidden><div class="k-xc-panel" role="dialog" aria-modal="true" aria-label="Details">
<span class="k-xc-img"></span>
<button class="xclose k-xc-close" type="button" aria-label="Schliessen"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18" />
<path d="m6 6 12 12" /></svg></button>
<div class="k-xc-head"><span class="k-xc-k"></span><span class="k-xc-t"></span></div>
<div class="k-xc-body"></div>
</div></div>
</div>
Foto öffnet sich
Eine Kachel wird zum grossen Bild, über die View-Transition-API des Browsers. Wo der Browser sie nicht kennt, öffnet sich das Bild ohne Übergang.
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Foto öffnet sich -->
<div class="k-vt">
<button class="k-vt-thumb" type="button" data-cap="Spielszene am Tisch" aria-label="Spielszene am Tisch gross anzeigen"><img src="assets/fotos/galerie/full/795.webp" alt="Spielszene am Tisch" loading="lazy"></button><button class="k-vt-thumb" type="button" data-cap="Mannschaft im Clublokal" aria-label="Mannschaft im Clublokal gross anzeigen"><img src="assets/fotos/galerie/full/781.webp" alt="Mannschaft im Clublokal" loading="lazy"></button><button class="k-vt-thumb" type="button" data-cap="Final der Clubmeisterschaft" aria-label="Final der Clubmeisterschaft gross anzeigen"><img src="assets/fotos/galerie/full/1050.webp" alt="Final der Clubmeisterschaft" loading="lazy"></button><button class="k-vt-thumb" type="button" data-cap="Kindertraining" aria-label="Kindertraining gross anzeigen"><img src="assets/fotos/galerie/full/1051.webp" alt="Kindertraining" loading="lazy"></button>
<div class="k-vt-big" hidden role="dialog" aria-modal="true" aria-label="Foto"><button class="xclose k-vt-close" type="button" aria-label="Schliessen"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18" />
<path d="m6 6 12 12" /></svg></button><img alt=""><p class="k-vt-cap"></p></div>
</div>
Message
Eine Nachricht mit Emblem-Avatar, Name, Zeit und Text.
Luca Jost19:42
Tisch 3 ist frei, wer spielt noch eine Runde?
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Message -->
<div class="k-msg k-w400"><span class="k-shield" style="background-image:url(assets/fotos/galerie/full/781.webp)"></span><div><p class="k-msg-h"><b>Luca Jost</b><span class="k-muted k-small">19:42</span></p><p>Tisch 3 ist frei, wer spielt noch eine Runde?</p></div></div>
Bubble
Nachricht als Sprechblase. Eigene rechts in Blau, andere links in Frost, Reaktionen darunter.
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Bubble -->
<div class="k-bubbles k-w400"><div class="k-bub them">Kommst du heute ins Training?</div><div class="k-bub me">Ja, ab 19:30 bin ich da.<span class="k-react"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z" />
<path d="M7 10v12" /></svg> 2</span></div></div>
Marker
Hinweis mitten im Gespräch: Tagestrenner oder Systemnotiz, ruhig und zentriert.
Mia Brunner ist der Gruppe beigetreten
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Marker -->
<div class="k-w400"><div class="k-marker"><span>Heute</span></div><p class="k-sysnote"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<line x1="19" x2="19" y1="8" y2="14" />
<line x1="22" x2="16" y1="11" y2="11" /></svg> Mia Brunner ist der Gruppe beigetreten</p></div>
Message Scroller
Chatverlauf zum Scrollen. Wer nach oben scrollt, bekommt einen Knopf zurück zur neuesten Nachricht.
Code für Entwickler
Braucht die drei Stylesheets und ui.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/ui.js"></script>
<!-- Message Scroller -->
<div class="k-ms k-w400"><div class="k-ms-list">
<div class="k-bub them">Wer bringt am Freitag die Bälle mit?</div><div class="k-bub me">Ich nehme zwei Sets mit.</div><div class="k-bub them">Super, danke!</div><div class="k-bub them">Beginnen wir um 19 Uhr?</div><div class="k-bub me">Ja, Auslosung um 19:15.</div><div class="k-bub them">Wie viele Anmeldungen haben wir?</div><div class="k-bub me">Aktuell 14 Teams.</div><div class="k-bub them">Dann brauchen wir alle vier Tische.</div><div class="k-bub me">Sind reserviert.</div>
</div><button class="k-ms-jump" type="button" hidden><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14" />
<path d="m19 12-7 7-7-7" /></svg> Neueste</button></div>
KI-Eingabe
Eingabe für den TFCZ-Assistenten: wächst mit dem Text, das Thema lässt sich umschalten, Enter sendet. Der Senden-Knopf ist erst aktiv, wenn etwas dasteht.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- KI-Eingabe -->
<div class="fx-ai" data-fx="ai-prompt">
<textarea rows="1" placeholder="Frag den TFCZ-Assistenten …" aria-label="Frage an den Assistenten"></textarea>
<div class="fx-ai-bar"><button class="fx-ai-chip fx-ai-topic" type="button" data-topics="Allgemein|Training|Turniere|Kasse"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z" />
<circle cx="7.5" cy="7.5" r=".5" fill="currentColor" /></svg><span>Allgemein</span></button><button class="fx-ai-chip" type="button" aria-label="Datei anhängen"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551" /></svg></button><button class="fx-ai-send" type="button" aria-label="Senden"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12 7-7 7 7" />
<path d="M12 19V5" /></svg></button></div>
</div>
KI-Suche
Wie die KI-Eingabe, dazu ein Schalter, der die Suche auf das Regelwerk ausdehnt. Eingeschaltet trägt er den goldenen Rahmen.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- KI-Suche -->
<div class="fx-ai" data-fx="ai-search">
<textarea rows="1" placeholder="Frag den TFCZ-Assistenten …" aria-label="Suche"></textarea>
<div class="fx-ai-bar"><button class="fx-ai-chip fx-ai-web" type="button" aria-pressed="false"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 7v14" />
<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z" /></svg><span>Regelwerk</span></button><button class="fx-ai-chip" type="button" aria-label="Datei anhängen"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551" /></svg></button><button class="fx-ai-send" type="button" aria-label="Suchen"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21 21-4.34-4.34" />
<circle cx="11" cy="11" r="8" /></svg></button></div>
</div>
KI arbeitet
Der Assistent zeigt Schritt für Schritt, was er gerade tut, mit dem TFCZ-Spinner. Erledigtes bekommt ein grünes Häkchen.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- KI arbeitet -->
<div class="fx-ai-load" data-fx="ai-loading" data-steps='["Kalender lesen","Freie Tische prüfen","Anmeldungen zählen","Antwort schreiben"]'>
<span class="spin" aria-hidden="true"></span>
<ol class="fx-ai-log" aria-live="polite"></ol>
</div>
KI denkt nach
Kurzer Hinweis, solange eine Antwort entsteht. Der Text wechselt und glänzt dabei leicht.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- KI denkt nach -->
<div class="fx-ai-think" data-fx="ai-thinking" data-steps='["Denkt nach …","Sucht im Kalender …","Formuliert die Antwort …"]' role="status"><span class="spin fx-spin-sm"></span><span class="fx-shimmer">Denkt nach …</span></div>
Spracheingabe
Tippen startet die Aufnahme: der Knopf wird rot und pulsiert, die Balken zeigen den Pegel, die Zeit läuft mit. Nochmals tippen beendet.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Spracheingabe -->
<div class="fx-voice" data-fx="ai-voice"><button class="fx-voice-btn" type="button" aria-pressed="false" aria-label="Spracheingabe"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19v3" />
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
<rect x="9" y="2" width="6" height="13" rx="3" /></svg></button><div class="fx-voice-bars" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div><span class="fx-voice-time">00:00</span><span class="fx-voice-lbl">Zum Sprechen tippen</span></div>
EffekteBewegung für Flächen, Texte und das Scrollen. Sparsam und nur mit Grund einsetzen.19 Bausteine
Schwerkraft-Wirbel
Der Standard-Hintergrund aller TFCZ-Seiten: ein Punkteraster, das um den Mauszeiger wirbelt und danach sanft zurückkehrt. Einschalten und mit der Maus über die ganze Seite fahren. Er kommt aus dem Baustein assets/js/bg-swirl.js, der Canvas und CSS selbst anlegt; er wird nie pro Seite kopiert. Auf dem Handy und bei «Bewegung reduzieren» ist er aus.
Code für Entwickler
Nur diese eine Zeile, der Baustein legt alles Weitere selbst an. Die Einbindung steht einmal im <head>.
<!-- im <head>: Schwerkraft-Wirbel -->
<script defer src="assets/js/bg-swirl.js"></script>
Linien-Hintergrund
Fliessende Linien in Blau und Gold, die sich langsam weiterzeichnen. Nur innerhalb einer Fläche wie einer Hero-Kachel; der Seitenhintergrund bleibt der Schwerkraft-Wirbel.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Linien-Hintergrund -->
<div class="fx-panel fx-bg fx-paths" data-fx="bg-paths"><svg viewBox="-380 -60 1064 620" preserveAspectRatio="xMidYMid slice" aria-hidden="true"></svg><div class="fx-bg-txt"><b>Clubmeisterschaft 2026</b><span>Samstag, 14. November</span></div></div>
Lichtstrahlen
Schräge Lichtstrahlen, die langsam durch die Fläche ziehen. Leise dosiert, damit der Text lesbar bleibt.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Lichtstrahlen -->
<div class="fx-panel fx-bg" data-fx="bg-beams"><div class="fx-bg-txt"><b>Livestream</b><span>Jeden Freitag ab 20 Uhr</span></div></div>
Strömungsfeld
Punkte folgen einem Strömungsfeld und hinterlassen Spuren, wie Bälle, die über den Tisch laufen.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Strömungsfeld -->
<div class="fx-panel fx-bg" data-fx="bg-flow"><div class="fx-bg-txt"><b>Töggele in Zürich</b><span>seit 1990</span></div></div>
Formen-Hero
Durchscheinende Formen fallen beim Sichtbarwerden ein und schweben danach leicht, dann erscheint der Text.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Formen-Hero -->
<div class="fx-panel fx-bg fx-shapes" data-fx="bg-shapes">
<span class="fx-shape" style="--r:-14deg; width:260px; height:70px; left:-40px; top:30px; background:rgba(92,167,220,.12)"></span>
<span class="fx-shape" style="--r:12deg; width:200px; height:56px; right:-30px; top:60px; background:rgba(205,168,87,.12)"></span>
<span class="fx-shape" style="--r:-8deg; width:160px; height:44px; left:18%; bottom:22px; background:rgba(92,167,220,.1)"></span>
<span class="fx-shape" style="--r:20deg; width:110px; height:34px; right:14%; bottom:36px; background:rgba(205,168,87,.1)"></span>
<div class="fx-bg-txt fx-shape-txt"><b>Komm an den Tisch</b><span>Offenes Training jeden Dienstag</span></div>
</div>
Wechselnder Gruss
Ein Wort wechselt alle zwei Sekunden, hier der Gruss in den Landessprachen.
Grüeziam Tisch
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Wechselnder Gruss -->
<p class="fx-txt fx-dyn" data-fx="dynamic" data-words='["Grüezi","Bonjour","Buongiorno","Allegra","Hello"]' aria-live="polite"><span class="fx-dyn-w">Grüezi</span><span>am Tisch</span></p>
Glitch-Text
Kurze Störung in Blau und Gold, alle paar Sekunden. Sparsam einsetzen, etwa für ein Event mit Technik-Thema.
LIVESTREAM
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Glitch-Text -->
<p class="fx-txt"><span class="fx-glitch" data-text="LIVESTREAM">LIVESTREAM</span></p>
Matrix-Text
Buchstaben laufen als Nullen und Einsen durch, bis sie stehen. Ein Klick spielt es erneut.
Tischfussball Club Zürich
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Matrix-Text -->
<p class="fx-txt fx-matrix" data-fx="matrix" data-text="Tischfussball Club Zürich" title="Nochmals abspielen"><span class="fx-matrix-t">Tischfussball Club Zürich</span></p>
Scroll-Wörter
Beim Scrollen durch die Liste leuchtet das Wort in der Mitte gold auf.
Wir sind
- offen
- ehrgeizig
- herzlich
- laut
- fair
- schnell
- zusammen
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Scroll-Wörter -->
<div class="fx-sw" data-fx="scroll-words"><p class="fx-txt" style="margin:0">Wir sind</p><ul class="fx-sw-box" tabindex="0" aria-label="Wörter"><li>offen</li><li>ehrgeizig</li><li>herzlich</li><li>laut</li><li>fair</li><li>schnell</li><li>zusammen</li></ul></div>
Glanz-Text
Ein goldener Glanz läuft in Schleife über den Text. Für eine einzelne Überschrift, nie für Fliesstext.
Clubmeisterschaft 2026
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Glanz-Text -->
<p class="fx-txt"><span class="fx-shim-txt">Clubmeisterschaft 2026</span></p>
Geteilter Text
Der Text liegt in zwei versetzten Hälften und fügt sich beim Überfahren zusammen.
Komm an den Tisch
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Geteilter Text -->
<p class="fx-txt"><span class="fx-slice" data-text="Komm an den Tisch">Komm an den Tisch</span></p>
Schatten-Text
Mehrere farbige Schatten in Blau und Gold, die beim Überfahren flach zusammenschnappen.
TÖGGELE!
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Schatten-Text -->
<p class="fx-txt"><span class="fx-swoosh">TÖGGELE!</span></p>
Schreibmaschine
Schreibt Wörter nacheinander, löscht sie wieder und beginnt von vorn, mit blinkendem Cursor.
Bei uns:
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Schreibmaschine -->
<p class="fx-txt" data-fx="typewriter" data-words='["Offenes Training","Plausch-Turniere","Kindertraining","Livestream"]'>Bei uns: <span class="fx-tw-t" style="color:var(--gold-lt)"></span><span class="fx-tw-c"></span></p>
Invertierender Text
Weisse Schrift, die sich über mix-blend-mode: difference aus dem Hintergrund rechnet: über Dunklem bleibt sie hell, über Hellem wird sie dunkel, und an einer Kante kippt sie mitten im Wort. Eine Option für grosse Überschriften über Fotos, sparsam eingesetzt.
Die Farbe des Textes entsteht aus dem Hintergrund und steht darum nicht im Brand. Erlaubt ist er nur in Weiss, über Fotos oder über Flächen, die klar hell oder klar dunkel sind. Nicht auf Blau, Gold oder den Marken-Verläufen und nicht über Mitteltönen, dort wird er unlesbar. Den Kontrast immer am echten Bild prüfen.
Wo Zürich töggelt
Töggele
Töggele
Töggele
Töggele
Code für Entwickler
Nur CSS, kein JavaScript. isolation: isolate am Behälter sorgt dafür, dass nur mit der eigenen Fläche gerechnet wird. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Invertierender Text · Hintergrund über style oder eigene Klasse -->
<div class="fx-inv fx-inv-hero" style="background-image:url(assets/fotos/galerie/full/dsc_6815.webp)"><p class="fx-inv-t">Wo Zürich töggelt</p></div>
Scroll-Zeitstrahl
Die Linie füllt sich im Takt des Scrollens, erreichte Punkte werden gold. Für Abläufe und Geschichte. Scrollen bleibt frei, die Seite reagiert nur darauf.
- 18:30TüröffnungAnmeldung an der Kasse
- 19:00AuslosungDie Teams stehen fest
- 19:15VorrundeSchweizer System, vier Runden
- 21:30KO-PhaseDie besten acht spielen weiter
- 23:00SiegerehrungPreise und ein Bier an der Bar
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Scroll-Zeitstrahl -->
<div class="fx-stl" data-fx="scroll-timeline"><div class="fx-stl-track"><span class="fx-stl-fill"></span></div><ol>
<li><span class="t">18:30</span><b>Türöffnung</b>Anmeldung an der Kasse</li><li><span class="t">19:00</span><b>Auslosung</b>Die Teams stehen fest</li><li><span class="t">19:15</span><b>Vorrunde</b>Schweizer System, vier Runden</li><li><span class="t">21:30</span><b>KO-Phase</b>Die besten acht spielen weiter</li><li><span class="t">23:00</span><b>Siegerehrung</b>Preise und ein Bier an der Bar</li>
</ol></div>
Angeheftete Szene
Die Bühne bleibt stehen, während man scrollt, und wechselt Schritt für Schritt. Hier im Kasten; auf einer Seite läuft es über die ganze Höhe.
Im Kasten scrollen.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Angeheftete Szene -->
<div class="fx-pin" data-fx="pin-scene"><div class="fx-pin-box" tabindex="0" aria-label="Scroll durch die Schritte"><div class="fx-pin-track"><div class="fx-pin-stage">
<div class="fx-pin-step on"><span class="n">01</span><b>Anmelden</b><span class="k-muted">An der Kasse oder online, bis 19:00.</span></div><div class="fx-pin-step"><span class="n">02</span><b>Auslosen</b><span class="k-muted">Die Software lost die Paarungen der ersten Runde.</span></div><div class="fx-pin-step"><span class="n">03</span><b>Spielen</b><span class="k-muted">Aufgerufen wird per Push, direkt an deinen Tisch.</span></div>
<div class="fx-pin-dots" aria-hidden="true"><i class="on"></i><i></i><i></i></div>
</div></div></div><div class="fx-pin-bar" aria-hidden="true"><i></i></div><p class="fx-pin-hint">Im Kasten scrollen.</p></div>
Linie zeichnet sich
Eine Linie zeichnet sich beim Sichtbarwerden, am Ziel springt die Markierung auf. Für Wege, Verbindungen und Abläufe.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Linie zeichnet sich -->
<div class="fx-map" data-fx="draw-line"><button class="kc-replay fx-replay" type="button" aria-label="Neu abspielen"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
<path d="M3 3v5h5" /></svg></button>
<svg viewBox="0 0 520 260" role="img" aria-label="Weg vom Bahnhof Wipkingen zum Clublokal">
<path class="street" d="M-10 200 L530 150"/><path class="street" d="M150 -10 L190 270"/><path class="street" d="M-10 70 L530 40"/><path class="street" d="M380 -10 L360 270"/>
<path class="rail" d="M-10 232 L530 186"/>
<circle class="stn" cx="120" cy="214" r="9"/><text x="68" y="248">Bahnhof Wipkingen</text>
<path class="fx-route" d="M120 214 C 150 205, 170 190, 178 160 S 190 90, 250 72 S 350 60, 368 110"/>
<g class="fx-route-pin"><path class="pin-body" d="M368 110 c-10 -14 -14 -20 -14 -27 a14 14 0 0 1 28 0 c0 7 -4 13 -14 27z"/><circle cx="368" cy="83" r="5" fill="#0d273d"/></g>
<text x="392" y="90">Clublokal</text><text x="392" y="106" style="font-weight:700; fill:#c3d2e0; font-size:11.5px">Landenbergstrasse 10</text>
</svg>
</div>
Ball auf der Bahn
Ein Element folgt einer vorgezeichneten Kurve: der Ball läuft vom Mittelfeld über die Stürmer ins Tor.
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Ball auf der Bahn -->
<div class="fx-table" data-fx="motion-path" role="img" aria-label="Ball läuft über den Tisch ins Tor"><button class="kc-replay fx-replay" type="button" aria-label="Neu abspielen"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
<path d="M3 3v5h5" /></svg></button>
<span class="fx-rod" style="left:18%"><span class="fx-man g" style="top:calc(30% - 11px)"></span><span class="fx-man g" style="top:calc(50% - 11px)"></span><span class="fx-man g" style="top:calc(70% - 11px)"></span></span><span class="fx-rod" style="left:32%"><span class="fx-man " style="top:calc(25% - 11px)"></span><span class="fx-man " style="top:calc(45% - 11px)"></span><span class="fx-man " style="top:calc(65% - 11px)"></span><span class="fx-man " style="top:calc(85% - 11px)"></span></span><span class="fx-rod" style="left:50%"><span class="fx-man g" style="top:calc(20% - 11px)"></span><span class="fx-man g" style="top:calc(35% - 11px)"></span><span class="fx-man g" style="top:calc(50% - 11px)"></span><span class="fx-man g" style="top:calc(65% - 11px)"></span><span class="fx-man g" style="top:calc(80% - 11px)"></span></span><span class="fx-rod" style="left:68%"><span class="fx-man " style="top:calc(30% - 11px)"></span><span class="fx-man " style="top:calc(70% - 11px)"></span></span><span class="fx-rod" style="left:82%"><span class="fx-man g" style="top:calc(50% - 11px)"></span></span>
<span class="fx-goal-mouth"></span><span class="fx-ball"></span><span class="fx-goal">Tor!</span>
</div>
Sortierbare Liste
Einträge am Griff ziehen, die anderen gleiten an ihren Platz. Mit der Tastatur: Griff fokussieren und Pfeil hoch oder runter.
- 1Punkte
- 2Buchholz
- 3Torverhältnis
- 4Siege
- 5Erzielte Tore
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Sortierbare Liste -->
<div class="fx-sort" data-fx="sortable"><ol><li><span class="fx-sort-n">1</span><b>Punkte</b><button class="fx-sort-h" type="button" aria-label="Punkte verschieben"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="9" cy="12" r="1" />
<circle cx="9" cy="5" r="1" />
<circle cx="9" cy="19" r="1" />
<circle cx="15" cy="12" r="1" />
<circle cx="15" cy="5" r="1" />
<circle cx="15" cy="19" r="1" /></svg></button></li><li><span class="fx-sort-n">2</span><b>Buchholz</b><button class="fx-sort-h" type="button" aria-label="Buchholz verschieben"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="9" cy="12" r="1" />
<circle cx="9" cy="5" r="1" />
<circle cx="9" cy="19" r="1" />
<circle cx="15" cy="12" r="1" />
<circle cx="15" cy="5" r="1" />
<circle cx="15" cy="19" r="1" /></svg></button></li><li><span class="fx-sort-n">3</span><b>Torverhältnis</b><button class="fx-sort-h" type="button" aria-label="Torverhältnis verschieben"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="9" cy="12" r="1" />
<circle cx="9" cy="5" r="1" />
<circle cx="9" cy="19" r="1" />
<circle cx="15" cy="12" r="1" />
<circle cx="15" cy="5" r="1" />
<circle cx="15" cy="19" r="1" /></svg></button></li><li><span class="fx-sort-n">4</span><b>Siege</b><button class="fx-sort-h" type="button" aria-label="Siege verschieben"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="9" cy="12" r="1" />
<circle cx="9" cy="5" r="1" />
<circle cx="9" cy="19" r="1" />
<circle cx="15" cy="12" r="1" />
<circle cx="15" cy="5" r="1" />
<circle cx="15" cy="19" r="1" /></svg></button></li><li><span class="fx-sort-n">5</span><b>Erzielte Tore</b><button class="fx-sort-h" type="button" aria-label="Erzielte Tore verschieben"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="9" cy="12" r="1" />
<circle cx="9" cy="5" r="1" />
<circle cx="9" cy="19" r="1" />
<circle cx="15" cy="12" r="1" />
<circle cx="15" cy="5" r="1" />
<circle cx="15" cy="19" r="1" /></svg></button></li></ol><p class="sr-only fx-sort-live" aria-live="polite"></p></div>
Wort-Reveal
Die Überschrift steigt Wort für Wort aus einer unsichtbaren Linie auf, sobald sie ins Bild kommt. Für eine Hauptüberschrift je Seite.
Der grösste Tischfussball-Verein der Schweiz
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Wort-Reveal -->
<div class="fx-wr" data-fx="word-reveal"><button class="kc-replay fx-replay" type="button" aria-label="Neu abspielen"><svg class="ki" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
<path d="M3 3v5h5" /></svg></button><p class="fx-wr-t">Der grösste Tischfussball-Verein der Schweiz</p></div>
Einblenden beim Scrollen
Ein Element steigt auf und wird sichtbar, während es ins Bild scrollt, und steht fest, sobald es ein knappes Drittel weit drin ist. Reines CSS über animation-timeline: view(), ohne JavaScript. Anwählen mit der Klasse fx-rise, gestaffelt mit d1 und d2.
Sparsam einsetzen: am Anfang eines Abschnitts oder für eine Reihe Karten. Steigt jedes Element einzeln auf, wirkt die Seite nach Vorlage. Browser ohne Unterstützung und Geräte mit «Bewegung reduzieren» zeigen den Inhalt sofort und ohne Bewegung, es geht nie etwas verloren. Im Kasten unten rechnet die Animation mit dem Kasten, auf einer Seite mit dem Fenster.
Unsere Turnierformate
Code für Entwickler
Nur CSS, kein JavaScript. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<!-- Einblenden beim Scrollen · Klasse fx-rise, gestaffelt mit d1 und d2 -->
<h2 class="fx-rise">Unsere Turnierformate</h2>
<div class="raster">
<article class="fx-rise">…</article>
<article class="fx-rise d1">…</article>
<article class="fx-rise d2">…</article>
</div>
Gestaffelt erscheinen
Die Elemente einer Liste oder Kartenreihe erscheinen nacheinander, sobald sie ins Bild kommt. Die Reihenfolge rechnet CSS mit sibling-index(), darum braucht kein Element eine eigene Nummer. Den Takt setzt --fx-step, Standard sind 60 ms.
Für kurze Listen bis etwa zwölf Einträge, einmal pro Abschnitt. Browser ohne sibling-index() staffeln über einen Rückfall bis 16 Einträge. Ohne JavaScript und bei «Bewegung reduzieren» steht alles sofort da.
- Fr2Regio Tour DoppelClublokal · 19:30Pro
- Sa10Swiss Open P4PUitikon Waldegg · 10:00Elite
- Fr30KäsekickClublokal · 19:00Fun
- Sa14Zürich Open 4/4Clublokal · 10:00Pro
- Fr27KäsekickClublokal · 19:00Fun
Code für Entwickler
Braucht die drei Stylesheets und effects.js. Die Einbindung steht einmal im <head>.
<!-- im <head> -->
<link rel="stylesheet" href="system/tokens.css">
<link rel="stylesheet" href="system/buttons.css">
<link rel="stylesheet" href="system/ui.css">
<script defer src="system/components/effects.js"></script>
<!-- Gestaffelt erscheinen · Abstand je Kind über --fx-step (Standard 60ms) -->
<ul class="fx-stagger" data-fx="stagger" style="--fx-step:60ms">
<li>…</li>
<li>…</li>
<li>…</li>
</ul>
Wie eine Seite entsteht
Die Bausteine sagen, woraus eine Seite besteht. Dieser Teil zeigt, wie man sie zusammensetzt, damit das Ergebnis nach TFCZ aussieht und nicht nach Vorlage. Jede Regel steht mit einem richtigen und einem falschen Beispiel da.
Zuerst die Richtung
Bevor die erste Zeile entsteht, steht fest, wie ruhig oder wie mutig die Seite wird. Die Stufe folgt aus dem Zweck der Seite. Farben, Schrift und Bausteine bleiben auf jeder Stufe dieselben, es ändern sich Aufbau, Bildanteil und Bewegung.
Für Seiten, auf denen jemand etwas nachschlägt: Anfahrt, Preise, Statuten, Kontakt. Eine Spalte, kaum Bilder, keine Bewegung ausser dem Hover.
Der Normalfall für Angebotsseiten und Landingpages. Text und Foto teilen sich den Platz, jeder Abschnitt hat eine eigene Form.
Für Kampagnen und grosse Events mit Ablaufdatum. Grosses Foto, wenig Text, mehr Bewegung beim Scrollen. Diese Stufe braucht einen Anlass.
Der Einstieg: Text links, Foto rechts
Der erste Bildschirm teilt sich in einen Textblock und ein echtes Foto aus dem Club. Links steht, worum es geht und was man tun kann, rechts sieht man, wie es sich anfühlt. Auf dem Handy steht der Text zuerst.
Lesbar in einem Blick. Das Foto zeigt den Ort und die Menschen, der Knopf steht direkt unter dem Satz, auf den er antwortet.
Zentrierter Titel auf leerer Fläche sieht aus wie jede zweite Vorlage. Er eignet sich für eine kurze Ankündigung oder ein Event auf Stufe 7 und höher, mit Foto im Hintergrund.
Keine drei gleichen Karten
Drei gleich grosse Kästchen mit Icon, Titel und Satz sagen dem Auge, dass alles gleich wichtig ist. Meistens stimmt das nicht. Das Wichtigste bekommt mehr Fläche und ein Foto, der Rest ordnet sich darunter.
Eine Hauptsache, zwei Nebensachen. Das Auge weiss sofort, wo es anfangen soll.
Alles gleich laut, darum fällt nichts auf. Ausnahme: echte Wahlmöglichkeiten wie die Paket-Karten, dort sind gleiche Kästen gewollt, damit sich die Pakete vergleichen lassen.
Jeder Abschnitt hat seine eigene Form
Eine Seite liest sich wie ein Gespräch, wenn die Abschnitte wechseln: Einstieg, eine Reihe mit Zahlen, Text neben Foto in der anderen Richtung, ein Zitat, zum Schluss die Aktion. Wiederholt sich dieselbe Form, blättert man durch, ohne etwas zu lesen.
Fünf Abschnitte, fünf Formen. Das Foto wechselt die Seite, die Zahlen stehen frei, die Aktion kommt am Schluss noch einmal.
Nach dem Einstieg dreimal dasselbe Raster. Man scrollt durch, und nichts bleibt hängen.
Abstand zeigt, was zusammengehört
Innerhalb einer Gruppe liegen die Dinge nah beieinander, zwischen zwei Gruppen ist deutlich mehr Luft. So braucht es weniger Linien und Kästen, weil der Abstand die Ordnung schon zeigt. Die Werte kommen aus den Spacing-Tokens des Brand Guides.
Zwei Gruppen, auf den ersten Blick getrennt.
Überall derselbe Abstand. Man muss lesen, um zu merken, wo das eine aufhört.
Eine Seite, eine Hauptaktion
Jede Seite hat genau eine Sache, die sie auslösen soll. Sie steht im Einstieg und am Schluss noch einmal. Alles andere ist Blau oder Ghost. Gold bleibt der Mitgliedschaft vorbehalten, wie im Abschnitt zu den Buttons beschrieben.
Eine goldene Aktion, daneben eine ruhige zweite Wahl.
Vier Knöpfe, drei davon golden. Wer alles hervorhebt, hebt nichts hervor.
Bewegung braucht einen Grund
Eine Animation ist erlaubt, wenn sie etwas sagt: dass ein Klick angekommen ist, dass sich ein Zustand ändert, was zuerst wichtig ist, oder wie eine Geschichte weitergeht. «Sieht lebendig aus» reicht nicht. Der Schwerkraft-Wirbel im Hintergrund ist die eine Bewegung, die einfach zur Marke gehört. Wer weniger Bewegung eingestellt hat, bekommt sie auch.
Klick auf den Knopf. Die Bewegung bestätigt, dass etwas passiert ist, und hört dann auf.
Pulsiert ohne Anlass und ohne Ende. Das Auge wird immer wieder hingezogen, auch wenn es gerade etwas anderes liest.
Kein Glühen, keine fremden Farben
Tiefe entsteht über die Frost-Fläche, den feinen Innenrand und die Brand-Line. Neon-Schein, Glas auf allem und Lila-Türkis-Verläufe gehören zu anderen Marken. Erlaubt sind die vier Verläufe aus dem Abschnitt Farben.
Freitag, 9. Oktober · 16 Plätze frei
Brand-Line oben blau, unten gold, dunkle Fläche. Die Karte gehört erkennbar zum TFCZ.
Freitag, 9. Oktober · 16 Plätze frei
Leuchtet, ist aber nicht mehr unsere Karte. So sehen Vorlagen aus, die eine KI ohne Vorgaben baut.
Auf dem Handy zuerst der Satz
Ein zweispaltiger Einstieg stapelt sich auf dem Handy. Oben stehen Titel, Satz und Knopf, darunter das Foto. So ist die Aktion sichtbar, ohne dass jemand scrollen muss.
Die Aktion steht im ersten Bildschirm.
Das Foto kommt zuerst und schiebt den Knopf aus dem Bild.
Vier Fragen vor der ersten Zeile
Wer eine neue Seite baut, beantwortet diese vier Fragen zuerst. Zusammen mit dem Playbook im Brand Guide ergibt das den Plan für die Seite.
Eine Zielgruppe aus dem Brand Guide und eine Hauptaktion. Gibt es zwei, braucht es zwei Seiten.
Ruhig, ausgewogen oder mutig. Die Stufe bestimmt Bildanteil, Aufbau und Bewegung.
Für jeden Abschnitt eine Form wählen, bevor Text entsteht. Zweimal dieselbe Form hintereinander ist ein Zeichen, dass ein Abschnitt zu viel ist.
Für jede Animation den Grund aufschreiben. Findet sich keiner, fällt sie weg.
Regeln der Kassen-Oberfläche
Die Kasse im Clublokal (okapi, /public/cashier) ist die einzige TFCZ-Oberfläche, die den ganzen Abend in einer Hand liegt und Geld bewegt. Sie benutzt dieselben Farben, Zeichen und Wörter wie alles andere — nur gelten dafür eigene Regeln, weil das Gerät, die Entfernung und die Eile andere sind. Wer ein neues Element der Kasse baut, liest zuerst dieses Kapitel und danach das Icon-Glossar KASSE-ICONS.md im okapi-Repo.
Ein Gerät, ein Finger, kein Zeiger
Die Kasse läuft auf EINEM Tablet quer (11 Zoll, 1194 × 834) und wird mit dem Finger bedient, im Stehen, oft mit der anderen Hand am Glas. Alles, was eine Maus voraussetzt, gibt es hier nicht.
:hoverEin Finger schwebt nicht. Ein Zustand, den nur die Maus zeigt, ist auf diesem Gerät unsichtbar — die Kasse kennt nur den Ruhezustand und :active.
Was gewählt werden kann, steht da. Ein Menü, das sich öffnet, verdeckt genau das, woran man sich orientiert, und schliesst beim nächsten Tipp daneben.
Tooltips, Popover, Hinweise am Zeiger: alles Dinge ohne Zeiger. Was erklärt werden muss, steht als Zeile im Fenster.
Schrittknöpfe 34 × 34, Zeilen und Knöpfe in Fenstern 40–44 px hoch. Kleiner trifft man im Betrieb nicht.
Nichts springt
Wer an der Kasse steht, tippt auf eine Stelle, die er eine Sekunde vorher gesehen hat. Eine Fläche, die ihre Grösse ändert, verschiebt den nächsten Tipp — und der bucht dann etwas anderes.
Das Fenster «Töggeler hinzufügen» hält seine Höhe, solange darin eine Trefferliste scrollt. Ein Fenster mit einer bekannten Zahl Felder wächst dafür mit ihnen, statt sie in ein festes Mass zu pressen.
Was gerade nicht geht, bleibt stehen und ist ausgegraut. Ein Knopf, der verschwindet, rückt alle anderen nach.
Beträge stehen in einer Spalte fester Breite. Ein Minuszeichen vor der Zahl darf die Knöpfe daneben nicht verschieben.
Das «+» im Warenkorb erhöht an Ort und Stelle. Nach oben rückt eine Zeile nur, wenn sie von der Artikelseite kommt — dort sucht man sie danach.
Vier Farben, vier Bedeutungen
Die Kasse läuft im Navy-Modus. Jede Farbe sagt genau eine Sache, und sie sagt sie überall gleich.
Favorit, gewählter Filter, aktives Eingabefeld, Fenstertitel und Abschnittsüberschrift. Gewählt ist ein goldener RAND, nie eine goldene Füllung — dieselbe Regel wie beim Toggle.
Der Rahmen oben, das Logo, der Lichtpunkt im Glas. Blau führt, es wählt nicht aus.
Bezahlknöpfe, die Marke an einer Kachel im Korb, die bezahlte Namenskarte. Der Ton ist der der Bezahlknöpfe (Farbton 142–149°), nie Türkis.
Entfernen, Ausloggen, Gefahr, dazu der Rosé-Ton #e4c9c9 für «Offen von früher»: dort spricht die GANZE Zeile in dieser Farbe, nicht nur der Betrag.
Die eine Ausnahme
Der goldene VERLAUF ist der Hauptaktion eines Fensters vorbehalten («Hinzufügen», die beiden Hälften der Umschaltleiste «Suchen / Neu erfassen»). Das ist keine Auswahl, sondern der Weg hinaus — darum darf er füllen.
Kontrast wird gemessen, nicht geschätzt
Das Tablet steht unter Deckenlicht, oft schräg. Jede Farbentscheidung nennt ihren gemessenen Wert im Kommentar daneben — wer sie später anfasst, sieht, woran er sie messen muss.
Code für Entwickler
/* RICHTIG — der Wert steht dabei, die Regel ist nachprüfbar */ /* Glas: Name 6.0:1, Zeichen 3.8:1 gemessen. */ .kasse-tile { background: radial-gradient(...), linear-gradient(155deg, rgba(26,52,78,.80), rgba(7,17,28,.70)); } /* FALSCH — «sieht hell genug aus» ist keine Messung */ .kasse-tile .nm { color: rgba(255,255,255,.55); }
Namen, Beschriftungen, Zusatzzeilen. Ein Zusatz darf leiser sein als der Name, aber nicht unter diesen Wert.
Totalbeträge und Kennzahlen ab 22 px.
Ein «Grün», das in Wahrheit Türkis ist, fällt beim Messen der Helligkeit nicht auf. Der Farbton gehört mitgeprüft.
Karten und Kacheln sind Glas
Jede Fläche, die etwas trägt — eine Namenskarte, eine Artikelkachel — ist dieselbe Glasfläche: ein Lichtpunkt oben links, ein Verlauf nach unten rechts, ein feiner Rahmen. Kein Schatten, keine zweite Sorte Karte.
Code für Entwickler
background:
radial-gradient(260px circle at 30% 40%, rgba(92,167,220,.22), transparent 60%),
linear-gradient(155deg, rgba(26,52,78,.80), rgba(7,17,28,.70));
border: 1px solid rgba(38,80,118,.9);
border-radius: 8px;
box-shadow: none;Karten, Kacheln und Zeilen. Fenster sind runder (18 px), kleine Knöpfe eckiger (10 px) — dazwischen gibt es nichts.
Gleicher Verlauf, gleicher Winkel, nur die Farbe der Bezahlknöpfe. Eine eigene Gestaltung für «bezahlt» wäre eine zweite Karte.
backdrop-filter neben KachelnDie Unschärfe liest Pixel ausserhalb des Elements: an der Buchstabenleiste zog sie die Kanten der Kacheln als Striche herein. Wo Flächen aneinanderstossen, wird nicht geblurrt.
Die Artikelkachel und ihre Zustände
Eine Kachel ist immer gleich gebaut: Bild oben, Name, Zusatz, unten Preis und Menge. Ihre Zustände kommen dazu, sie ersetzen nichts.
Eine grüne Marke am unteren Rand (inset 0 -4px 0) und die Menge im Fuss. Die Kachel selbst bleibt, wie sie ist.
Halbe Deckkraft, wenn die Kasse nichts mehr annimmt. Der Grund steht als Zeile über den Kacheln, nicht an jeder einzelnen.
Was einmal und nur einmal gebucht wird (Schnuppertraining), nimmt danach nichts mehr an — trägt aber die grüne Marke in voller Helligkeit. Halb weggeblendet sähe es aus wie kaputt.
Eigener Artikel, Gutschein, Schnuppertraining laufen durch dieselbe Vorlage (im Code über negative Ids). Dadurch tragen sie denselben Stern und dieselben Zustände, ohne dass irgendetwas zweimal gebaut wird.
Favoriten: der Stern überall, das Gold nur in der Sektion
Favoriten sind persönlich und stehen in einer eigenen Sektion zuoberst oder zuunterst. Unter seiner Gruppe bleibt derselbe Artikel unauffällig — sonst stünde jede zweite Kachel in Gold und die Ordnung der Gruppen ginge unter.
Gesetzt: star, gold gefüllt. Nicht gesetzt: star-plus, grau. Der Stern ist kein eigener Knopf mit Rahmen, sondern eine Fläche auf der Kachel.
Dort trägt die Kachel goldenen Rahmen UND goldenen Namen. Unter ihrer Gruppe ist nur der Stern gold.
Drücken und halten (250 ms), dann ziehen — und nur in der Sektion, denn nur dort gibt es eine Reihenfolge. Ein sehr leiser Griff in der Ecke sagt, dass es geht.
elementFromPoint gibt ausserhalb des sichtbaren Bereichs null zurück. Getroffen wird über die Rechtecke der Kacheln, und die Kachel trägt touch-action: none, sonst nimmt der Browser den Finger für seinen Bildlauf.
Jede Korbzeile ist gleich gebaut
Name links, Bedienung in der Mitte, Betrag rechts — in allen Zeilen an derselben Stelle, auch wenn eine Zeile weniger kann als die andere.
Code für Entwickler
/* Die Betragsspalte ist FEST: mit «auto» wuchs sie um die Breite des
Minuszeichens, und die Knöpfe der Gutschein-Zeile standen weiter links
als die aller anderen. 68 px fassen auch «-1234.50». */
.kd2-l { display: grid; grid-template-columns: minmax(0,1fr) auto 68px; gap: 10px; }«−», Zahl, «+». Was nicht mehr werden kann (ein Gutschein), zeigt den «+» ausgeschaltet — nicht ein anderes Bedienelement an anderer Stelle.
font-variant-numeric: tabular-nums, rechtsbündig. Untereinander stehende Beträge sollen sich vergleichen lassen.
Eine Zeile ist eine Zeile. Was nicht passt, wird abgeschnitten; der Zusatz darunter trägt nur Sorte, Menge und «à Preis».
Die Aufteilung eines Namens in Titel und Zusatz gilt dem Sortiment. Ein eigener Artikel «test 2» ist ein Name, kein Titel mit Zusatz.
Meldungen stehen IM Korb, nicht darüber
Was den Korb betrifft, erscheint an der Stelle, an der es passiert ist — so breit wie die Zeilen, in der Farbe der Sache, und es verschwindet von selbst.
Dezentes Rot, Balken links, ein Icon-Knopf undo-2 ohne Wort, 5 Sekunden. Die Leiste steht an der Stelle der gelöschten Zeile.
«Bezahlt und abgeschlossen» steht als eine Zeile über den Kacheln, mit dem Weg zurück. Nicht an jeder Kachel.
Ein Hinweis, der nur wiederholt, was gleich passiert, wird nicht gelesen und kostet die Höhe, die der Inhalt braucht.
Ein Mass für jedes Fenster
Alle Fenster der Kasse sind gleich breit: so breit, wie es das breiteste braucht. Unterschiedlich breite Fenster lasen sich wie ein Versehen.
Was länger ist, scrollt im Fenster. Die Leiste oben und die Fusszeile unten bleiben immer frei, dazu 16 px Luft.
Der Fenstertitel trägt dieselbe Farbe wie die Abschnittsüberschriften darunter. Darüber links das Zeichen der Sache in seinem Feld.
Blaue Linie oben, goldene unten — wie überall bei TFCZ.
Der Weg hinaus ist ein stiller Knopf, die Handlung trägt Gold (oder Rot, wenn sie zerstört).
Wählen im Fenster
Eine Auswahl aus wenigen Möglichkeiten ist eine Liste, keine Wolke aus Pillen — eine Zeile trifft man mit dem Finger sicherer, und untereinander liest sie sich schneller.
Code für Entwickler
/* Gewählt = goldener RAND, nie goldene Füllung. Haken 6.9:1, Rand 5.1:1 gemessen. */
.kd-check .box { width:18px; height:18px; border-radius:5px;
border:1.5px solid rgba(195,210,224,.45); background:rgba(0,0,0,.2); color:transparent; }
.kd-check[aria-checked="true"] .box { border-color:var(--gold);
background:rgba(205,168,87,.16); color:var(--gold-lt); }Zeilen zu 36 px, zweispaltig, ohne Kasten je Antwort. Sieben Kacheln untereinander sind mehr Rahmen als Inhalt.
Zeile zu 44 px mit rundem Punkt. Gewählt ist der PUNKT, nicht die Zeile: ein goldener Rahmen um das ganze Feld las sich wie ein aktives Eingabefeld.
Nur was freiwillig ist, trägt die Marke «freiwillig». Pflicht sagt sich anders: der Knopf bleibt gesperrt, bis die Angabe da ist.
Ein Zeichen, eine Bedeutung
Jedes Icon der Kasse ist genau einer Sache zugeordnet. Das Verzeichnis ist KASSE-ICONS.md im okapi-Repo — nicht der Code: wer ein Zeichen neu vergibt, trägt es dort ein, wer eines braucht, das schon vergeben ist, nimmt ein anderes. Dasselbe Bild an zwei Orten hiesse am Tresen zweierlei.
Code für Entwickler
/* Die Strichstärke folgt der Grösse: 34px-Icon auf einer Kachel = 34/24 = 1.4px */ svg { stroke-width: calc(var(--ih, 17px) / var(--iw, 15)); vector-effect: non-scaling-stroke; } .kd2-tile .pic { --ih: 34px; --iw: 24; } /* gross und trotzdem dünn */ /* Ein «fettes» Icon ist immer ein Fehler im Zuschnitt, nie Absicht. */
Die Pfade kommen aus dem Icon-Satz des Guides, die viewBox wird auf den gezeichneten Bereich zugeschnitten. So sind gleich grosse Icons auch optisch gleich gross.
Welches Bild ein Artikel oder eine Gruppe trägt, entscheidet eine Tabelle aus dem Namen — nicht die Hand. Ein neues Getränk bekommt sein Zeichen von selbst.
Gilt in der Kasse wie überall.
Bewegung hat einen Grund
Die Kasse bewegt sich nur dort, wo Bewegung etwas bestätigt. Alles andere steht still.
Der Partikel-Knopf des Guides, 18 Punkte, blau und gold zu gleichen Teilen. Sie steigen IMMER am «+» der Zeile auf, die gerade gewachsen ist — auch wenn ein gleichnamiger Artikel dazugezählt wurde.
Total und Trinkgeld rollen beim Ändern. Der Betrag ist die Zahl, auf die alle schauen.
Ausloggen läuft über Hold to Confirm — ein Fenster mit «Sicher?» kostet am Tresen mehr Zeit als das Halten.
prefers-reduced-motionSchaltet jede dieser Bewegungen ab. Keine Ausnahme.
Wie die Kasse spricht
Kurz, konkret, in der Sprache des Clublokals. Die Kasse redet nicht über sich selbst.
Die männliche Form, überall, ohne Gendern. So heissen die Leute im Club.
«Artikel entfernt», «Bezahlt und abgeschlossen», «geht aufs Haus». Nicht: «Der Vorgang wurde erfolgreich durchgeführt».
11.5 px, weit gesperrt, mit dem kurzen goldenen Strich davor — dasselbe Mass über Artikelgruppen wie in Fenstern.
Was schiefging UND was jetzt hilft. Ein Code aus der Schnittstelle ist kein Satz für den Tresen.
Bevor etwas an die Kasse kommt
Die Kasse ist die einzige Oberfläche, bei der ein Fehler Geld kostet und niemand Zeit hat, ihn zu suchen. Darum gelten für sie zwei zusätzliche Regeln der Arbeitsweise.
Dieselbe Kachel, dieselbe Zeile, dasselbe Fenster werden einmal gebaut und von allen benutzt. Zwei Fassungen laufen auseinander — und die Kasse zeigt dann zweierlei für dieselbe Sache.
Kontrast, Geometrie, Abstände: nachrechnen, bevor es «passt» heisst. Die Werte gehören als Kommentar neben die Regel.
Änderungen an der Gestaltung ändern nichts an dem, was gebucht wird. Was die Buchung betrifft, wird vorher besprochen.
Wer eine Regel ersetzt, nimmt die alte mit — die verwaiste Klasse, das nicht mehr vergebene Icon, die Kopie im Mockup.













