-
Notifications
You must be signed in to change notification settings - Fork 7
Synthesizer 7 axes
The v3.0 flagship. Maps any brief to a fresh, deterministic design language by computing 7 continuous axis values and mixing brand vocabulary along them.
| Axis | Range | Derived from | Compiles to |
|---|---|---|---|
warmth |
0=cold ↔ 1=warm | industry seed + tone tags | RGB warmth shift on palette |
contrast |
0=flat ↔ 1=dramatic | tone tags + attention need | Modular scale ratio (1.200/1.250/1.333), weight curve |
density |
0=airy ↔ 1=dense | data richness, must-have | Spacing base (4/6/8/12px), line-height |
geometry |
0=sharp ↔ 1=soft | emotional register | Radius scale (2-18px), corner style |
formality |
0=playful ↔ 1=corporate | audience | Tracking, motion dampening, spacing override |
motion |
0=still ↔ 1=kinetic | interactivity | Animation timing (140-220ms), curve pick |
type_personality |
0=geometric ↔ 1=humanist | voice | Display family bias, weight curve |
All seven are continuous floats in [0.0, 1.0]. Same brief → same values, every time. Pure Python math, no randomness, no LLM.
from engine.synthesizer import compute_axes
axes = compute_axes(brief)
# AxisValues(warmth=0.35, contrast=0.55, density=0.6, geometry=0.4,
# formality=0.75, motion=0.45, type_personality=0.4)Three-step derivation:
- Industry seed — 17 documented industries each have a 7-axis seed (fintech-payments, gaming, luxury, healthcare, etc.). Unknown industry → neutral 0.5 across the board.
- Tone nudges — 35+ documented tone tags push axes by ±0.10 to ±0.30 (warm → +0.20 warmth; bold → +0.25 contrast; minimal → −0.30 density).
-
Forbidden clamps —
forbidden=["playful"]clamps formality to [0.6, 1.0].forbidden=["dense"]clamps density to [0.0, 0.55].
All values clamped to [0.0, 1.0] after each step.
# Pick 8 axis-matching brand exemplars
# Distill palette anchors (canvas, ink, primary, accent)
# Weighted RGB mix + warmth-shiftThe warmth axis pushes the mixed RGB warmer (more R+G) or cooler (more B). Distance from 0.5 sets intensity. Max shift ±18 units.
Uses interactions.spacing_base_for(axes) for the conflict resolution:
| density | formality | base px | Why |
|---|---|---|---|
| dense (>0.65) | any | 4 | Bloomberg-school: density wins |
| airy (<0.4) | formal (>0.7) | 12 | Luxury: formality wins, breathing room |
| airy (<0.4) | casual | 8 | Standard airy |
| balanced | any | 6 | Mid-density default |
Scale array follows Fibonacci-ish growth from the base.
Uses interactions.radius_base_px_for(axes, vocab_mean):
-
geometry < 0.3 + formality > 0.7→ 2px (sharp + corporate, NYT/Bloomberg) -
geometry > 0.7 + formality < 0.4→ 18px (soft + playful, Glossier) - Otherwise weighted blend: 50% axis target + 30% vocab mean + 20% formality dampener
-
base_ms = int(220 - 80 * motion)— kinetic = snappier - High motion + corporate → +25% dampener (corporate slows kinetic axes)
- Curve picked by motion axis:
>0.7spring-soft,>0.4ease-cinema, else standard
Modular ratio from contrast: 1.200 / 1.250 / 1.333. 9-step ladder anchored at body=16px.
Weight curve from type_personality + contrast:
- Geometric + loud → display 800
- Geometric + quiet → display 700
- Humanist + loud → display 700
- Humanist + quiet → display 600
- Body always 400; caption 500 (legibility)
Tracking: display tighter (−0.015 to −0.025em), caption wider (+0.04 to +0.06em). Formality tilts.
Line-heights: hero 1.02, display 1.05, h1 1.08, body 1.50-1.65 (density inverse), caption 1.45.
Auto-dispatched based on what's in the brief:
# Mode 1: strict_brand — 100% brand verbatim
brief.reference_brands = ["stripe"]
brief.strict = True
# → SynthesizedSystem(mode="strict_brand", anchor_brand_id="stripe", ...)
# Mode 2: brand_anchor — 70/30
brief.reference_brands = ["stripe"]
# → SynthesizedSystem(mode="brand_anchor", anchor_brand_id="stripe", ...)
# Mode 3: pure_synthesis — infinity space
# (no reference_brands)
# → SynthesizedSystem(mode="pure_synthesis", ...)In brand_anchor mode, the named brand is double-counted in the vocabulary distillation pool (so its weight is roughly 2× any other brand's, ~70% of the mixed result). The other 4 brands come from pick_exemplars_by_axes matching the brief's axes.
In pure_synthesis mode, 8 axis-matching brands are pulled and weighted equally.
- Same brief → same axes (no randomness, no clock)
- Same axes → same exemplar set (deterministic sort by distance, tie-break by brand id alphabetical)
- Same vocabulary → same output tokens (pure math, no LLM)
- Same output → same evaluation (deterministic scorers)
Reproducible across machines, filesystems, Python versions.
from engine import synthesize, compute_axes
from engine.synthesizer.interactions import (
spacing_base_for, radius_base_px_for,
motion_timing_for, accent_register_for,
)- v3-The-Brain — the architectural shift
- Architecture — module layout
- Brand-Library-110 — the 160-brand vocabulary pool