Verified against Semitexa Ultimate 2026.09.19.1020
Skin Generation
Skins are generated from one seed color via deterministic OKLCH math. The same seed + algorithm + knobs + mode always produces the same token palette, independent of time or LLM state.
Three algorithms
Each algorithm has a fixed character (colors, shadows, radii, motion) and a handful of knobs that fine-tune it without leaving its territory.
| Algorithm | Character | Knobs |
|---|---|---|
balanced |
Corporate-readable. Soft drop shadows, conservative radii, smooth transitions. | radius_scale, shadow_intensity, motion_speed |
glass |
Translucent frosted panels, larger radii, diffuse shadows, emphasized motion. | blur_amount, surface_transparency, shadow_softness |
brutalist |
Bold & structural. Zero radius, hard offset shadows, instant motion, high-contrast colors. | shadow_offset, contrast_boost, shadow_color_mode |
Full knob enums + defaults: run bin/semitexa skins:generate --describe, or see skin-algorithms.md.
Seed mode — deterministic
bin/semitexa skins:generate balanced "#2f6fed" --name=enterprise --write
bin/semitexa skins:generate glass "#6b7bff" --name=frosted --knob=blur_amount:heavy --write
bin/semitexa skins:generate brutalist "#d93025" --name=manifesto --knob=shadow_color_mode:brand --mode=dark --write
Omit --write for a dry-run CSS preview to stdout.
Produces under the project's src/skins/<name>/ directory:
tokens.css—:root { --ui-*: <value>; }skin.json— v2 manifest (algorithm, seed, mode, resolved knobs, history, tokens)
SkinDiscovery in semitexa/theme scans two sources:
vendor/semitexa/skins-base/src/Application/Static/skins/— framework default (ships only the singledefaultreference skin)src/skins/— project-local (project slugs override same-named framework slugs)
Both served under the unified URL prefix /assets/skins/<slug>/tokens.css (registered at worker boot by Semitexa\Theme\Runtime\BootProjectSkinsAssetAliasListener). Theme authors reference a skin by slug — they don't care where it physically lives.
Prompt mode — LLM-assisted
Requires semitexa/llm with a reachable Ollama provider.
bin/semitexa skins:generate --prompt="punk zine manifesto, anti-corporate" --name=zine --write
The LLM skill (platform-ui.skin.resolve-prompt) picks algorithm + seed + knobs + mood from prose, runs through the output validator, then hands off to the same deterministic OKLCH pipeline as seed mode.
Override the LLM's algorithm choice by passing --algorithm=<id> explicitly; override its knob suggestions via --knob=name:value (repeatable). CLI always wins over LLM.
See llm-prompt.md for the system prompt and output contract.
Light & dark mode
Every algorithm emits both. Default is light; pass --mode=dark for a dark palette built from the same brand hue.
bin/semitexa skins:generate balanced "#3c7fbf" --name=ocean-dark --mode=dark --write
Dark mode inverts surface/text lightness in OKLCH, shifts state colors brighter for legibility, triples shadow alpha (so drop shadows remain perceptible against near-black surfaces), and flips the brutalist neutral shadow from matte-black to near-white. skin:refine preserves the mode of the source skin.
Token contract (41 tokens)
Color (24) — unchanged since v1
| Role | Token |
|---|---|
| Text | --ui-text-primary, --ui-text-muted, --ui-text-on-accent |
| Surface | --ui-surface-page, --ui-surface-panel, --ui-surface-raised, --ui-surface-sunken |
| Border | --ui-border-subtle, --ui-border-strong |
| Accent | --ui-accent-brand, --ui-accent-brand-contrast |
| State | --ui-state-success, --ui-state-warning, --ui-state-danger, --ui-state-info |
| Interactive | --ui-focus-ring |
| Chart | --ui-chart-1 … --ui-chart-8 |
Non-color (17) — new in v2
| Role | Tokens |
|---|---|
| Radius | --ui-radius-none, -sm, -md, -lg, -pill |
| Shadow | --ui-shadow-xs, -sm, -md, -lg, --ui-shadow-color |
| Motion duration | --ui-motion-duration-fast, -normal, -slow |
| Motion easing | --ui-motion-easing-standard, -emphasized |
| Glass effects | --ui-surface-blur, --ui-surface-saturation |
Primitives and grammar slices consume these via var(--ui-X, <fallback>). Old 24-token skins keep rendering against fallbacks.
Strict definition: src/Skin/TokenContract.php (PHP enum).
Manifest v2 (skin.json)
{
"name": "enterprise",
"schema_version": "2.0",
"source": "seed",
"algorithm": "balanced",
"mode": "light",
"seed": "#2f6fed",
"knobs": { "radius_scale": "default", "shadow_intensity": "default", "motion_speed": "default" },
"generated_at": "2026-04-24T09:00:00+00:00",
"updated_at": "2026-04-24T09:00:00+00:00",
"history": [
{ "at": "…", "kind": "generate", "algorithm": "balanced", "mode": "light", "seed": "#2f6fed", "knobs": {…}, "source": "seed" }
],
"tokens": { "--ui-surface-page": "#fcfcfd", … }
}
Manifest is the source of truth; tokens.css is derived. Any regeneration (seed, prompt, or refine) appends to history[] so the iteration chain is auditable. Refine with LLM also records the prompt, model, attempts, latency, and rationale on each entry.
v1 skins (no knobs/history/mode fields, schema_version absent or "1.0") are auto-migrated on first skin:refine — the missing fields get defaults and a synthetic "kind": "migrated" entry is prepended to history[].
OKLCH math (balanced — reference algorithm)
- Seed → OKLCH. Clamp to envelope: L ∈ [0.35, 0.70], C ≤ 0.25.
- Surface ramp: tints of seed hue. Light-mode L 0.94–1.00, dark-mode L 0.10–0.22.
- Border ramp: visible against surface. Light L 0.78–0.90, dark L 0.28–0.42.
- Text: dark seed hue for primary (L 0.22 light / 0.95 dark), mid for muted (L 0.50 / 0.65).
- Accent: the seed itself, walked in L until WCAG-AA contrast met against
SurfacePage. Walk direction depends on mode — dark backgrounds need brighter accents, light need darker. - States: canonical hues (success 145°, warning 70°, danger 25°, info 240°) with matched chroma; dark mode shifts lightness up ~0.08 so they remain legible on dark surfaces.
- Charts: 8 equally-spaced hues (45° apart) starting from seed hue.
Glass + brutalist layer their character on top of the same frame — larger radii + blur for glass; zero radius + hard offset shadows + saturated accents for brutalist.
Extension point — adding an algorithm
Implement Semitexa\PlatformUi\Contract\SkinAlgorithm (four methods: id(), description(), knobSchema(), generate()) and register it in SkinAlgorithmRegistry. The LLM prompt block is built from the registry at runtime, so the new algorithm appears in skin:generate --prompt "…" without any prompt-file edit.
Recommended: add 1–2 few-shot examples to resources/llm/skin-resolve-fewshot.json — concrete examples still meaningfully shape LLM performance even though the prompt itself is dynamic.
See skin-algorithms.md for the knob-schema convention and walk-through.
Introspection
bin/semitexa skins:generate --describe— all algorithms with knob enums + defaultsbin/semitexa skins:generate <algo> "#hex"(no--write) — dry-run CSS previewbin/semitexa skins:explain-prompt "<text>"— show LLM resolution, no CSS emissionSemitexa\PlatformUi\Skin\Oklch\ContrastScore::contrast($hex1, $hex2)— WCAG relative-luminance ratio- See skin-refinement.md for iterating on an existing skin