Jeden Montag tippt auf S12.at eine KI das nächste Bundesliga-Spiel des FC Red Bull Salzburg, im Salzburger Dialekt und mit einer Wahrscheinlichkeit für Sieg, Remis oder Niederlage. Sie heißt Karli, und ich habe sie mit Vibe Coding gebaut. Ich bin seit 1974 Fußballfan und seit 1989 in der Kommunikationsbranche selbstständig, und in diesem Projekt kommt beides zusammen.
Die KI ist bei einem Orakel der kleinste Teil. Alles andere entscheidet, ob Karli brauchbar tippt oder Unsinn erzählt, allen voran die eigene Statistik auf statistik.s12.at, aus der er seine Daten holt. Ich zeige dir, wie Karli und diese Statistik aufgebaut sind, welche Fehler mich Nerven gekostet haben und was du für dein eigenes Projekt mitnehmen kannst. Zuerst schauen wir, wie viel KI überhaupt in einem Fußballblog steckt.
- Vibe Coding
- du beschreibst einer KI, was du bauen willst, und iterierst mit ihr, bis das Ergebnis stimmt. Richtung und Qualitätskontrolle bleiben bei dir.
- n8n
- ein Automatisierungswerkzeug, das Abläufe aus verbundenen Bausteinen zusammensteckt. Es lässt sich auf dem eigenen Server betreiben.
- Cron-Job
- ein Zeitplan, nach dem ein Skript automatisch läuft, zum Beispiel jeden Montag zu einer festen Uhrzeit.
- Shadow DOM
- ein abgeschotteter Bereich in einer Webseite, in den Styles von außen nicht hineinwirken. Ein Widget bleibt dadurch vom Theme der Seite unberührt.
Wie viel KI steckt in einem Fußballblog?
Auf S12.at schreibe ich selbst, unabhängig, ohne Verein und ohne Pressestelle. Die Texte sind meine. Die Technik darunter baue ich mit Claude Code, einem Coding Agent, der Dateien liest, Code schreibt und Änderungen vorbereitet. Ich liefere die Anforderungen und entscheide, was gut genug ist. Wie ich Claude Code sonst im Dauerbetrieb einsetze, steht im Artikel über den /loop-Befehl.
Heraus kamen vier Widgets: Ergebnisse und Tabelle der Bundesliga, die internationalen Spiele des FC Salzburg, Karli und die Parkplatzparty, die drei Stunden vor Anpfiff das nächste Heimspiel anzeigt. Hinter allem liegt eine eigene Statistik-Datenbank, die ich im August begonnen habe und die ich gleich genauer zeige. Die Karli-Seite ist schon seit Oktober 2025 online.
Das ist kein Konzern-Setup, sondern ein Fanblog auf einem Mittwald-Server mit PHP und MySQL. Wenn Leute über KI im Fußball reden, zeigen sie auf Liverpool oder Sevilla. Dazu hab ich einen eigenen Artikel geschrieben, wie generative KI einem Verein in der Krise helfen kann. Karli ist die kleine Variante davon: ein Fan, der sich sein eigenes Werkzeug baut.
Das Orakel ist der kleinste Teil von Karli. Der Rest ist Handwerk: Daten sammeln, Ausgaben absichern und Fehler so sichtbar machen, dass ich sie finde.
Auf der Karli-Seite steht, was er sich ansieht: Teamform, Saisonverlauf, Verletzte und Gesperrte. Daraus rechnet er die Wahrscheinlichkeit für Sieg, Remis oder Niederlage samt Torwette. Verletzte und Gesperrte fließen in die Berechnung ein, angezeigt werden sie auf der Karte nicht. Er versteht sich als datengetriebener Nerd mit Fußballleidenschaft, nicht als Prophet. Für verlorene Wettscheine haftet weder er noch ich.
statistik.s12.at: der Dreh- und Angelpunkt
Wenn Karli das Gesicht des Projekts ist, dann ist statistik.s12.at sein Fundament. Dort läuft meine eigene Bundesliga-Statistik, und fast alles andere hängt daran: die Widgets auf S12.at, die Auswertungen und die Form, mit der Karli rechnet. Karli ist nur der prominenteste Kunde dieser Datenbank.
Auf der Seite wählst du die Saison, aktuell 2026/27. Du blätterst durch die Runden und siehst pro Spiel Ergebnis, Stadion und Zuschauerzahl. Die Tabelle zeigt neben Punkten und Tordifferenz die Form der letzten fünf Spiele als Folge von S, U und N. Oben rechts steht der Zeitpunkt des letzten Syncs. Ein Klick auf ein Spiel öffnet das Detailfenster mit Torschützen, Karten und Statistik wie Ballbesitz, Schüssen aufs Tor und Passquote. Das klingt schlicht, und genau darum geht es: wenige Fragen, jedes Mal gleich und schnell beantwortet.

