
Webanwendung

Eine kontemplative, mehrsprachige Webanwendung, die Benutzer durch eine introspektive Reise führt. Sie ordnet menschliche emotionale Zustände Farben, Zahlen und den Göttlichen Namen (Asma-ul Husna) zu, um tiefgreifende, psychologisch fundierte Reflexionen anzubieten.
Das mobile Gegenstück ist kein eigenes Produkt: Es ist ein mit Expo paketierter schlanker WebView-Wrapper, der dieselbe Webadresse anzeigt — eine Codebasis, zwei Vertriebswege.
Die meisten Kontemplations-Apps holen ihre Inhalte von einem Server und zeichnen auf, was der Nutzer auswählt. Das eigentliche Problem war hier nicht der Inhalt, sondern die Spannung zwischen Privatsphäre und Kontinuität: Der Nutzer gibt eine Stimmung ein — nicht persönlich genug, um eine Speicherung zu rechtfertigen, aber wertvoll genug, dass ihr Verlust mitten in der Reise schmerzt. Gleichzeitig sollte die Anwendung vollständig statisch bleiben, also durften überhaupt keine Nutzerdaten einen Server erreichen.
Eine Persona in zwei Kontexten. Jemand, der die App in einer kurzen Pause am Telefon öffnet und nach wenigen Berührungen einen Kontemplationstext erhalten möchte: Die Zahl der Schritte muss klein bleiben, und auf jedem Bildschirm muss ein Zurück möglich sein. Kehrt dieselbe Person regelmäßig zurück, will sie ihre früheren Reisen sehen — dafür aber kein Konto anlegen. Vier Sprachen (TR/EN/DE/ES) liefern denselben Ablauf mit denselben Texten zu den Göttlichen Namen in der jeweiligen Sprache.
Alleiniger Entwickler. Datenmodellierung der Zuordnung, Inhaltsstruktur über vier Sprachen, clientseitige Zustandsmaschine, Umgebungsklang über Web Audio, PWA-Einrichtung, Security-Header und Vercel-Deployment.
Next.js 16 App Router ganz ohne Serverseite: keine API-Routen, keine Datenbank, keine Sessions. Alle lokalisierten Routen werden statisch erzeugt. Der Ablauf läuft als fünfstufige Zustandsmaschine in einer einzigen Client-Komponente — Einstieg, Stimmung, Farbe, Zahl, Göttlicher Name — die Übergänge laufen über useReducer. Das Routing nutzt next-intl ohne Präfix für die Standardsprache (localePrefix "as-needed"); auf dem präfixlosen Pfad /privacy leitet die Proxy-Schicht Besucher anhand des Sprach-Cookies in ihre eigene Sprache um. Der aktive Sitzungszustand liegt im sessionStorage, der Verlauf abgeschlossener Reisen im localStorage.
Die Kette Stimmung → Farbe → Zahl → Göttlicher Name wird nicht berechnet, sie ist eine redaktionelle Entscheidung: Welche fünf Farben und welche fünf Namensnummern zu welcher Stimmung gehören, wurde von Hand festgelegt und liegt als feste Tabelle für acht Stimmungen im Code. Eine Regel-Engine hätte eine Allgemeinheit vorgetäuscht, die tatsächlich nicht existiert.
KompromissInhaltliche Änderungen — eine neue Stimmung, eine geänderte Namenszuordnung — erfordern eine Codeänderung und ein neues Deployment; über ein Panel geht das nicht. Der zweite Preis liegt im Ablauf selbst: Das Ergebnis bestimmt allein die gewählte Zahl, die gewählte Farbe fließt nicht ein. Der Farbschritt ist für den Nutzer eine bedeutsame Pause, für das System aber nur eine visuelle Variable.
Ein Stimmungseintrag ist zu alltäglich, um jemanden ein Konto anlegen zu lassen, und zu persönlich, um auf einem Server gesammelt zu werden. Die letzten 10 Reisen werden in den localStorage geschrieben, und der Nutzer löscht sie alle mit einer Schaltfläche. Damit hat die Anwendung weder ein Backend noch gespeicherte Nutzerdaten.
KompromissDer Verlauf ist an das Gerät gebunden: Bei einem Gerätewechsel oder gelöschten Browserdaten gibt es keinen Weg zurück. Im privaten Modus oder bei vollem Speicherkontingent scheitert das Schreiben still — der Code schluckt den Fehler, weil es keinen sinnvollen Wiederherstellungs- schritt gäbe, den man dem Nutzer zeigen könnte. Auch die Grenze von zehn Einträgen ist willkürlich; die elfte Reise löscht die älteste.
Zwei Klänge waren nötig: ein ruhiger Ton auf dem Einstiegsbildschirm und Regen auf dem Bildschirm des Göttlichen Namens. Beide entstehen im Browser — der Regen aus einem Rosa-Rauschen-Puffer über einen Tiefpassfilter, der Ton aus vier harmonischen Oszillatoren mit abnehmender Verstärkung. Keine einzige Audiodatei liegt im Repository, also gibt es weder Medien zum Herunterladen noch eine CSP-Regel, die sie erlauben müsste.
KompromissDer Klang wird in jeder Sitzung neu auf dem Client erzeugt: Ein zwei Sekunden langer Rauschpuffer wird Sample für Sample gefüllt, was auf einem schwachen Telefon im Moment des Einschaltens spürbar CPU kostet. Da ein AudioContext ohne Nutzergeste nicht starten kann, setzt der Klang erst nach dem Tastendruck ein. Und den Klang zu ändern ist keine Gestaltungsaufgabe mehr: Eine neue Textur zu wünschen bedeutet, DSP-Code zu schreiben.
Der Schritt einer abgebrochenen Reise liegt im sessionStorage; der Server kann ihn nicht kennen. Das vom Server erzeugte HTML enthält immer den Einstiegsbildschirm, der gespeicherte Zustand wird erst nach abgeschlossener Hydration angewandt — über ein explizites Hydrations-Flag auf Basis von useSyncExternalStore. So bleibt pro Sprache genau eine statische Ausgabe, die unverändert im CDN zwischengespeichert werden kann.
KompromissEin wiederkehrender Nutzer sieht im ersten Frame den Einstiegsbildschirm und springt dann zu seinem Schritt — ein kurzer, aber realer visueller Sprung. Die Alternative, den Zustand serverseitig zu lesen, hätte die Seite aus der statischen Generierung genommen und in ein Rendering pro Anfrage verwandelt.
Ohne Backend reduzierte sich die Stack-Wahl auf eine einzige Frage: eine viersprachige Struktur, die sich vollständig statisch erzeugen lässt und dabei eine interaktive Insel trägt. next-intl bindet Metadaten- und hreflang-Erzeugung an die Routing-Schicht selbst und erspart es, SEO je Sprache von Hand zu schreiben. Framer Motion steuert über AnimatePresence die Austrittsanimation zwischen den Schritten — beim Bildschirmwechsel verlässt die Komponente den Baum, was das mit CSS allein unmöglich machte. Tailwind und CSS-Custom-Properties lassen die Akzentfarbe der gewählten Stimmung aus einer einzigen Variablen über die gesamte Oberfläche wirken.
Sechs HTTP-Header sind in next.config.ts definiert: CSP, nosniff, Frame-Verbot, Referrer-Policy, XSS-Filter und eine Permissions-Policy, die Kamera, Mikrofon, Standort und Zahlung schließt. In der CSP ist connect-src nur für Vercel-Analytics-Endpunkte offen, font-src und worker-src bleiben beim eigenen Ursprung. Die Angriffsfläche ist konstruktionsbedingt schmal: kein Formular, keine Session, kein API-Endpunkt, keine Datenbank, und keine Nutzereingabe verlässt das Gerät. Die bekannte Schwäche ist 'unsafe-inline' in script-src — offen gelassen für das Inline-Bootstrap-Skript von Next.js; ein Wechsel zu einer nonce-basierten CSP hieße, die statische Generierung aufzugeben. In der Produktion wird die Konsolenausgabe bis auf error und warn entfernt.
Alle lokalisierten Routen werden statisch erzeugt; beim ersten Paint fällt keine Serverberechnung an. Alle fünf Schrittbildschirme sind über dynamic import in eigene Chunks getrennt, nur Hintergrund und Fortschrittsbalken liegen im Hauptbundle — der Preis dafür ist, dass ein Schrittwechsel bei langsamer Verbindung beim ersten Mal auf seinen Chunk wartet. @next/bundle-analyzer liegt hinter einem ANALYZE-Flag bereit im Repo. Für dieses Projekt existiert keine gespeicherte, datierte Lighthouse-Ausgabe, deshalb steht hier kein Seitengeschwindigkeitswert.
Heute würde ich den Farbschritt entweder wirklich auf das Ergebnis wirken lassen oder ihn aus dem Ablauf entfernen. So wie es ist, trifft der Nutzer eine Wahl, die nichts verändert — die Oberfläche hält also ein Versprechen nicht, das sie gibt. Zweitens würde ich die Zuordnungstabellen aus dem Code in Inhaltsdateien verlagern, die zur Build-Zeit gelesen werden: In Gorev Kahramani und in diesem Portfolio selbst liegt Inhalt als MDX/JSON vor und durchläuft eine Schema-Validierung; derselbe Ansatz würde hier eine Inhaltsänderung aufhören lassen, eine Codeänderung zu sein — ohne bei der statischen Generierung irgendetwas einzubüßen.
Sagen Sie mir, was Sie bauen wollen; ich sage Ihnen vorab, wie lange es dauert und wo man anfängt.