Zum Hauptinhalt springen

Devlog · Phase 0 · 6/7

Ein Catppuccin-Design-System, Glass-first

Sechsundzwanzig Catppuccin-Tokens pro Flavour, ein Glassmorphism mit 62 % Transparenz und drei selbst gehostete Schriftfamilien: die Haut von aubia.dev, vom CSS-Token bis Reduced-Motion.

Veröffentlicht am 17 August 20265 Min. Lesezeit
  • design-system
  • catppuccin
  • react

Sobald das Backend in Module einsortiert war, brauchte aubia.dev ein Gesicht. Ich wollte eine straffe Identität, dunkel als Standard, die Sorgfalt atmet, ohne zu schreien. Das Ergebnis läuft auf vier Entscheidungen hinaus: eine Catppuccin-Palette, ein Theming, das sich von selbst einstellt, eine visuelle Sprache aus Glas und eine bemessene Bewegung. Der echte Code aus dem Repo stützt jede von ihnen.

Catppuccin: vier Flavours, zwei als Standard

Die Palette stammt von Catppuccin, einer im Dev-Ökosystem gut bekannten Farbfamilie. Aubia bündelt vier Flavours, aber die Landingpage exponiert nur zwei: Mocha im Dunklen, Latte im Hellen. Die sechsundzwanzig Tokens jedes Flavours sind als CSS-Variablen im @theme-Block von resources/css/app.css deklariert. Tailwind v4 konfiguriert sich hier vollständig in CSS, ohne eine tailwind.config.js-Datei.

@theme {
    --color-ctp-base: #1e1e2e;
    --color-ctp-mantle: #181825;
    --color-ctp-text: #cdd6f4;
    --color-ctp-mauve: #cba6f7;
    /* ... vingt-six tokens pour Mocha */
}

Das Mauve ist mein primärer Akzent. Die beiden anderen Flavours, Frappé und Macchiato, sind als dunkle Varianten für die Phase 1 gebündelt, bleiben aber außerhalb des öffentlichen Switchers, solange die Landingpage ihre straffe Identität behält. Der springende Punkt: Keine Komponente referenziert einen fest eingetragenen Hexadezimalwert. Eine Klasse wie bg-ctp-mauve ändert ihren Wert mit dem aktiven Flavour, ohne dass das JSX davon etwas weiß.

Das Theming stellt sich von selbst ein

Die Themenwahl folgt heute einer einfachen Regel: dunkel als Standard. Im April ging die Landingpage vom Gegenteil aus, dem System zuerst, indem sie beim Mounten prefers-color-scheme las, um Mocha oder Latte je nach OS-Einstellung anzuwenden. Ich habe diesen Standard im Juni auf Dunkel umgestellt. Fortan kommt ein Besucher in jedem Fall in Mocha an und folgt der OS-Einstellung nur, wenn er ausdrücklich die Option "System" im Switcher wählt. Der aktive Flavour wird auf <html data-theme="..."> gesetzt, und die Werte von Latte überschreiben die von Mocha über dieses Attribut. Jede ausdrückliche Wahl, hell, dunkel oder System, wird in localStorage gespeichert und hat beim nächsten Besuch Vorrang vor dem Standard.

Diese Umstellung ist nicht kosmetisch. Die Zielgruppe von Aubia sind Entwickler, die im Dark Mode leben: Da empfängt man sie besser in ihrer natürlichen Umgebung, statt ihnen beim Öffnen der Seite einen weißen Blitz zuzumuten.

Glass-first: die Transparenz als Grundsatzentscheidung

Die visuelle Sprache der Landingpage beruht auf Glassmorphism-Karten: halbtransparenter Hintergrund, feine Kante, Hintergrundunschärfe. Das lässt den Verlauf der Seite durch jede Fläche erahnen und gibt diesen Eindruck gedämpfter Tiefe. Statt denselben Stapel Klassen auf jeder Karte zu wiederholen, lebt das Rezept in einer Tailwind-v4-Utility, in CSS deklariert:

@utility glass-card {
    background: color-mix(in oklch, var(--glass-bg-card) 62%, transparent);
    backdrop-filter: blur(40px) saturate(1.6) brightness(1.02);
    border: 1px solid color-mix(in oklch, var(--glass-stroke) 12%, transparent);
    box-shadow: inset 0 1px 0 color-mix(in oklch, var(--glass-highlight) 8%, transparent);
}

