bifroest/app/static/css-framework/.opencode/skills/SKILL.md
2026-10-27 08:34:17 +01:00

406 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: css-framework
description: Erstellt und bearbeitet HTML/CSS/JS für CSS-Framework — ein classless-first CSS-Framework für Jinja2/HTMX-Webapps. Verwende diesen Skill für alle UI-Aufgaben: Layouts, Navigation, Buttons, Cards, Formulare (inkl. Switch, File Upload), Tabellen (inkl. sortierbar, sticky, mobile-stack), Modals, Dropdowns, Tabs, Alerts (inkl. dismissible), Accordion, Filter Chips, Timeline, Stepper, List Group, Meta List, Tooltip, Toast, Pagination, Feedback-Komponenten (Spinner, Skeleton, Stat, Avatar) sowie Alignment- und Spacing-Utilities. Auch für Design Tokens, Dark Mode, HTMX-Integration und die JavaScript-API (showToast, dismissAlert, sortTable, openModal, switchTab, toggleDropdown).
metadata:
author: tom
version: "2.1"
---
## Überblick
CSS-Framework ist ein schlankes, tokenbasiertes CSS-Framework für Flask/Jinja2-Webapps mit HTMX. Kein Build-Tool, keine Abhängigkeiten.
**Einbinden:**
```html
<script src="/static/js/theme.js"></script> <!-- im <head>, ohne defer -->
<link rel="stylesheet" href="/static/css/main.css">
<script src="/static/js/framework.js" defer></script>
```
**Datei-Architektur:**
| Datei | Zweck |
|---|---|
| `tokens.css` | CSS Custom Properties — einzige Source of Truth |
| `base.css` | Classless Reset — semantisches HTML braucht keine Klassen |
| `components.css` | Opt-in Klassen für Layouts & Komponenten |
| `utilities.css` | Minimale Helfer-Klassen |
| `main.css` | Entry Point |
| `framework.js` | JavaScript API — `data-action` und globale Funktionen |
| `theme.js` | Hell/Dunkel vor dem Rendern setzen und umschalten |
Vollständige Referenzen: [tokens.md](references/tokens.md) · [components.md](references/components.md) · [jinja2.md](references/jinja2.md)
---
## Kernprinzipien
1. **Classless first** — `h1–h6`, `p`, `a`, `table`, `input`, `select`, `details` werden ohne Klassen gestylt
2. **Opt-in Klassen** — nur wo semantisches HTML nicht ausreicht
3. **Token first** — alle Werte über `tokens.css`, nie hartverdrahten
4. **Dark Mode** via `data-theme="dark"` auf `<html>`
5. **Minimales JS** — framework-unabhängig, in `framework.js` via `<script src>` eingebunden
6. **CSP-fest** — Aktionen über `data-action`, Styles über Klassen, kein Inline-Code
---
## Design Tokens (Kurzübersicht)
```css
/* Hintergründe */ --bg --bg-subtle --bg-muted
/* Surfaces */ --surface --surface-raised --surface-overlay
/* Rahmen */ --border --border-strong --border-focus
/* Text */ --text --text-muted --text-subtle --text-on-accent
/* Akzentfarbe */ --accent --accent-hover --accent-subtle --accent-muted
/* Status */ --color-success --color-warning --color-danger --color-info
/* Spacing (4px-Grid) */ --space-1(4px) … --space-24(96px)
/* Typografie */ --font-sans --font-mono | --text-xs … --text-4xl
```
---
## Layout-Klassen
```html
<div class="container"> <!-- max-width 1280px -->
<div class="container container-md"> <!-- max-width 768px -->
<div class="stack"> <!-- flex-col, gap 1rem -->
<div class="stack stack-sm"> <!-- gap 0.5rem -->
<div class="stack stack-lg"> <!-- gap 2rem -->
<div class="cluster"> <!-- flex-wrap, gap 0.75rem, align center -->
<div class="grid grid-2"> <!-- 2 Spalten -->
<div class="grid grid-3"> <!-- 3 Spalten -->
<div class="grid grid-4"> <!-- 4 Spalten -->
<div class="with-sidebar"> <!-- sidebar-width + 1fr -->
```
---
## Komponenten (Schnellreferenz)
Vollständige Beispiele: [references/components.md](references/components.md)
### Navigation
```html
<nav class="topnav">...</nav> <!-- sticky, mit burger + dropdown -->
<aside class="sidebar">...</aside> <!-- mit .with-sidebar kombinieren -->
<nav class="breadcrumbs"><ol>...</ol></nav>
<footer class="footer">...</footer> <!-- .footer-full für mehrspaltig -->
```
### Buttons
```html
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-ghost">Ghost</button>
<button class="btn btn-danger">Danger</button>
<!-- Größen: btn-xs btn-sm btn-lg btn-xl | Modifiers: btn-full btn-icon -->
<div class="button-group">...</div> <!-- bündige Gruppe -->
<div class="segmented">...</div> <!-- Radio-Basis, kein JS -->
```
### Badges & Cards
```html
<span class="badge badge-success badge-dot">Live</span>
<!-- badge-default/primary/success/warning/danger/info/outline -->
<div class="card">
<div class="card-header"><h3>Titel</h3></div>
<div class="card-body">...</div>
<div class="card-footer">...</div>
</div>
<!-- card-flat / card-raised / card-ghost / card-hover -->
```
### Formulare
```html
<div class="form-group">
<label for="x">Label</label>
<input type="text" id="x">
<span class="form-hint">Hinweis</span>
</div>
<div class="form-row">...</div> <!-- nebeneinander -->
<input class="error"> <span class="form-error"> <!-- Fehler-State -->
<div class="input-group"><span class="input-group-prefix">https://</span><input></div>
<label class="file-upload">...</label> <!-- .file-upload-dropzone -->
<label class="switch"><input class="switch-input" type="checkbox">...</label>
```
### Tabellen
```html
<table>...</table> <!-- classless -->
<div class="table-wrap"><table>...</table></div> <!-- mit Rahmen + Scroll -->
<table class="table-striped">
<table class="table-compact">
<table class="table-sticky"> <!-- sticky thead -->
<table class="table-sticky-col"> <!-- sticky erste Spalte -->
<table class="table-stack"> <!-- Mobile: Key-Value (data-label auf td) -->
<table class="table-sortable"> <!-- data-action="sort" data-sort="" auf th -->
```
### Alerts & Feedback
```html
<div class="alert alert-success">
<div class="alert-content"><p class="alert-title">Titel</p><p>Text</p></div>
<button class="alert-dismiss" data-action="alert-dismiss">✕</button>
</div>
<!-- alert-info / alert-success / alert-warning / alert-danger -->
<div class="spinner"></div> <!-- spinner-sm / spinner-lg -->
<div class="progress"><div class="progress-bar" data-progress="75"></div></div>
<div class="skeleton skeleton-line is-short"></div> <!-- auch skeleton-title, skeleton-block -->
<div class="stat"><span class="stat-label">...</span><span class="stat-value">...</span></div>
<div class="avatar">AM</div> <!-- avatar-sm / avatar-lg -->
<div class="empty"><p class="empty-title">...</p></div>
<div class="divider">oder</div>
```
### Modals & Dropdowns & Tabs
```html
<div class="modal-backdrop" id="m" data-action="modal-backdrop">
<div class="modal modal-lg">
<div class="modal-header">...</div>
<div class="modal-body">...</div>
<div class="modal-footer">...</div>
</div>
</div>
<button data-action="modal-open" data-target="m">Öffnen</button>
<!-- modal-sm / modal-lg / modal-xl -->
<div class="dropdown" id="dd"><button data-action="dropdown" data-target="dd">...</button>
<div class="dropdown-menu"><button class="dropdown-item">...</button></div>
</div>
<div class="tabs">
<ul class="tab-list" role="tablist">
<li><button class="active" role="tab" data-action="tab" data-target="p1">Tab</button></li>
</ul>
<div id="p1" class="tab-panel active">...</div>
</div>
```
### Neue Komponenten
```html
<!-- Filter Chips (Checkbox/Radio, kein JS) -->
<div class="chip-group">
<label class="chip"><input class="chip-input" type="checkbox" checked><span class="chip-label">Design</span></label>
</div>
<!-- Timeline -->
<div class="timeline">
<div class="timeline-item timeline-success">
<div class="timeline-marker"><div class="timeline-dot"></div><div class="timeline-connector"></div></div>
<div class="timeline-content"><p class="timeline-title">Titel</p><p class="timeline-meta">Meta</p></div>
</div>
</div>
<!-- timeline-accent / timeline-success / timeline-warning / timeline-danger -->
<!-- Stepper -->
<div class="stepper">
<div class="step step-done"><div class="step-indicator">✓</div><span class="step-label">Schritt</span></div>
<div class="step step-active"><div class="step-indicator">2</div><span class="step-label">Aktiv</span></div>
<div class="step"><div class="step-indicator">3</div><span class="step-label">Offen</span></div>
</div>
<!-- .stepper-vertical für vertikale Variante -->
<!-- List Group / Action List -->
<ul class="list-group">
<li class="list-item list-item-active">Aktiv</li>
<li class="list-item">Eintrag</li>
</ul>
<div class="action-list">
<a href="#" class="action-item">
<div class="action-item-content"><p class="action-item-title">Titel</p></div>
</a>
</div>
<!-- list-group-compact / action-list-flush -->
<!-- Meta List -->
<dl class="meta-list">
<div class="meta-item"><dt class="meta-label">Status</dt><dd class="meta-value">Aktiv</dd></div>
</dl>
<!-- meta-list-stacked / meta-list-compact / meta-list-flush -->
<!-- Tooltip (CSS-only) -->
<button data-tooltip="Erklärung">Hover</button>
<button data-tooltip="Unten" data-tooltip-pos="bottom">...</button>
<!-- data-tooltip-pos: top (default) / bottom / left / right -->
<!-- Accordion -->
<div class="accordion">
<details class="accordion-item" open>
<summary>Frage</summary>
<div class="accordion-body"><p>Antwort</p></div>
</details>
</div>
<!-- accordion-flush / accordion-muted -->
```
---
## Utility-Klassen (Auswahl)
```
Display: block inline flex hidden
Flex: flex-center flex-between flex-start flex-end flex-col flex-wrap flex-1 flex-shrink-0
Alignment: items-start/center/end/stretch/baseline
justify-start/center/end/between
self-start/center/end/stretch
Gap: gap-1/2/3/4/6/8
Margin: mt-0/2/4/6/8/auto mb-0/2/4/6/8 ml-auto mr-auto mx-auto
Padding: p-0/2/4/6/8 px-4/6 py-4/6/8
Text: text-xs/sm/base/md/lg/xl text-left/center/right
text-muted text-subtle text-accent text-success text-warning text-danger
Font: font-normal/medium/semibold/bold font-mono uppercase capitalize truncate nowrap
Border: border border-t border-b rounded rounded-lg rounded-xl rounded-full
BG: bg-subtle bg-muted bg-surface
Shadow: shadow-xs shadow-sm shadow-md shadow-lg
Other: w-full h-full min-h-screen overflow-hidden overflow-auto overflow-x-auto
relative absolute sticky inset-0 cursor-pointer opacity-50 transition
filter-bar sr-only hide-mobile hide-desktop
```
---
## JavaScript API
Aktionen deklarativ über `data-action` — CSP-fest, funktioniert auch in HTMX-Fragmenten. **Nie `onclick`, `style=` oder Inline-`<script>`** (Consumer-Apps laufen mit strenger CSP).
| `data-action` | Weitere Attribute | Wirkung | Entspricht |
|---|---|---|---|
| `modal-open` | `data-target="id"` | Modal öffnen, Seite nicht mehr scrollen | `openModal(id)` |
| `modal-close` | `data-target="id"` (optional, sonst umgebendes Modal) | Modal schließen | `closeModal(id)` |
| `modal-backdrop` | auf `.modal-backdrop` mit `id` | Klick auf den Hintergrund schließt | `closeOnBackdrop(e, id)` |
| `tab` | `data-target="panel-id"` | Tab wechseln | `switchTab(btn, id)` |
| `dropdown` | `data-target="id"` (optional, sonst umgebendes `.dropdown`) | Dropdown öffnen/schließen | `toggleDropdown(id)` |
| `nav-dropdown` | – | Nav-Dropdown öffnen/schließen | `toggleNavDd(btn)` |
| `sidebar-open` / `sidebar-close` | – | Mobile Sidebar ein-/ausblenden | `toggleSidebar()` / `closeSidebar()` |
| `alert-dismiss` | – | Alert mit Transition entfernen | `dismissAlert(btn)` |
| `sort` | auf `th`, mit `data-sort=""` | Spalte sortieren | `sortTable(th)` |
| `toggle-class` | `data-class="checked"` | Klasse am Element umschalten | – |
| `toast` | `data-message="…"`, `data-type="success"` | Toast einblenden | `showToast(msg, type)` |
| `toast-close` | – | Toast schließen (setzt `showToast` selbst) | `removeToast(id)` |
| `theme-toggle` | – | Hell/Dunkel umschalten (`theme.js`) | – |
Für eigene Skripte gibt es dieselben Funktionen global: `openModal(id)`, `closeModal(id)`, `switchTab(btn, id)`, `toggleDropdown(id)`, `showToast(msg, type)`, `dismissAlert(btn)`, `sortTable(th)`. `showToast` setzt die Meldung als Text (kein HTML).
---
## Häufige Muster
### Seitenstruktur mit Sidebar
```html
<div class="mobile-topbar"><button data-action="sidebar-open">☰</button><span>App</span></div>
<div id="sidebar-backdrop" class="sidebar-backdrop" data-action="sidebar-close"></div>
<div class="with-sidebar">
<aside class="sidebar">...</aside>
<main class="stack stack-lg">...</main>
</div>
```
### Dashboard-Kacheln
```html
<div class="grid grid-3">
<div class="card"><div class="card-body">
<div class="stat"><span class="stat-label">Umsatz</span><span class="stat-value">€ 48.291</span></div>
</div></div>
</div>
```
### Detailseite mit Meta-Infos
```html
<div class="grid grid-2">
<div class="card"><div class="card-body">
<dl class="meta-list">
<div class="meta-item"><dt class="meta-label">Status</dt><dd class="meta-value"><span class="badge badge-success">Aktiv</span></dd></div>
</dl>
</div></div>
</div>
```
### Aktionsliste mit Zeilen-Alignment
```html
<div class="action-list">
<a href="/einstellungen" class="action-item">
<div class="action-item-content">
<p class="action-item-title">Einstellungen</p>
<p class="action-item-desc">Profil und Konto</p>
</div>
<svg class="action-item-end">›</svg>
</a>
</div>
```
### Filter-Leiste
```html
<div class="filter-bar">
<div class="form-group"><label>Monat</label><input type="month"></div>
<div class="chip-group"><label class="chip"><input class="chip-input" type="checkbox" checked><span class="chip-label">Aktiv</span></label></div>
<button class="btn btn-secondary btn-sm">Filtern</button>
</div>
```
---
## HTMX-Muster
Vollständige Referenz inkl. Basis-Template und Flask-Patterns: [references/jinja2.md](references/jinja2.md)
### Button Loading State
```html
<button class="btn btn-primary"
hx-post="/api/speichern"
hx-disabled-elt="this">
<span class="htmx-indicator spinner spinner-sm"></span>
Speichern
</button>
```
### Inline-Validation (422)
Server antwortet mit HTTP 422 + rendert Formular mit `.error`-Klassen auf Inputs:
```html
<form hx-post="/api/formular" hx-swap="outerHTML">
<div class="form-group">
<input type="email" name="email" class="error">
<span class="form-error">Ungültige E-Mail</span>
</div>
</form>
```
### Skeleton als Ladeindikator
```html
<div hx-get="/api/daten" hx-trigger="load" hx-swap="outerHTML">
<div class="stack">
<div class="skeleton skeleton-title"></div>
<div class="skeleton skeleton-line"></div>
</div>
</div>
```
### Modal via HTMX befüllen
```html
<button class="btn btn-secondary"
hx-get="/api/detail/{{ id }}"
hx-target="#modal-body"
hx-swap="innerHTML"
data-action="modal-open" data-target="m">Details</button>
<div class="modal-backdrop" id="m" data-action="modal-backdrop">
<div class="modal">
<div class="modal-body" id="modal-body">
<div class="skeleton skeleton-block"></div>
</div>
</div>
</div>
```
### Toast über HX-Trigger-Header
```python
# Flask: HX-Trigger im Response-Header setzen
response.headers['HX-Trigger'] = '{"showToast":{"message":"Gespeichert","type":"success"}}'
```
```html
<!-- body-Attribut im Basis-Template -->
<body hx-on::after-request="
var h=event.detail.xhr.getResponseHeader('HX-Trigger');
if(h){try{var t=JSON.parse(h);if(t.showToast)showToast(t.showToast.message,t.showToast.type);}catch(e){}}
">
```