DESIGN-SKILL.md
Die Designsprache von Open Analytics in einer einzigen Datei, geschrieben für KI-Agenten und die Menschen, die sie steuern. Jeder Wert hier wird im Produkt ausgeliefert: Hex-Codes, Federkonstanten, Radien, Easings. Fügen Sie diese Datei in den Kontext Ihres Agenten ein (oder legen Sie sie als Regeldatei in Ihr Repository) und bitten Sie um Bildschirme „im OA-Stil“.
Dies ist die kompilierte Einzelfile-Ausgabe, zum Einfügen gemacht. Die gleiche Sprache
wird in diesem Repository auch als echter Agenten-Skill ausgeliefert
(skills/oa-design/: komponentenspezifische Rezepte mit der vollständigen typgeprüften Quelle
eingebettet) und als CLI (npx getopen-design add <component>); bevorzugen Sie diese
für Claude Code und andere Skill-bewusste Agenten.
Der Look in einem Satz: weiße Oberflächen mit kontinuierlich gekrümmten Ecken, ruhend auf einer stillen grauen Bühne, gezeichnet in einer einzigen Tinte, bewegt von einer einzigen Feder.
1. Die zehn Regeln
- Eine Tinte, alles abgeleitet. Das gesamte neutrale System ist eine Farbe, gemischt mit Transparenz in festen Prozentsätzen. Nie eine zweite Graustufe wählen.
- Zwei Schichten, und der Zwischenraum ist die Seite. Oberflächen sind ein weißer Rahmen, der einen vertieften grauen Einsatz hält; Abschnitte sind Platten und der Seitenhintergrund zwischen ihnen ist der einzige Trenner. Keine horizontalen Linien.
- Squirl-Formen für Oberflächen, Pillen für Aktionen. Karten erhalten kontinuierlich gekrümmte Ecken; alles Anklickbare, das keine Karte ist, ist eine Pille.
- Eine Federfamilie. Sieben benannte Federn decken das gesamte Produkt ab. Erfinden Sie keine achte.
- Chrome wartet nie. Layout und Titel werden sofort gerendert; nur Daten wechseln aus einem pixelgenauen Skelett, ankommend durch Unschärfe, nicht durch Aufpoppen.
- Gewicht stoppt bei 500. Nichts Fettschrift. Hierarchie kommt von Größe, Farbe und Abstand.
- Ein Akzent, an einem Ort verbraucht. Ein einzelnes Blau für primäre Aktionen und die primäre Diagrammlinie; semantische Farben sind Texttönungen, keine Füllungen.
- Zustände erhalten Pillen, Ereignisse beenden sich selbst. Anhaltende Bedingungen werden als nicht entfernbare Streifen gerendert, die genau so lange leben wie der Zustand; einmalige Ergebnisse sind Toasts, die sich allein zurückziehen.
- Text ist Teil des Designs. Satzfall; Schaltflächen sagen, was passiert; Fehler nennen die Ursache und den Ausweg, ohne Schuldzuweisung.
- Qualitätsniveau, immer. Fokusringe,
role="status",aria-hiddenDekorationen,prefers-reduced-motionund kein horizontales Seiten-Scrolling.
2. Tokens
Hell
:root {
--ink: #292929;
--background: #f6f6f6; /* die graue Bühne zwischen den Platten */
--card: #ffffff;
--popover: #ffffff;
/* tintenabgeleitete Neutrale: die Prozentangaben SIND das System */
--border: color-mix(in srgb, var(--ink) 12%, transparent);
--input: color-mix(in srgb, var(--ink) 14%, transparent);
--accent: color-mix(in srgb, var(--ink) 5%, transparent); /* Hover-Waschung */
--muted: color-mix(in srgb, var(--ink) 5%, transparent);
--muted-foreground: #6d6d6d;
--foreground: var(--ink);
--primary: #305dde; /* der eine Akzent */
--primary-foreground: #ffffff;
--ring: #3ba6f1;
--secondary: #e9e9e9; /* flache graue Steuerelemente, kein Rand */
--secondary-foreground: var(--ink);
/* semantisch, als TEXT-Tönungen auf hellem Grund (Tailwind-Skala, Zeile 700) */
--destructive: var(--color-red-500);
--destructive-foreground: var(--color-red-700);
--success-foreground: var(--color-emerald-700);
--warning-foreground: var(--color-amber-700);
--info-foreground: var(--color-blue-700);
--chart-1: #296FF0; /* primäre Diagrammlinie */
--radius: 0.625rem;
}
Benötigen Sie einen stärkeren Trenner? Erhöhen Sie den Tintenprozentsatz (das Produkt verwendet nur 4, 5, 10, 12, 14). Semantische Farben kommen auf Wörter und 8px Punkte, niemals auf Panel-Füllungen.
Dunkel
Gleiches Rezept, neu eingefärbt, mit zwei systematischen Verschiebungen: Haarstrich-Prozentsätze steigen um 2 Punkte, und semantischer Text bewegt sich von der Zeile 700 zur Zeile 400.
.dark {
--ink: #ededed;
--background: #191919;
--card: #212121;
--popover: #242424;
--border: color-mix(in srgb, var(--ink) 14%, transparent);
--input: color-mix(in srgb, var(--ink) 16%, transparent);
--muted-foreground: #969696;
--secondary: #2e2e2e;
--primary: #296FF0;
--destructive-foreground: var(--color-red-400);
--success-foreground: var(--color-emerald-400);
}
Radius
Eine Basis, multipliziert; niemals ein maßgeschneiderter Radius:
--radius: 0.625rem; /* sm ×0.6, md ×0.8, lg ×1, xl ×1.4,
2xl ×1.8, 3xl ×2.2, 4xl ×2.6 */
rounded-full ist für Pillen reserviert. Squirl-Oberflächen haben ihre eigenen größeren
Radien (Abschnitt 4).
Typografie
- Inter Tight, variabel, Gewichte 300 bis 500, selbst gehostet (Schriftart-CDN brechen Builds; die woff2 im Repository behalten).
- Geist Mono für
code, kbd, samp, pre, Tasten und Schnipsel. - Die Gewichtsobergrenze ist die Identität: nichts wird schwerer als 500 gerendert.
Hierarchie kommt von Größe,
text-foreground/80vstext-muted-foreground, und Abstand. - Überschriften:
font-medium tracking-tight; Marketing-Überschriften fügentext-wrap: balancehinzu. - Daten:
tabular-numsbei jeder Zahl in einer Spalte oder bei Aktualisierung an Ort und Stelle. - Standard-UI-Stimme ist 14px; sekundäre Zeilen 12px, mit
leading-5/leading-6.
Schatten
Die Erhebung ist binär:
/* ruhende Karte */ box-shadow: 0 1px 2px rgba(0,0,0,0.06);
/* schwebendes Chrome */ box-shadow: 0 1px 2px rgba(0,0,0,0.08),
0 8px 24px rgba(0,0,0,0.08);
Hintergrund-Disziplin
body ist weiß (Überlauf zeigt den Körper; Grau, das hinter einem weißen
Bildschirm hervorlugt, liest sich wie eine andere Seite). Bildschirme, die die graue Bühne wollen, malen
ihren eigenen Hintergrund. Root erhält overflow-x: clip.
3. Layout
Platten. Abschnitte sind in sich geschlossene Panels; der Seitenhintergrund, der zwischen
ihnen sichtbar ist, ist die einzige Grenze. Vertikale Abstände großzügig
(gap-12 sm:gap-20 Marketing, gap-6 in der App), seitlicher Abstand enger
(px-4 sm:px-6). Geschwister werden durch den gap des Elternteils beabstandet, niemals durch
Ränder pro Kind. Kein <hr>, kein border-b zwischen Abschnitten.
Breiten pro Bereich: Marketing 76rem (6.5xl), App-Seiten max-w-6xl,
Listen-/Auswahlbildschirme max-w-5xl, Leseflächen ~65ch. Zentriert, mx-auto.
App-Seitenanatomie:
fester Header (h-14) → Container pt-14
optionale schwebende Pille (fixed top-16, zentriert)
<main class="px-4 pb-36 pt-8 sm:px-6 bg-white">
Titelzeile: h1 text-xl font-medium tracking-tight + EIN Steuerelement auf der rechten Seite
optionale Hinweisstreifen
Platten: flex flex-col gap-6
Die Titelzeile enthält genau ein Steuerelement. pb-36 hält den Inhalt von der
Ansichtsfenstergrenze fern.
Raster: grid gap-3, responsive Spalten (grid-cols-2 sm:grid-cols-4 für
Statistiken). Karten teilen sich eine feste Inhaltshöhe und scrollen intern; die Grundlinie des Rasters
bricht nie.
Responsive: Design bei 390px und 1280px. Horizontale Paarungen stapeln unter
sm: mit der Aktion zuletzt. Breiter Inhalt scrollt in seinem eigenen
overflow-x-auto Container; die Seite scrollt niemals seitwärts.
Einstellungen: schmale Registerkartenleiste + Inhalts Spalte von Abschnittspanels; jeder Abschnitt
beginnt mit einem Überschriftsblock (Titel + ein grauer Satz, optionale einzelne Aktion
auf der rechten Seite). Verlinken Sie tief auf Registerkarten via ?tab=<id>.
4. Komponenten
Die Squirl-Oberfläche
Kontinuierlich gekrümmte Ecken via eines CSS shape() Clip-Pfads, parametrisiert durch
zwei Variablen, mit corner-shape: squircle wo unterstützt:
.squircle {
border-radius: 26px;
--card-clip-radius: 14px;
--card-clip-handle: 2.25px;
clip-path: shape(
from var(--card-clip-radius) 0px,
line to calc(100% - var(--card-clip-radius)) 0px,
curve to 100% var(--card-clip-radius)
with calc(100% - var(--card-clip-handle)) 0px / 100% var(--card-clip-handle),
line to 100% calc(100% - var(--card-clip-radius)),
curve to calc(100% - var(--card-clip-radius)) 100%
with 100% calc(100% - var(--card-clip-handle)) / calc(100% - var(--card-clip-handle)) 100%,
line to var(--card-clip-radius) 100%,
curve to 0px calc(100% - var(--card-clip-radius))
with var(--card-clip-handle) 100% / 0px calc(100% - var(--card-clip-handle)),
line to 0px var(--card-clip-radius),
curve to var(--card-clip-radius) 0px
with 0px var(--card-clip-handle) / var(--card-clip-handle) 0px,
close
);
corner-shape: squircle;
}
@media (min-width: 640px) {
.squircle { border-radius: 50px; --card-clip-radius: 20px; --card-clip-handle: 3px; }
}
Die Minikarte
äußere Squirl-Form: bg-card, border, p-1, ruhender Schatten
├── Kopfstreifen (pb-2 pl-3.5 pr-2 pt-1.5):
│ 16px gedämpftes Icon + text-sm font-medium text-foreground/80 Titel
│ optionaler Chip auf der Mittellinie des Titels
│ "Alle ansehen ›" (text-xs muted; hover: accent wash, Pfeil gleitet 2px)
└── innere Squirl-Form: bg #f6f6f6, border, rounded-[22px] sm:rounded-[44px],
FESTE Höhe (h-44 = fünf 32px Zeilen), overflow hidden
Karten wachsen nie mit Daten; längere Listen scrollen innen. Überlauf zeigt einen 24px
Chevron-Chip unten rechts, der y:[0,2.5,0] auf und ab wippt (1,4s easeInOut Schleife), ausgeblendet
(0.18s, Skalierung 0.7) am Ende des Scrollens. Zeilen: 32px, vollflächige
Akzent-Waschung bei Hover, tabular-nums Werte.
Schaltflächen
Pillen, font-medium, physisch gedrückt:
active:translate-y-px active:scale-[0.98].
- primär: Akzentblau mit einer Abschrägung (Hintergrund/Rand farbgemischt in Richtung
eines tiefen Indigos,
inset 0 1px 0 rgba(255,255,255,0.22)oben, dunkel inset unten); Hover hellt in Richtung reines Primär auf. - sekundär: flach
--secondary, transparenter Rand, Hover mischt 5% Tinte. Das Arbeitstier. - ghost: nur gedämpfter Text. destructive: rote Füllung, nur Bestätigungsschritt.
- Größen h-9 / h-8 / h-7; App-Chrome läuft meist mit den kleinen.
- Laden behält das Label und fügt einen 14px Ring-Spinner hinzu
(
border-2 border-current border-t-transparent); Breite springt nie. focus-visible:ring-[3px]in--ring/50. Niemals entfernt.
Dropdown-Panels (Menüs, Auswahlen, Schalter)
Auslöser-Pille → schwebendes Panel (schwebender Schatten, Rand, weiß, rounded-2xl+), einschwingen bei PANEL (550/38) mit kleinem y/scale-Offset, schnellerer Ausgang. 32px rounded-lg Elemente mit Akzent-Waschung bei Hover; die aktive Hervorhebung ist ein gemeinsames element, das sich bewegt (layoutId oder width/height Federn), niemals umzeichnen pro Element. Schließen bei außerhalb pointerdown und Escape.
Modals
Hintergrund blendet ~0,15s ein; Panel schwingt bei POP (400/26) ein von
opacity 0, scale .96, Ausgang bei 380/28. Mehrschritt-Dialoge behalten ein Panel,
dessen Höhe zu jedem Schritt gemessener Größe animiert (LAYOUT 550/40 +
ResizeObserver), während Schritte durchgleiten (x: ±12px, popLayout, 0.16s
easeOut ausgeblendet). Fußzeile: h-12 Streifen, Zurück (sekundär xs) + primär xs, rechts
ausgerichtet.
Registerkartenleiste
Die aktive Pille ist ein festes Fenster; Labels gleiten dahinter. Das eingehende Label tritt aus der Bewegungsrichtung bei 110% seiner eigenen Breite ein; das ausgehende verlässt ein festes 130px auf die andere Seite (ein Prozentsatz reicht nicht aus, wenn die Pille in Richtung eines längeren Wortes wächst). Pillenbreite/-höhe folgen dem gemessenen Label auf LAYOUT-Federn. Volle Deckkraft während des gesamten Vorgangs; die Maske erledigt die Arbeit.
Skelette
- Chrome wartet nie; nur der Datenbereich ist Skelett.
- Pixelgenau: Ein Balken sitzt im gleichen Zeilenhöhen-Slot wie der Text, den er
repräsentiert (
flex h-6 items-center, das einenh-3Balken umschließt); der Austausch bewegt nichts. - Daten kommen durch Fokus an: eine einmalige Animation von
opacity ~0.4 + blur(4px)zu scharf. Eine Animation, kein Übergang (der Inhalt wird bereits endgültig eingehängt; ein Übergang hat keinen vorherigen Wert). Behalten Siefilter: blur(0)danach bei.
Balken: rounded bg-muted-foreground/15, langsamer Hintergrundpositions-Durchlauf
(2s linear unendlich, -1s Startversatz, so dass Balken phasenverschoben sind).
Hinweise, leere Zustände, die schwebende Pille
Hinweisstreifen oberhalb des Inhalts, den er erklärt:
<div class="flex flex-col gap-2 rounded-2xl border border-border bg-card
px-4 py-3 sm:flex-row sm:items-center sm:justify-between"
role="status">
<p class="text-sm leading-6 text-muted-foreground">
<span class="font-medium text-foreground">Noch keine Ereignisse.</span>
Wenn das Snippet live ist, durchsuchen Sie Ihre Website und wir erfassen den ersten
Seitenaufruf innerhalb von Sekunden.
</p>
<button class="…secondary pill…">Installation prüfen</button>
</div>
Eine fette Behauptung, ein grauer Satz, eine sekundäre Aktion; stapelt unter sm:;
lebt genau so lange wie der Zustand; keine Entfernung.
Schwebende Pille für app-weite anhaltende Zustände: fixed top-16, zentriert,
rounded-full bg-card py-1.5 pl-3 pr-4 text-xs font-medium, schwebender Schatten,
8px Statuspunkt (pulsierend bei Dringlichkeit), einschwingend bei BANNER (400/30) von
y:-8, Ausgang 0.15s. Die ganze Pille verlinkt dorthin, wo der Zustand aufgelöst wird.
Zustandspillen entfernen sich nie selbst; Ereignispillen ziehen sich nach ~6s zurück.
Toasts
Unter 0,35s, Easing cubic-bezier(0.5, 1, 0.89, 1):
- Erfolg: Puls
scale 1 → 1.025 → 0.99 → 1(0,32s). - Fehler: Schütteln
x 0 → -3 → 3 → -3 → 0px (0,28s).
Abwechselnde ungerade/gerade Keyframe-Kopien, so dass Wiederholungen neu starten.
5. Bewegung
| Name | Wert | Verwendung für |
|---|---|---|
| PANEL | 550 / 38 | Dropdowns, Menüs, Boards, Schalter |
| LAYOUT | 550 / 40 | gemessene Höhe/Breite, gleitende Pillen |
| POP | 400 / 26 | Modaleingang |
| POP_EXIT | 380 / 28 | Modalausgang |
| BANNER | 400 / 30 | schwebende Pillen, Seitenbanner |
| FLICK | 900 / 50 | Mikrobewegungen von Icons |
| CHART | 300 / 25-30 | Diagramm-Tooltips, Fadenkreuz |
Mikroausblendungen: 0,1s bis 0,18s easeOut. Nichts in App-Chrome tweened länger als 0,2s; wenn es sich langsam anfühlt, verringern Sie die Dämpfung, nicht die Dauer verlängern.
Signaturbewegungen: gemessene Höhenchoreographie (ein konstantes Panel, Schritte durchgleitend), die wandernde Hervorhebung (ein gemeinsames aktives Element, niemals umzeichnen pro Element), Eingangs-/Ausgangsasymmetrie (Ausgänge immer schneller), Hover als nur Farbe (plus höchstens einen 2px Pfeil-Schlitten; Karten heben oder skalieren nie) und höchstens eine winzige Umgebungsschleife pro Bildschirm.
Regeln: AnimatePresence für alles, was ausgehängt wird; respektieren Sie
prefers-reduced-motion (Einklappungen auf sichtbar reduzieren, Schleifen stoppen); animieren Sie nie
ungemessenes Layout; lesen Sie nie die Wanduhr während des Renderns (abonnieren Sie einen
laufenden Store in Minutenblöcken).
6. Landingpages
Gleiche Tokens, s
…
[gekürzt zur Übersetzung]