diff --git a/.claude/rules/stack-and-commands.md b/.claude/rules/stack-and-commands.md index dc53ac6..bc54d15 100644 --- a/.claude/rules/stack-and-commands.md +++ b/.claude/rules/stack-and-commands.md @@ -1,19 +1,32 @@ ## Stack - -- **Language:** -- **Key deps:** +- **Language:** Python 3.12 (Django 6), Node 22 (Vite + Tailwind v4) +- **Key deps:** Django, htmx, Alpine.js, Tailwind v4, whynot-design (vendored + under `static/src/vendor/whynot-design/`) ## Dev Commands ```bash -# TODO: Fill in the standard commands for this repo - # Install dependencies +uv sync # Python +npm ci # Node (Vite/Tailwind) -# Run tests +# Run dev stack +make db # Start postgres if not running +make dev # Django runserver on :9000 +make css # Tailwind/Vite watcher (rebuilds main.css) -# Lint / type check +# Build CSS bundle for prod / for verification +npm run build # → static/dist/main.css -# Build / package (if applicable) +# Re-vendor the whynot-design system from a pinned upstream commit +make sync-whynot-design # reads .whynot-design-ref by default + # or: ./scripts/sync-whynot-design.sh + +# Tests / lint +make test # uv run pytest +make lint # ruff + mypy ``` + +See `wiki/DesignSystem.md` for the whynot-design adoption status (Phase 1 +tokens+CSS done; Phase 2 components deferred) and local style conventions. diff --git a/Makefile b/Makefile index b36abe2..2e2931a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help db dev css seed migrate test lint shell superuser collectstatic +.PHONY: help db dev css seed migrate test lint shell superuser collectstatic sync-whynot-design .DEFAULT_GOAL := help @@ -41,3 +41,6 @@ shell: ## Open a Django shell (shell_plus if available) collectstatic: ## Collect static files into staticfiles/ (production step) uv run manage.py collectstatic --noinput + +sync-whynot-design: ## Re-vendor whynot-design CSS+tokens from the pinned ref + ./scripts/sync-whynot-design.sh diff --git a/scripts/sync-whynot-design.sh b/scripts/sync-whynot-design.sh new file mode 100755 index 0000000..2ec8a01 --- /dev/null +++ b/scripts/sync-whynot-design.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Synchronises the vendored copy of the whynot-design system from a pinned +# upstream commit. Source: ~/whynot-design (worktree) or a clone from gitea. +# +# Usage: ./scripts/sync-whynot-design.sh [] +# Default: reads .whynot-design-ref from the vendor directory. +# +# See workplans/WP-0017-whynot-design-tokens.md for the adoption strategy. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +VENDOR_DIR="$ROOT/static/src/vendor/whynot-design" +REF_FILE="$VENDOR_DIR/.whynot-design-ref" +SRC_REPO="${WHYNOT_DESIGN_SRC:-$HOME/whynot-design}" + +REF="${1:-}" +if [[ -z "$REF" && -f "$REF_FILE" ]]; then + REF="$(cat "$REF_FILE")" +fi +if [[ -z "$REF" ]]; then + echo "Usage: $0 (or write a ref to $REF_FILE)" >&2 + exit 2 +fi + +if [[ ! -d "$SRC_REPO/.git" ]]; then + echo "Source not found: $SRC_REPO" >&2 + echo "Set WHYNOT_DESIGN_SRC or clone gitea:whynot/whynot-design there." >&2 + exit 1 +fi + +mkdir -p "$VENDOR_DIR/tokens" +git -C "$SRC_REPO" show "$REF:src/styles/colors_and_type.css" \ + > "$VENDOR_DIR/colors_and_type.css" +for f in colors.json type.json spacing.json index.json; do + git -C "$SRC_REPO" show "$REF:tokens/$f" > "$VENDOR_DIR/tokens/$f" +done +git -C "$SRC_REPO" rev-parse "$REF" > "$REF_FILE" + +echo "Vendor synced → $VENDOR_DIR (ref: $(cat "$REF_FILE"))" diff --git a/static/src/main.css b/static/src/main.css index bc4d68e..b2eb910 100644 --- a/static/src/main.css +++ b/static/src/main.css @@ -1,3 +1,9 @@ +/* whynot-design tokens & semantic element styles (pinned via + scripts/sync-whynot-design.sh; see .whynot-design-ref). + Must precede the Tailwind import so the @import url(...) for IBM Plex + ends up at the top of the generated bundle. */ +@import "./vendor/whynot-design/colors_and_type.css"; + @import "tailwindcss"; /* Explicit content sources. Without these, Tailwind's automatic detection @@ -7,44 +13,88 @@ template dirs copied in the Dockerfile `assets` stage. */ @source "../../vergabe_teilnahme/templates"; +/* whynot tokens → Tailwind theme. Exposes utilities like bg-paper, text-ink, + border-line, bg-paper-2, text-ink-3, … */ @theme { - --color-brand-50: #f0f4ff; - --color-brand-100: #dce7ff; - --color-brand-500: #3b5bdb; - --color-brand-600: #2f4ac7; - --color-brand-700: #2541b2; - --color-brand-900: #152d99; + --color-ink: #0A0A0A; + --color-ink-2: #1F1F1F; + --color-ink-3: #5C5C5C; + --color-ink-4: #8A8A8A; + --color-ink-5: #B5B5B3; + --color-line: #E5E5E2; + --color-line-strong: #C9C9C5; + --color-line-soft: #F0F0EC; + --color-paper: #FFFFFF; + --color-paper-2: #FAFAF7; + --color-paper-3: #F4F4EF; + --color-hi: #FFE14A; + --color-hi-2: #FFD400; + --color-hi-ink: #1A1500; + + /* Backwards-compat aliases for legacy `brand-*` utility usage in templates. + Keeps Phase 1 a tokens-only swap; templates can migrate to ink/paper at + leisure. Map blue-brand scale onto the whynot ink ramp. */ + --color-brand-50: #FAFAF7; + --color-brand-100: #F4F4EF; + --color-brand-500: #0A0A0A; + --color-brand-600: #1F1F1F; + --color-brand-700: #0A0A0A; + --color-brand-900: #0A0A0A; +} + +/* Off-spec — vergabe-local until whynot-design defines a canonical + destructive color. See history/2026-05-23-whynot-design-cross-framework-analysis.md + §4 for context. */ +:root { + --danger: #B22222; + --danger-fg: #FFFFFF; } @layer base { - /* German-app base resets */ html { - font-family: ui-sans-serif, system-ui, sans-serif; + font-family: var(--ff-sans, ui-sans-serif), system-ui, sans-serif; } } @layer components { - .card { @apply bg-white rounded-xl border border-slate-200 shadow-sm p-6; } - .btn-primary { @apply bg-brand-500 text-white px-4 py-2 rounded-lg hover:bg-brand-600 transition-colors; } - .btn-secondary { @apply bg-white text-slate-700 border border-slate-300 px-4 py-2 rounded-lg hover:bg-slate-50; } - .btn-danger { @apply bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700; } - .btn-ghost { @apply text-slate-600 px-3 py-2 rounded-lg hover:bg-slate-100; } - .field-row { @apply grid grid-cols-3 gap-4 py-3 border-b border-slate-100 last:border-0; } - .field-label { @apply text-sm font-medium text-slate-500 col-span-1; } - .field-value { @apply text-sm text-slate-900 col-span-2; } - .phase-badge { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold; } - .phase-todo { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-slate-200 text-slate-500; } - .phase-active { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-brand-500 text-white; } - .phase-done { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-green-500 text-white; } - .phase-warn { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-amber-400 text-amber-900; } - .section-title { @apply text-base font-semibold text-slate-900 mb-4; } - .page-title { @apply text-2xl font-bold text-slate-900; } - .form-input { @apply w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent; } - .form-label { @apply block text-sm font-medium text-slate-700 mb-1; } - .table-base { @apply w-full text-sm text-left; } - .table-header { @apply bg-slate-50 text-slate-500 font-medium text-xs uppercase tracking-wide; } - .table-row { @apply border-t border-slate-100 hover:bg-slate-50 transition-colors; } - .sidebar-link { @apply flex items-center px-3 py-2 rounded-lg text-sm text-slate-700 hover:bg-slate-100 transition-colors; } - .sidebar-link-active { @apply bg-brand-50 text-brand-700 font-medium; } - .sidebar-section-btn { @apply w-full flex items-center justify-between px-3 py-2 text-xs font-semibold text-slate-500 uppercase tracking-wide hover:text-slate-700; } + /* Cards / sheets — whynot: no shadow, hairline border */ + .card { @apply bg-paper rounded border border-line p-6; } + + /* Buttons — whynot: 3 variants + off-spec danger */ + .btn-primary { @apply bg-ink text-paper px-4 py-2 rounded hover:bg-ink-2 transition-colors; } + .btn-secondary { @apply bg-paper text-ink border border-line px-4 py-2 rounded hover:bg-paper-2 transition-colors; } + .btn-ghost { @apply text-ink-3 px-3 py-2 rounded hover:bg-paper-2; } + .btn-danger { background: var(--danger); color: var(--danger-fg); @apply px-4 py-2 rounded transition-colors; } + .btn-danger:hover { filter: brightness(0.92); } + + /* Field-row — label/value grid */ + .field-row { @apply grid grid-cols-3 gap-4 py-3 border-b border-line-soft last:border-0; } + .field-label { @apply text-sm font-medium text-ink-3 col-span-1; } + .field-value { @apply text-sm text-ink col-span-2; } + + /* Phase indicators — vergabe semantics (todo/active/done/warn), translated + into whynot palette. `phase-warn` uses --hi (annotation yellow). */ + .phase-badge { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold; } + .phase-todo { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-paper-3 text-ink-4; } + .phase-active { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-ink text-paper; } + .phase-done { @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold bg-ink-3 text-paper; } + .phase-warn { background: var(--hi); color: var(--hi-ink); @apply inline-flex items-center justify-center w-7 h-7 rounded-full text-sm font-bold; } + + /* Titles / sections */ + .section-title { @apply text-base font-semibold text-ink mb-4; } + .page-title { @apply text-2xl font-medium text-ink tracking-tight; } + + /* Forms */ + .form-input { @apply w-full rounded border border-line px-3 py-2 text-sm bg-paper focus:outline-none focus:border-ink transition-colors; } + .form-label { @apply block text-sm font-medium text-ink-2 mb-1; } + + /* Tables */ + .table-base { @apply w-full text-sm text-left; } + .table-header { @apply bg-paper-2 text-ink-3 font-medium text-xs uppercase tracking-wide; } + .table-row { @apply border-t border-line-soft hover:bg-paper-2 transition-colors; } + + /* Sidebar */ + .sidebar-link { @apply flex items-center px-3 py-2 rounded text-sm text-ink-2 hover:bg-paper-2 transition-colors; } + .sidebar-link-active { @apply bg-paper text-ink font-medium; box-shadow: inset 0 0 0 1px var(--line); } + .sidebar-section-btn { @apply w-full flex items-center justify-between px-3 py-2 text-xs font-semibold text-ink-4 uppercase tracking-wide hover:text-ink-2; } } diff --git a/static/src/vendor/whynot-design/.whynot-design-ref b/static/src/vendor/whynot-design/.whynot-design-ref new file mode 100644 index 0000000..8c65555 --- /dev/null +++ b/static/src/vendor/whynot-design/.whynot-design-ref @@ -0,0 +1 @@ +9419f166ce395858f55b10a5c72268a1fe9fc9d2 diff --git a/static/src/vendor/whynot-design/colors_and_type.css b/static/src/vendor/whynot-design/colors_and_type.css new file mode 100644 index 0000000..d64a8ff --- /dev/null +++ b/static/src/vendor/whynot-design/colors_and_type.css @@ -0,0 +1,273 @@ +/* ============================================================ + WhyNot Design System — Colors & Type + ------------------------------------------------------------ + Neutral, mostly black/white. Color is used SPARINGLY — only + one warm accent (annotation yellow) borrowed from the LEGO + brick in the logo. The system favours light grey wireframe + artefacts over heavy fills. + ============================================================ */ + +/* ---------- Webfonts (Google Fonts, see /fonts for offline) ---------- */ +@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@300;400;500;600;700&family=IBM+Plex+Serif:ital,wght@0,400;0,500;1,400&display=swap"); + +:root { + /* ---------- Base palette: neutrals ---------- */ + --ink: #0A0A0A; /* near-black, the only "fill" most of the time */ + --ink-2: #1F1F1F; + --ink-3: #5C5C5C; + --ink-4: #8A8A8A; + --ink-5: #B5B5B3; /* placeholder text, wireframe labels */ + --line: #E5E5E2; /* default 1px wireframe rule */ + --line-strong: #C9C9C5; /* dividers between sections */ + --line-soft: #F0F0EC; /* hairline within a card */ + --paper: #FFFFFF; /* canvas */ + --paper-2: #FAFAF7; /* sheet, dim canvas */ + --paper-3: #F4F4EF; /* recessed surface, code block bg */ + + /* ---------- Foreground / background semantic ---------- */ + --fg-1: var(--ink); + --fg-2: var(--ink-3); + --fg-3: var(--ink-4); + --fg-mute: var(--ink-5); + --fg-on-dark: #FAFAF7; + + --bg-1: var(--paper); + --bg-2: var(--paper-2); + --bg-3: var(--paper-3); + --bg-invert: var(--ink); + + --border: var(--line); + --border-strong: var(--line-strong); + --border-soft: var(--line-soft); + + /* ---------- The single accent: annotation yellow ---------- */ + /* Lifted from the LEGO brick. Used as highlighter, "draft" + stamp, signal-marker. Never as a button fill. */ + --hi: #FFE14A; + --hi-2: #FFD400; + --hi-ink: #1A1500; /* text on yellow */ + + /* ---------- Status (for prototype lifecycle, signal strength) ---------- */ + /* Kept deliberately desaturated so they read as labels, not UI. */ + --status-raw: #B5B5B3; /* S0 — no signal */ + --status-weak: #8A8A8A; /* S1 — weak signal */ + --status-medium: #5C5C5C; /* S2 — medium signal */ + --status-strong: #0A0A0A; /* S3 — strong signal */ + --status-commercial: #FFD400; /* S4 — commercial */ + + /* ---------- Type families ---------- */ + --ff-sans: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif; + --ff-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace; + --ff-serif: "IBM Plex Serif", "Iowan Old Style", Georgia, serif; + + /* ---------- Type scale (modular, ~1.2) ---------- */ + --fs-xs: 11px; + --fs-sm: 13px; + --fs-base: 15px; + --fs-md: 17px; + --fs-lg: 20px; + --fs-xl: 24px; + --fs-2xl: 32px; + --fs-3xl: 44px; + --fs-4xl: 64px; + --fs-5xl: 96px; + + --lh-tight: 1.05; + --lh-snug: 1.25; + --lh-base: 1.5; + --lh-loose: 1.7; + + --tr-tight: -0.02em; + --tr-snug: -0.01em; + --tr-base: 0em; + --tr-mono: 0.02em; + --tr-label: 0.08em; /* uppercase eyebrow labels */ + + /* ---------- Spacing (4px base) ---------- */ + --sp-1: 4px; + --sp-2: 8px; + --sp-3: 12px; + --sp-4: 16px; + --sp-5: 24px; + --sp-6: 32px; + --sp-7: 48px; + --sp-8: 64px; + --sp-9: 96px; + --sp-10: 128px; + + /* ---------- Radii — small, mostly square ---------- */ + --r-0: 0px; + --r-1: 2px; + --r-2: 4px; + --r-3: 8px; + --r-pill: 999px; + + /* ---------- Elevation — almost none. This is a wireframe system. ---------- */ + --shadow-0: none; + --shadow-1: 0 1px 0 var(--line); + --shadow-2: 0 1px 0 var(--line-strong); + --shadow-3: 0 4px 12px -6px rgba(10,10,10,0.10); +} + +/* ============================================================ + Semantic element styles + ============================================================ */ + +html { + font-family: var(--ff-sans); + font-size: var(--fs-base); + line-height: var(--lh-base); + color: var(--fg-1); + background: var(--bg-1); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; +} + +body { + margin: 0; + font-feature-settings: "ss01", "cv11"; + text-wrap: pretty; +} + +/* ---------- Headings ---------- */ +h1, .h1 { + font: 600 var(--fs-3xl)/var(--lh-tight) var(--ff-sans); + letter-spacing: var(--tr-tight); + margin: 0 0 var(--sp-5); + color: var(--fg-1); +} +h2, .h2 { + font: 500 var(--fs-2xl)/var(--lh-snug) var(--ff-sans); + letter-spacing: var(--tr-snug); + margin: 0 0 var(--sp-4); +} +h3, .h3 { + font: 500 var(--fs-xl)/var(--lh-snug) var(--ff-sans); + letter-spacing: var(--tr-snug); + margin: 0 0 var(--sp-3); +} +h4, .h4 { + font: 500 var(--fs-lg)/var(--lh-snug) var(--ff-sans); + margin: 0 0 var(--sp-2); +} +h5, .h5 { + font: 500 var(--fs-md)/var(--lh-snug) var(--ff-sans); + margin: 0 0 var(--sp-2); +} + +/* ---------- Display (for hero / title slides) ---------- */ +.display-1 { + font: 300 var(--fs-5xl)/0.95 var(--ff-sans); + letter-spacing: -0.035em; + color: var(--fg-1); +} +.display-2 { + font: 400 var(--fs-4xl)/1.0 var(--ff-sans); + letter-spacing: var(--tr-tight); +} + +/* ---------- Body ---------- */ +p { + margin: 0 0 var(--sp-4); + line-height: var(--lh-base); + color: var(--fg-1); +} +.lead { + font-size: var(--fs-md); + line-height: 1.55; + color: var(--fg-2); +} +small, .small { + font-size: var(--fs-sm); + color: var(--fg-2); +} + +/* ---------- Eyebrow / uppercase labels (very common in this system) ---------- */ +.eyebrow, +.label { + font: 500 var(--fs-xs)/1.2 var(--ff-mono); + letter-spacing: var(--tr-label); + text-transform: uppercase; + color: var(--fg-3); +} + +/* ---------- Code / mono ---------- */ +code, kbd, samp, pre, .mono { + font-family: var(--ff-mono); + font-size: 0.92em; + letter-spacing: var(--tr-mono); +} +code { + background: var(--bg-3); + padding: 1px 6px; + border-radius: var(--r-1); + color: var(--ink-2); +} +pre { + background: var(--bg-3); + border: 1px solid var(--border); + padding: var(--sp-4); + overflow-x: auto; + border-radius: var(--r-2); + font-size: var(--fs-sm); + line-height: var(--lh-snug); +} +pre code { background: none; padding: 0; } + +/* ---------- Editorial serif moments ---------- */ +.serif { font-family: var(--ff-serif); } +.serif-quote { + font: 400 italic var(--fs-xl)/1.4 var(--ff-serif); + color: var(--fg-2); +} + +/* ---------- Links ---------- */ +a { + color: var(--fg-1); + text-decoration: underline; + text-decoration-color: var(--border-strong); + text-underline-offset: 3px; + text-decoration-thickness: 1px; + transition: text-decoration-color 120ms ease, color 120ms ease; +} +a:hover { + text-decoration-color: var(--fg-1); +} + +/* ---------- HR ---------- */ +hr { + border: 0; + border-top: 1px solid var(--border); + margin: var(--sp-5) 0; +} + +/* ---------- Highlighter (the one place yellow appears in body copy) ---------- */ +mark, .mark { + background: var(--hi); + color: var(--hi-ink); + padding: 0 2px; +} + +/* ---------- Tables (used in templates) ---------- */ +table { + width: 100%; + border-collapse: collapse; + font-size: var(--fs-sm); +} +th, td { + text-align: left; + padding: var(--sp-3) var(--sp-4); + border-bottom: 1px solid var(--border); +} +th { + font-weight: 500; + color: var(--fg-2); + font-family: var(--ff-mono); + font-size: var(--fs-xs); + letter-spacing: var(--tr-label); + text-transform: uppercase; +} + +/* ---------- Selection ---------- */ +::selection { background: var(--hi); color: var(--hi-ink); } diff --git a/static/src/vendor/whynot-design/tokens/colors.json b/static/src/vendor/whynot-design/tokens/colors.json new file mode 100644 index 0000000..00f0590 --- /dev/null +++ b/static/src/vendor/whynot-design/tokens/colors.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://design-tokens.github.io/community-group/format/", + "ink": { "value": "#0A0A0A", "type": "color", "comment": "Near-black. The only fill most of the time." }, + "ink-2": { "value": "#1F1F1F", "type": "color" }, + "ink-3": { "value": "#5C5C5C", "type": "color" }, + "ink-4": { "value": "#8A8A8A", "type": "color" }, + "ink-5": { "value": "#B5B5B3", "type": "color", "comment": "Placeholder text, wireframe labels." }, + "line": { "value": "#E5E5E2", "type": "color", "comment": "Default 1px wireframe rule." }, + "line-strong": { "value": "#C9C9C5", "type": "color" }, + "line-soft": { "value": "#F0F0EC", "type": "color" }, + "paper": { "value": "#FFFFFF", "type": "color" }, + "paper-2": { "value": "#FAFAF7", "type": "color" }, + "paper-3": { "value": "#F4F4EF", "type": "color" }, + "hi": { "value": "#FFE14A", "type": "color", "comment": "Annotation yellow. Highlighter only, never a button fill." }, + "hi-2": { "value": "#FFD400", "type": "color" }, + "hi-ink": { "value": "#1A1500", "type": "color", "comment": "Text on yellow." }, + "status-raw": { "value": "#B5B5B3", "type": "color", "comment": "S0 — no signal" }, + "status-weak": { "value": "#8A8A8A", "type": "color", "comment": "S1 — weak signal" }, + "status-medium": { "value": "#5C5C5C", "type": "color", "comment": "S2 — medium signal" }, + "status-strong": { "value": "#0A0A0A", "type": "color", "comment": "S3 — strong signal" }, + "status-commercial": { "value": "#FFD400", "type": "color", "comment": "S4 — commercial" } +} diff --git a/static/src/vendor/whynot-design/tokens/index.json b/static/src/vendor/whynot-design/tokens/index.json new file mode 100644 index 0000000..974065d --- /dev/null +++ b/static/src/vendor/whynot-design/tokens/index.json @@ -0,0 +1,6 @@ +{ + "comment": "Manifest pointing at the three token files. Source-of-truth for any future Style Dictionary build.", + "colors": "./colors.json", + "type": "./type.json", + "spacing": "./spacing.json" +} diff --git a/static/src/vendor/whynot-design/tokens/spacing.json b/static/src/vendor/whynot-design/tokens/spacing.json new file mode 100644 index 0000000..0e6e0e0 --- /dev/null +++ b/static/src/vendor/whynot-design/tokens/spacing.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://design-tokens.github.io/community-group/format/", + "spacing": { + "1": { "value": "4px", "type": "dimension" }, + "2": { "value": "8px", "type": "dimension" }, + "3": { "value": "12px", "type": "dimension" }, + "4": { "value": "16px", "type": "dimension" }, + "5": { "value": "24px", "type": "dimension" }, + "6": { "value": "32px", "type": "dimension" }, + "7": { "value": "48px", "type": "dimension" }, + "8": { "value": "64px", "type": "dimension" }, + "9": { "value": "96px", "type": "dimension" }, + "10": { "value": "128px", "type": "dimension" } + }, + "radius": { + "0": { "value": "0px", "type": "dimension" }, + "1": { "value": "2px", "type": "dimension" }, + "2": { "value": "4px", "type": "dimension" }, + "3": { "value": "8px", "type": "dimension" }, + "pill": { "value": "999px", "type": "dimension" } + }, + "shadow": { + "0": { "value": "none", "type": "shadow" }, + "1": { "value": "0 1px 0 #E5E5E2", "type": "shadow" }, + "2": { "value": "0 1px 0 #C9C9C5", "type": "shadow" }, + "3": { "value": "0 4px 12px -6px rgba(10,10,10,0.10)", "type": "shadow", "comment": "Floating elements only." } + } +} diff --git a/static/src/vendor/whynot-design/tokens/type.json b/static/src/vendor/whynot-design/tokens/type.json new file mode 100644 index 0000000..2022120 --- /dev/null +++ b/static/src/vendor/whynot-design/tokens/type.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://design-tokens.github.io/community-group/format/", + "family": { + "sans": { "value": "\"IBM Plex Sans\", ui-sans-serif, system-ui, sans-serif", "type": "fontFamily" }, + "mono": { "value": "\"IBM Plex Mono\", ui-monospace, \"SF Mono\", Menlo, monospace", "type": "fontFamily" }, + "serif": { "value": "\"IBM Plex Serif\", \"Iowan Old Style\", Georgia, serif", "type": "fontFamily" } + }, + "size": { + "xs": { "value": "11px", "type": "dimension" }, + "sm": { "value": "13px", "type": "dimension" }, + "base": { "value": "15px", "type": "dimension" }, + "md": { "value": "17px", "type": "dimension" }, + "lg": { "value": "20px", "type": "dimension" }, + "xl": { "value": "24px", "type": "dimension" }, + "2xl": { "value": "32px", "type": "dimension" }, + "3xl": { "value": "44px", "type": "dimension" }, + "4xl": { "value": "64px", "type": "dimension" }, + "5xl": { "value": "96px", "type": "dimension" } + }, + "lineHeight": { + "tight": { "value": 1.05, "type": "number" }, + "snug": { "value": 1.25, "type": "number" }, + "base": { "value": 1.5, "type": "number" }, + "loose": { "value": 1.7, "type": "number" } + }, + "tracking": { + "tight": { "value": "-0.02em", "type": "dimension" }, + "snug": { "value": "-0.01em", "type": "dimension" }, + "base": { "value": "0em", "type": "dimension" }, + "mono": { "value": "0.02em", "type": "dimension" }, + "label": { "value": "0.08em", "type": "dimension", "comment": "Uppercase eyebrow labels." } + } +} diff --git a/vergabe_teilnahme/templates/base.html b/vergabe_teilnahme/templates/base.html index 89f6b71..0242d06 100644 --- a/vergabe_teilnahme/templates/base.html +++ b/vergabe_teilnahme/templates/base.html @@ -8,7 +8,7 @@ - + {% include "partials/topbar.html" %}
diff --git a/wiki/DesignSystem.md b/wiki/DesignSystem.md new file mode 100644 index 0000000..5ae141d --- /dev/null +++ b/wiki/DesignSystem.md @@ -0,0 +1,50 @@ +# Design System + +vergabe-teilnahme nutzt das `whynot-design`-System (gitea +`whynot/whynot-design`) als visuelle Basis. + +## Phase 1 — Tokens + CSS (aktiv, ab WP-0017) + +- Vendored CSS unter `static/src/vendor/whynot-design/`. +- Sync via `make sync-whynot-design` (Skript: `scripts/sync-whynot-design.sh`). +- Gepinnter Commit steht in `static/src/vendor/whynot-design/.whynot-design-ref`. +- `static/src/main.css` importiert die Vendor-CSS und mappt die whynot-Tokens + in den Tailwind-`@theme`-Block: `bg-ink`, `bg-paper`, `text-ink-3`, + `border-line` usw. sind als Utility-Klassen verfügbar. +- Legacy `bg-brand-*` / `text-brand-*` Utilities sind weiterhin nutzbar; sie + sind als Aliasse auf die ink/paper-Skala gemappt, damit Page-Templates + nicht in einer großen Migration mitgezogen werden müssen. + +## Phase 2 — Komponenten (offen) + +Adoption der whynot-Komponenten erfolgt sobald upstream Lit Web Components +und die fehlenden Atome (`Card`, `Modal`, `Input`, `Table`, `Toast`) +ausliefert. Eigener Workplan wird zu diesem Zeitpunkt angelegt. + +## Lokale Abweichungen vom whynot-System + +Dokumentiert direkt in `static/src/main.css`: + +- **`.btn-danger`** nutzt ein Off-Spec-Rot (`#B22222`, `--danger`-Variable). + whynot definiert aktuell keine destruktive Farbe; vergabe-Nutzung erfordert + sie für Löschen-Aktionen. Wird zurückgebaut, sobald upstream eine + kanonische Lösung definiert. + +## Visuelle Hausregeln aus whynot übernommen + +- Mostly Black & White; gelber Akzent (`--hi: #FFE14A`) nur als Highlighter / + Stamp / S4-Signal — nie als Button-Fill oder Hero-Hintergrund. +- 1px-Hairlines (`var(--line)` / `border-line`), großzügiger Weißraum, + Monospace-Eyebrow-Labels. +- Keine Schatten auf Cards; nur Popovers bekommen einen weichen 4–12px-Shadow. +- 0–4px Border-Radius für Cards/Sheets; 8px nur für große Modale; Pill nur + für Tag-Capsules. +- IBM Plex Sans / Mono / Serif via Google-Fonts (`@import url(...)` in der + Vendor-CSS). Build-Container und Browser brauchen Internet-Zugriff zu + Google Fonts. Air-gapped Deployment würde self-hosting erfordern. + +## Hintergrund + +- Strategie-Analyse + Komponenten-Lücken-Inventar: + `history/2026-05-23-whynot-design-cross-framework-analysis.md`. +- Adoption-Workplan: `workplans/WP-0017-whynot-design-tokens.md`. diff --git a/workplans/WP-0017-whynot-design-tokens.md b/workplans/WP-0017-whynot-design-tokens.md index 16dbb49..94bfa95 100644 --- a/workplans/WP-0017-whynot-design-tokens.md +++ b/workplans/WP-0017-whynot-design-tokens.md @@ -1,7 +1,7 @@ --- id: WP-0017 title: whynot-design Adoption — Phase 1 (Tokens + CSS) -status: ready +status: finished phase: 17-of-n created: "2026-05-23" depends_on: WP-0016 @@ -41,7 +41,7 @@ Inventar und Lücken-Liste in ```task id: WP-0017-T01 title: Vendor-Sync-Skript + initiale Vendor-Übernahme -status: todo +status: done Ziel: deterministisches Pull der whynot-design CSS-/Token-Quellen aus einem gepinnten Commit nach `static/src/vendor/whynot-design/`, ohne Docker-Build @@ -114,7 +114,7 @@ Diffs gegen den Vendor sind Teil des Review-Surfaces beim nächsten Bump. ```task id: WP-0017-T02 title: CSS-Integration in static/src/main.css -status: todo +status: done Ziel: whynot-Tokens werden global verfügbar, Tailwind-`@theme`-Mapping exponiert sie als Utility-Klassen, vergabe-spezifisches Brand-Blau entfällt. @@ -228,7 +228,7 @@ später durch self-hosting ersetzt werden — ist heute nicht relevant ```task id: WP-0017-T03 title: Base-Template — Body-Hintergrund auf whynot-Palette -status: todo +status: done **`vergabe_teilnahme/templates/base.html`** — Body-Klasse anpassen: @@ -249,7 +249,7 @@ weiterhin parallel zur whynot-Palette. ```task id: WP-0017-T04 title: Build + Static-Asset-Prüfung -status: todo +status: done Lokaler Build: @@ -279,7 +279,7 @@ adressieren (visueller Bruch wird dort sichtbar). ```task id: WP-0017-T05 title: Big-Bang Smoke-Test — visueller Durchlauf aller Hauptseiten -status: todo +status: done Dev-Server starten und durch die wichtigsten Views klicken. Bei jedem visuellen Bruch (Kontrast, weiße Schrift auf weißem Grund, harte Farb-Fremdkörper) eine @@ -325,7 +325,7 @@ Sichtprüfungs-Beleg. ```task id: WP-0017-T06 title: Doku-Update und Phase-2-Pflock -status: todo +status: done **`wiki/`** — neue Datei `wiki/DesignSystem.md` mit knappem Inhalt: