uxskill
Star on GitHub
Language
English 简体中文 繁體中文 日本語 한국어 हिन्दी Bahasa Indonesia Tiếng Việt ไทย العربية Español Français Deutsch Português Русский Türkçe Italiano

## The shift in one paragraph

v2 said: "your brief sounds like a fintech dashboard. Here's the Stripe spec. Use it." The output looked Stripe-like. Good for prototyping, dangerous for shipping. If you wanted "Stripe-like but for healthcare," you got Stripe-like and had to manually warm the palette.

v3 says: "your brief sounds like a fintech dashboard. Here are 8 brands whose category-seed axes are closest to yours in 7-dimensional space: Stripe, Linear, Datadog, Snowflake, ClickHouse, and 3 more. I'm going to distill their palettes, type stacks, motion vocab, and density signals into a fresh design language that fits THIS brief. No one specific brand wins. The output is unique." The same 160 brand files now play a different role.

## The 7-axis synthesizer

The brief maps deterministically to 7 continuous axes:

Axis | 0.0 end | 1.0 end
--- | --- | ---
warmth | cold / clinical | warm / inviting
contrast | flat / muted | dramatic / loud
density | airy / sparse | dense / packed
geometry | sharp / angular | soft / rounded
formality | playful | corporate
motion | still | kinetic
type_personality | geometric | humanist

Same brief → same axes, every call. No randomness. No clock. No LLM. Pure Python math.

Each axis then compiles to concrete tokens:

- `warmth` → RGB warmth shift on the synthesized palette
- `contrast` → modular type scale ratio (1.200 quiet / 1.250 balanced / 1.333 loud)
- `density` → spacing base (4/6/8/12px), line-height openness
- `geometry` → radius scale (2-18px), corner character
- `formality` → tracking, motion dampening, spacing override
- `motion` → animation timing (140-220ms), curve pick
- `type_personality` → display family bias, weight curve

## Three modes, auto-dispatched

The synthesizer chooses how to consult the brand catalogue based on what the brief says:

### strict_brand

Brief: `reference_brands=[stripe] strict=True`. The recommender emits 100% Stripe tokens verbatim. No synthesis. Fastest path. Used when you know exactly what you want and need pixel-fidelity to one brand.

### brand_anchor

Brief: `reference_brands=[stripe]` (no strict flag). Stripe gets ~70% of the vocabulary weight in the mix. The other 30% comes from 4 sibling brands whose category-seed axes match the rest of the brief. Used when you want Stripe's taste but applied to your domain.

### pure_synthesis

Brief: no `reference_brands` at all. Synthesizer pulls 8 brands closest to the brief's axes in 7-D space, distills their vocabulary, mixes weighted by axis position. No single brand dominates. Output is novel each time. Infinity space, grounded in real brand patterns.

## Why we kept the catalogue (and didn't burn it for "infinite space")

The maximalist v2.1 spec said: drop the manifests, generate every palette / type pair / spacing scale from scratch on every call, achieve true infinity.

We rejected this. Three reasons:

- Speed. The catalogue is fixed JSON. Reading 160 specs is microseconds. Synthesizing every token from scratch with no grounding would require an LLM call per generation: slow, expensive, non-deterministic.
- Linter compatibility. The 152 anti-pattern rules grade outputs by matching against curated bad / good examples drawn from the same brand catalogue. If we burn the catalogue, we burn the linter's reference.
- Determinism. The same brief should produce the same output. Infinity-space LLM generation can't guarantee that. The synthesizer can.

Instead, v3 achieves infinity by making the COMBINATIONS of finite vocabulary continuous. The catalogue stays. The output is still novel. Best of both.

## Try it

    pip install uxskill
    uxskill init                              # auto-detects your IDE
    uxskill synthesize --industry fintech-payments --tone bold
    # → SynthesizedSystem(mode="pure_synthesis", axes={warmth: 0.35, ...},
    #                    palette={canvas: "#0a1014", ...}, type_pair={...})

    uxskill synthesize --brand stripe          # brand_anchor (70/30)
    uxskill synthesize --brand stripe --strict # strict_brand (100% Stripe)

## Related reads

- How to make AI coding tools output on-brand UI
- The full v3.0 launch post · The Brain
- The 152 anti-pattern rules: the linter catalogue
- The 160-brand catalogue: what the synthesizer learns from
- Anti AI-slop in Claude Code: the linter explained