Wie die Statistik aufgebaut ist. Gefüttert wird sie aus vielen unterschiedlichen Quellen. Kein Anbieter liefert alles vollständig und gleich benannt, also führt ein Sync die Daten zusammen und legt sie in meiner eigenen MySQL-Datenbank mit fünf Tabellen ab. Erst dort entsteht ein einheitlicher Bestand. Eine kleine JSON-API reicht ihn an die Widgets für Ergebnisse, Tabelle und Karli weiter, die als Shadow DOM auf S12.at eingebettet sind. Dazu kommen die internationalen Spiele des FC Salzburg und die Parkplatzparty, die eigene Wege gehen. Das Ganze ist PHP 8.2 mit strikter Typisierung.
International läuft anders. Das Widget für die internationalen Spiele des FC Salzburg hängt nicht an meiner Datenbank. Es liest die Spiele direkt aus dem Spielplan und filtert nach dem Wettbewerbsnamen. Dadurch laufen Europa League, Conference League und Champions League ohne Codeänderung durch. Die Tabelle der Ligaphase kommt von einem Anbieter, der meinen Server abweist. Deshalb fragt der Browser des Lesers selbst an. Das löst das Problem, kostet mich aber den Server-Cache. Gezeigt wird ein Ausschnitt mit den Plätzen 1 bis 8 plus Salzburg und je zwei Nachbarn, höchstens 13 Zeilen. Ein Detail am Rand: International heißt der Verein FC Salzburg, in Bundesliga und Cup RB Salzburg (voll ausgeschrieben wäre für die Darstellung zu lange). Der Name muss also je nach Wettbewerb zugeordnet werden.
Warum ein enges Korsett kein Nachteil ist. Gehostet ist alles bei Mittwald: PHP, MySQL, FTP, kein SSH, kein Composer. Ich habe deshalb einen eigenen Autoloader gebaut, Migrationen laufen als SQL-Dateien über phpMyAdmin, und Updates überschreiben einfach die Dateien per FTP. Dasselbe Muster hab ich schon bei meinem Strom-Dashboard genutzt, wo Claude Code PHP und Datenbank ebenfalls sauber strukturiert hat. Wer weiß, was ein Server kann, schreibt Code, der darauf läuft.
Was pro Spiel gespeichert wird. Ich speichere 33 bis 38 Statistikwerte pro Partie, im Detailfenster gruppiert nach Überblick, Offensive und Defensive. Nur 29 Bezeichnungen kommen in jedem Spiel vor. Deshalb liegen die Werte als JSON in einer Spalte statt in einem starren Schema, und ein neuer Wert braucht keine Migration. Rund jede siebte gespielte Partie hat gar keine Statistik, dann fällt der Block im Detailfenster einfach weg. Aufstellungen, einen vollen Ticker und Formationsgrafiken gäbe es theoretisch, ich brauche sie nicht.
Was Karli davon hat. Karli holt sich Form, Spiele und Ergebnisse aus dieser Statistik, nicht aus dem Netz und nicht aus dem Gedächtnis des Modells. Die Form des Gegners kommt erst seit dem 7. Oktober 2026 von dort, vorher hat Gemini sie geschätzt. Dafür gibt es den Begriff Grounding: Das Modell stützt sich auf überprüfbare Daten statt auf Vermutungen. Bei Karli hat das einen sehr konkreten Grund, wie du bei den Fehlern gleich siehst.
Das ist die eigentliche Pointe des Projekts. Eine KI, die tippt, ist schnell gebaut. Eine Datenbank, der die KI vertrauen darf, ist die Arbeit. Und weil die Statistik als eigenständiges Stück existiert, profitieren auch Leser von ihr, die sich nie für Karli interessieren: Wer wissen will, wie Salzburg in der Tabelle steht, braucht dafür keine KI. Nach sieben Runden steht der FC Red Bull Salzburg dort mit 17 Punkten auf Platz zwei, einen Punkt hinter Rapid (Stand 7. Oktober 2026).
Was passiert jeden Montag im Hintergrund?
Der Auslöser ist ein Workflow in n8n. Den Server dafür betreibe ich selbst auf Hetzner mit Coolify. Wie du in einer Stunde deinen ersten Workflow baust, steht in meinem n8n-Einstiegsartikel. Für einen Wochenrhythmus reicht ein Zeitplan: Der Schedule Trigger kennt laut n8n-Dokumentation Wochenintervalle und Cron-Ausdrücke, etwa für jeden Montag zu Mittag.
Was danach passiert, läuft in fünf Schritten:
- n8n ruft ein PHP-Skript auf meinem Server auf. Es ermittelt das nächste Spiel.
- Ein zweites Skript holt Form, Spiele und Ergebnisse beider Teams aus meiner eigenen Statistik auf statistik.s12.at.
- Diese Fakten gehen in zwei Stufen an Gemini, das Sprachmodell hinter Karli. Es liefert den Tipp und den Text im Dialekt.
- Ein weiteres Skript baut daraus die Karte mit Vereinslogos, Tipp, Halbkreis-Anzeige und Countdown und legt sie in eine Cache-Datei.
- Das Widget auf S12.at liest nur diese Cache-Datei.
Besucher lösen also keine frische KI-Abfrage aus, sie bekommen die fertige Karte vom Montag. Die Logos spiegle ich auf meinen eigenen Server, damit beim Laden der Seite nirgends Fremdadressen auftauchen. Seit dem 28. September 2026 sieht Karli aus wie eine Anzeigetafel. Der Countdown läuft im Browser, und ein rundes Karli-Video begleitet den Tipp. Das Video hat 320 Pixel Durchmesser und wiegt nach ffmpeg noch 97 Kilobyte, vorher waren es 2,6 Megabyte. WPVibe sei Dank!
Welche Fehler hat Karli mir beigebracht?
Karli läuft, sauber war er nie. Das sind die Lektionen, die ich in meiner Doku festgehalten hab.
Ein Jahr im Code ist eine Zeitbombe
In Karlis Code stand eine Prüfung auf die Saison 2025. Ab Juli 2026 meldete er deshalb durchgehend Saisonende. Behoben hab ich das nicht mit neuer Jahresrechnung, sondern indem ich die Prüfung gestrichen habe: Was in der Zukunft liegt und noch nicht gespielt ist, gehört zwangsläufig zur laufenden Saison.
Prompts sind Code
Im Prompt stand fest verdrahtet, Salzburg spiele in der Arena des Gegners. Bei Heimspielen bekam das Modell damit das Gegenteil der Wahrheit. Ein Prompt gehört geprüft wie jede andere Datei. Auch der Vereinsname hängt vom Wettbewerb ab: International heißt der Klub FC Salzburg, in Bundesliga und Cup RB Salzburg (abgekürzt, sonst wäre der Name in der Statistik unschön dargestellt worden).
Widersprechen sich zwei Felder, ist eines falsch
Karli bekam als Form der letzten zehn Spiele dauerhaft die Meistergruppe der Vorsaison und erfand daraus fünf Niederlagen. Die Tabelle im selben Datensatz zeigte Salzburg ungeschlagen. Das ist eine Halluzination mit Ansage: Aus falschen Eingaben macht das Modell plausible falsche Aussagen. Die Lösung war kein besserer Prompt, sondern bessere Daten.
Zahlen im Text sind ein Risiko
Karli sollte den Tipp als X:Y liefern, Salzburg zuerst. Einmal kam Heim:Gast, und meine Konsistenzprüfung machte daraus einen Tipp, den niemand getippt hatte. Eine Auto-Korrektur, die Werte erfindet, versteckt den Fehler. Heute liefert Gemini die Tore als benannte Felder, eines für Salzburg, eines für den Gegner, und der Karli-Text enthält keine Ziffern mehr. Eine Reihenfolge-Anweisung hält ein Sprachmodell nicht zuverlässig ein, ein benanntes Feld eher.
Ein gescheiterter Abruf darf nicht wie ein leeres Ergebnis aussehen
Meine frühere Abfrage für Ausfälle antwortete bei einem kaputten Abruf mit „Keine Ausfälle gemeldet“. Ein defekter Datenabruf sah damit aus wie ein komplett fitter Kader. Seither müssen sich beide Zustände im Code unterscheiden. Dazu passt: Ein HTTP-Status 200 sagt nichts über den Inhalt. Eine Quelle lieferte 200 und exakt null Treffer. Ich prüfe die Trefferzahl, nicht den Status.
Deployen ohne Pipeline
Bei FTP-Uploads ohne Pipeline lautet die häufigste Frage, ob die neue Datei überhaupt am Server liegt. Deshalb trägt jede deploybare Datei einen Versionsmarker. Aktuell steht die Tipp-Logik auf Version 2026-10-07b. Das hat mir mehrere Diagnoserunden gespart.
Die Kleinigkeiten, die trotzdem wehtun
Ein automatischer Edit hat zweimal aus [\s\S] ein [sS] gemacht. Das ist weiterhin gültiger Code, trifft aber nie und sah aus wie eine leere Datenquelle. Seither kompiliert ein Skript nach jeder Änderung alle Muster probeweise. Ein anderes Beispiel: Eine Theme-Regel für Videos hat Karlis Video im Live-Betrieb zur Ellipse verzogen, in meiner leeren Testseite fiel es nicht auf. Die Testseite enthält jetzt die echten Theme-Regeln. Und Vereinsnamen sind nie überall gleich geschrieben: Aus „RZ Pellets Wolfsberg“ muss „RZ Pellets WAC“ werden. Ich ordne sie über gemeinsame Wörter ab drei Zeichen zu und hab das gegen alle elf Gegner getestet.
Wie sieht Vibe Coding bei Karli konkret aus?
Der Ablauf ist banal und genau deshalb tragfähig. Ich beschreibe Claude Code ein Problem in normalen Sätzen, etwa dass Karli bei Heimspielen von der falschen Arena redet. Der Agent sucht die Stelle, schlägt eine Änderung vor und passt die Dateien an. Ich lade sie per FTP hoch, rufe die Seite live auf und prüfe, ob der Versionsmarker stimmt und der Tipp plausibel ist. Erst dann gilt die Änderung als erledigt.
Der wichtigste Teil kommt danach: Jeder Fehler landet mit Ursache und Gegenmaßnahme in meiner Doku. Das ist kein Schönheitsprojekt. Beim nächsten Projekt lese ich diese Liste zuerst, und ein Coding Agent bekommt sie ebenfalls als Kontext. Aus Fehlern werden so Regeln, und Regeln machen aus Vibe Coding ein Handwerk.
Dabei hilft mir ein Grundsatz: Ich vertraue dem Agenten bei der Arbeit, aber nicht bei der Abnahme. Gemini liefert den Tipp, Claude Code baut die Mechanik, und ob beides zusammen stimmt, entscheide ich am Ende selbst, mit dem Blick eines Fans, der seit Jahrzehnten Salzburger Spiele sieht. Eine KI, die den Tipp bewertet, den eine andere KI geschrieben hat, ersetzt diesen Blick nicht.
Was ist bei Karli noch offen?
Karli bleibt ein Spaßprojekt mit Datenbasis, kein Wettorakel. Das sagt die Karli-Seite selbst, und daran ändert auch die beste Statistik nichts. Offen ist vor allem die Frage, wie gut Karli über eine ganze Saison tippt. Eine saubere Trefferquote führe ich bislang nicht, und ohne sie bleibt jede Aussage über seine Qualität ein Bauchgefühl. Genau das möchte ich als Nächstes ändern: Tipp und Ergebnis nebeneinander speichern, Woche für Woche.
Häufige Fragen zu Karli und Vibe Coding
Was heißt das für dein eigenes Projekt?
Such dir etwas, das du jede Woche brauchst und bisher von Hand machst: ein Preisblatt, eine Terminübersicht, ein Kundenportal. Fang mit dem kleinsten Widget an, hol die Daten aus deiner eigenen Datenbank statt aus der Schätzung des Modells, und halte jeden Fehler fest, den du findest. Mein Fußballblog war der Anlass, aber die Lektionen gelten für jede Branche, in der eine KI Aussagen aus Daten bauen soll.
Du kannst warten, bis Vibe Coding auch bei dir Alltag ist. Oder du baust dir diese Woche dein erstes Karli.
Ebenso noch erwähnenswert: S12.at läuft auf WordPress. Ich hab nur die Grundeinstellungen gemacht, wie man sie halt bei WordPress macht. Claude hat die komplette selber gebaut – nach meinen Vorstellungen. Ich hab den Review gemacht, getestet, iteriert, getestet, freigegeben. Das war eine sehr entspannte Session mit meinem (virtuellen) Webprogrammierer.