Das Detail, das den Effekt macht oder bricht, ist die Transparenz. Ein deckender Hintergrund hebt den backdrop-filter auf: ohne die 62 % des color-mix hat die Unschärfe nichts mehr zu verwischen, und die Karte wird zu einem einfachen Rechteck. Das saturate und das brightness geben dem Glas etwas Leben zurück, das die Unschärfe allein auslöschen würde, und der Schatten liegt innen, ein bloßer Lichtstreif auf der oberen Kante. Die Disziplin ist strikt: durchscheinender Hintergrund, kaum markierte Kante, niemals ein schwerer Schlagschatten, der die Illusion zerbräche.

Drei selbst gehostete Schriftfamilien

Die Typografie beruht auf drei Familien, alle lokal gehostet: Geist für den Fließtext und die Oberfläche, Geist Mono für Code und Dateipfade, Chakra Petch für die großen Display-Titel.

Kein Netzwerkaufruf an eine fremde Foundry: Die @font-face werden beim Build vom Schriften-Plugin von Laravel erzeugt, aus den installierten Paketen. Im Juni habe ich das Laden auf die @fonts-Direktive umgestellt, die diese Deklarationen vor dem Haupt-Stylesheet in den <head> injiziert.

Lokal zu hosten geht über die Performance hinaus: eine fremde Foundry aufzurufen, würde die IP jedes Besuchers an einen Subunternehmer außerhalb der EU durchsickern lassen, was ich für eine Website ablehne, die beim Datenschutz makellos sein will. Ich habe dieses Trio mit Absicht komponiert. Die Wortmarke Aubia verlangt Chakra Petch, und ein Inter für den Fließtext zu behalten, hätte eine dauerhafte Abweichung zu diesem Titel geschaffen. Geist und Geist Mono stammen aus derselben Familie, ihre vertikale Ausrichtung zwischen Prosa und Code ist nativ.

Der @theme-Block bleibt die Quelle der Wahrheit der drei Schrift-Stacks:

--font-display: 'Chakra Petch', 'Chakra Petch fallback', system-ui, sans-serif;
--font-sans: 'Geist', 'Geist fallback', system-ui, sans-serif;
--font-mono: 'Geist Mono', 'Geist Mono fallback', ui-monospace, monospace;

Die Fallback-Schrift, die auf jede Familie folgt, neutralisiert die Layout-Verschiebung im Moment des Swaps. Nur Chakra Petch wird vorgeladen, weil sie den Haupttitel trägt und im wahrgenommenen Rendering ins Gewicht fällt.

Die Bewegung, mit einer Notbremse

Die Animationen laufen über Motion. Ich habe die wiederverwendbaren Variants in einer einzigen Datei zentralisiert, motion-presets.ts, statt sie in jeder Komponente neu zu definieren. Das Standard-Erscheinen der Abschnitte ist ein aufsteigendes Einblenden, im Luma-Signatur-Easing:

export const fadeUpSpring: Variants = {
    hidden: { opacity: 0, y: 24 },
    visible: {
        opacity: 1,
        y: 0,
        transition: { duration: 0.6, ease: EASE_LUMA },
    },
};

Die Notbremse ist die Barrierefreiheit. Ein Besucher, der auf System-Ebene weniger Bewegung verlangt hat, darf keine Einblendungen erleiden. Motion respektiert prefers-reduced-motion nativ, und ich normalisiere seinen Wert mit einem Hook useSafeReducedMotion(), der immer einen Boolean zurückgibt, selbst während des Server-Renderings, wo die Media Query noch nicht ausgewertet ist. Das Animationsbudget ist bewusst knapp: selten mehr als zwei animierte Elemente gleichzeitig im sichtbaren Feld, um die Aufmerksamkeit zu hierarchisieren, statt sie zu zerstreuen.

Diese visuellen Entscheidungen dienen einer Produktüberzeugung, die ich im Abschnitt Vision der Landingpage ausführe. Der letzte Artikel der Serie betrachtet, was die Maschinen von all dem sehen: das mehrsprachige SEO und die Extraktion durch die KI-Antwortmaschinen. Wenn der Geist von Aubia Sie anspricht, können Sie sich in die Warteliste eintragen.

Dieses Projekt wird hier erzählt, Artikel für Artikel. Kommen Sie an Bord: Ihr Feedback zeichnet den weiteren Weg.

Auf die Warteliste