Selectools Design System¶
The concrete tokens, components, animation language, and rules that make up the selectools visual language.
This is the what. For the why — brand personality, design principles, voice and tone — see .impeccable.md at the project root.
Source of truth: landing/index.html (inline <style> block + JS observers). This document mirrors that source. When the source changes, this document must be updated to match.
1. Design tokens¶
All tokens live on :root in landing/index.html and are referenced via var(--name). Never hardcode hex values in component CSS.
1.1 Colors¶
Surface palette¶
| Token | Hex | Use |
|---|---|---|
--bg | #0f172a | Page background. The slate-950 anchor everything else sits on. |
--surface | #1e293b | Cards, code frames, default panel background. |
--surface-2 | #273548 | Slightly elevated surface (rarely used). |
--border | #334155 | Default card/panel border. 1px solid. |
Text palette¶
| Token | Hex | Use |
|---|---|---|
--text | #e2e8f0 | Primary body text. Headings on dark surface. |
--text-dim | #94a3b8 | Secondary text. Section descriptions. Card body copy. |
--text-faint | #64748b | Tertiary text. Labels, captions, comment color in code. |
Accent palette¶
| Token | Hex | Use |
|---|---|---|
--cyan | #22d3ee | Primary accent. The signature brand color. Use for: links, active states, focus rings, the execution pointer, code keywords (from, import), interactive feedback. |
--blue | #3b82f6 | CTAs (secondary buttons). LLM-judge evaluator tags. |
--green | #22c55e | Success states, "passed" badges, costs (when low is good), version-stack lit rungs, stability "@stable". |
--amber | #f59e0b | Warning, "running" states, latency bars, stability "@beta". |
--red | #ef4444 | Errors, mac terminal close dot, broken-state vignettes, stability "@deprecated". |
--purple | #a855f7 | Code keywords (from, class, def in code blocks). |
Rule: One accent per component. Never mix two accents (cyan + green) on the same element. The exception is the eval report, which uses cyan / amber / green to differentiate metric types — that's intentional and earned.
Glow tokens (used by the execution-pointer system)¶
| Token | Value |
|---|---|
--exec-color | #22d3ee (alias of --cyan, named for intent) |
--exec-glow | rgba(34, 211, 238, 0.55) |
--exec-glow-soft | rgba(34, 211, 238, 0.18) |
1.2 Typography¶
| Token | Stack |
|---|---|
--font-ui | 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif |
--font-mono | 'JetBrains Mono', ui-monospace, 'Cascadia Code', monospace |
Loaded weights: Plus Jakarta Sans 400 / 500 / 600 / 700 / 800; JetBrains Mono 400 / 500.
Type scale (de facto, extracted from existing components)¶
| Use | Size | Weight | Family |
|---|---|---|---|
| Hero h1 | 64-80px | 800 | UI |
| Section h2 | 32-44px (clamp) | 700-800 | UI |
| Card h3 | 18-26px | 700 | UI |
| Body | 15px | 400 | UI |
| Card body | 13.5px | 400 | UI |
| Caption | 12.5px | 400 | UI |
| Section label | 11px (uppercase, letter-spacing 0.12em) | 500 | Mono |
| Code body | 13px | 400 | Mono |
| Mono labels | 9-11px (uppercase) | 400-500 | Mono |
Headings: Tight tracking (letter-spacing: -0.02em to -0.03em). Body: Generous line-height (1.7). Mono labels: Wide tracking (0.12em to 0.18em), uppercase.
1.3 Spacing¶
There is no formal spacing scale — values are chosen per-component for visual rhythm. Common values that recur:
| px | Use |
|---|---|
| 4 | Tight inline gaps. |
| 8 | Compact card padding edges. |
| 10-14 | Default internal gaps. |
| 16 | Mobile section padding. |
| 18-22 | Default panel padding. |
| 24 | Code body padding. |
| 28 | Default desktop card padding. |
| 32-44 | Section heading bottom margin. |
| 64-100 | Section vertical padding. |
1.4 Radii¶
| px | Use |
|---|---|
| 6 | Inline code, small chips. |
| 8-9 | Buttons, version stack rungs. |
| 10-12 | Default card radius. |
| 14 | Big panels (terminals, bento cells, eval report). |
| 999 | Pills (status badges, capability chips, eval tags). |
1.5 Easing & duration¶
| Token | Value | Use |
|---|---|---|
--ease | cubic-bezier(0.16, 1, 0.3, 1) | Default. Almost-elastic ease-out. Use for hover, reveal, layout transitions. |
--exec-ease-soft | cubic-bezier(0.16, 1, 0.3, 1) | Alias of --ease for the execution-pointer system. |
--exec-ease-step | cubic-bezier(0.4, 0, 0.2, 1) | Snappier. Use for stamps, scan lines, node-to-node travel. |
--exec-pulse-dur | 1.6s | Default pulse loop. |
--exec-step-dur | 0.55s | Node-to-node travel. |
--exec-blink-dur | 1.05s | Terminal caret blink cadence. |
Rule: Never use cubic-bezier literals in component CSS. Reference the tokens.
1.6 Layout¶
| Token | Value |
|---|---|
--max-w | 1120px |
--grain | inline SVG turbulence |
1.7 Breakpoints¶
| Width | Devices | What changes |
|---|---|---|
≥1280px | Desktop | Default. 6-col bento, side-by-side stages, full nav. |
≤1100px | Small desktop | Bento drops to 4-col. |
≤1024px | Tablet | Nav links hide, hamburger shows, multi-col layouts collapse to 1-col. |
≤880px | Large mobile | Section stages collapse to single column. |
≤768px | Tablet portrait | Builder iframe fallback shows. |
≤700px | Mobile | Bento goes single-column with !important overrides. Drag-to-compare stacks vertically. |
≤640px | Small mobile | Tighter padding, smaller code body font. |
≤480px | Tiny mobile | Hero scale adjusts. |
Rule: Use content-driven breakpoints. The 700/880/1100 values exist because that's where specific sections break, not because they're standard widths.
1.8 Touch targets¶
@media (pointer: coarse): any touch device, regardless of viewport width. Used to defeat Chrome's "Desktop Site" mode which lies about the viewport. All interactive elements must be ≥44×44px on coarse pointer.
2. Component inventory¶
All components live in landing/index.html. Search for the class name to find the styles.
2.1 Layout shells¶
| Class | Purpose |
|---|---|
.wrap | Max-width container. Use as the first child of every <section>. |
.section | Vertical-padded section wrapper. |
.section-label | Mono uppercase label that sits above an h2 (e.g. "WHAT IS SELECTOOLS?"). |
.section-desc | Body paragraph below the h2. |
2.2 Code frame (mac-style terminal)¶
The reusable code window with traffic-light dots and a filename label.
<div class="code-frame code-frame-accent">
<div class="code-bar">
<div class="code-dot" style="background:rgba(239,68,68,0.8)"></div>
<div class="code-dot" style="background:rgba(250,204,21,0.8)"></div>
<div class="code-dot" style="background:rgba(34,197,94,0.8)"></div>
<span class="code-bar-label">filename.py</span>
</div>
<div class="code-body"><span class="kw">from</span> selectools <span class="kw">import</span> Agent</div>
</div>
| Class | Purpose |
|---|---|
.code-frame | The outer frame with border, radius, surface bg. |
.code-frame-accent | Cyan-bordered variant. Use for the "good" code in comparisons. |
.code-bar | Top bar with dots + filename. |
.code-dot | Mac traffic-light dot (style with inline background). |
.code-bar-label | The filename label. Mono, 11px, faint color. |
.code-body | The code container. Has white-space: pre and mask-image right-edge fade. |
.code-dim | Modifier for "the bad code" (opacity 0.45). |
Syntax token classes (apply to <span> inside .code-body): | Class | Color | For | |---|---|---| | .kw | #c084fc (purple) | from, import, def, class, return, @. | | .cls | #7dd3fc (cyan-300) | Class names, type annotations. | | .fn | #fbbf24 (amber) | Function names, callable invocations. | | .str | #86efac (green) | String literals. | | .cmt | var(--text-faint) | Comments. | | .num / .bool | #fb923c (orange) | Numeric literals, booleans. |
2.3 Buttons¶
| Class | Purpose |
|---|---|
.btn | Base button. |
.btn-primary | Cyan-bg primary CTA. |
.btn-ghost | Outlined secondary. |
.btn-link | Inline cyan text link. |
All buttons: transform: scale(0.97) on :active, transition via --ease. Touch hover guards via @media (hover: none).
2.4 Cards & bento¶
| Class | Purpose |
|---|---|
.card | Default rounded panel with border + grain overlay. |
.bento | 6-col CSS Grid container. Auto-flow dense. |
.bento__cell | Default cell (grid-column: span 2). |
.bento__cell--hero | 3-col × 2-row hero cell. |
.bento__cell--wide | 3-col × 1-row wide cell. |
.bento__cell--full | Full-width footer cell. |
.bento__label, .bento__title, .bento__desc, .bento__metric | Cell content slots. |
Bento mobile rule: at ≤700px, all cells force grid-column: 1 / -1 !important to override any inline style="grid-row:N;grid-column:M / N" placements that pin cells in the 6-col grid.
2.5 The execution-pointer atoms (the unifying device)¶
These are the shared visual vocabulary that every section composes. The "execution pointer" is a conceptual entity (a cyan pulse representing live execution), not a single DOM node. Each section has its own animation that reuses these atoms.
| Class / keyframe | Purpose |
|---|---|
.exec-dot | Pulsing cyan dot. 8×8 default. Modifiers: --lg (10×10), --sm (6×6). |
.exec-caret | Blinking block cursor. Used in terminals and the wordmark. |
.exec-scan | One-shot cyan beam sweep across an element. Apply to a positioned parent; the ::after is the beam. |
@keyframes exec-pulse | Box-shadow ripple, 1.6s loop. |
@keyframes exec-blink | Caret on/off, 1.05s, steps(2, jump-none). |
@keyframes exec-scan-sweep | Beam translateX, 1.4s, one-shot. |
@keyframes exec-stamp | One-shot scale + cyan ring "stamp" effect. Use on viewport entry. |
Rule: When you build a new animated section, compose these atoms instead of inventing new ones. Cohesion comes from sharing color, easing, and timing — not from a runtime framework.
2.6 Section-specific components¶
These were designed once for the landing page sections. Reuse if a similar use case appears.
| Section | Class prefix | What it is |
|---|---|---|
#what | .what-stage, .what-term, .what-rail, .what-chip | Terminal panel + capability rail with chips that light up via --chip-delay CSS var. |
#see-difference | .diff-stage, .diff-pane, .diff-pane__col, .diff-anno | CSS Grid 2-column drag-to-compare slider driven by --split (unitless 0..100). |
#production | .prod-stage, .prod-trace, .prod-node, .prod-panel | Clickable trace explorer with auto-play through nodes. |
#enterprise | .ent-shelf, .ent-exhibit, .ent-ring, .ent-counter, .ent-versions, .ent-sbom, .ent-markers | 5-column compliance shelf. Each exhibit is its own micro-component. |
#eval | .eval-stage, .eval-report, .eval-metric, .eval-marquee, .eval-tag | Hardcoded mock test report + 2-row marquee. |
#faq | .repl, .repl__rail, .repl__cat, .repl__searchbar, .repl__q | Search-driven docs REPL. |
| Footer | .footer-term, .footer-term__tree | Terminal sitemap ($ tree selectools.dev/). |
| Wordmark | .wm, .wm--1, .wm--2, .wm--3 | 3 switchable variants gated on [data-logo="N"]. |
2.7 Bento legacy components (from PR #46)¶
| Class prefix | What it is |
|---|---|
.why-vignette | "Three problems" before/after diff blocks with sticky cyan numerals. |
.persona | Tilted persona postcards with display-font pull quotes. |
.path | Live preview cards for "Get started" section. |
.path-term, .path-marquee, .path-graph | Path-card content variants. |
3. Animation language¶
3.1 The reveal pattern¶
Every animated section uses the same pattern:
- CSS sets the initial state (opacity 0, transform Y, etc.) and defines the animated state under a
.in-viewmodifier class. - JS uses an
IntersectionObserver(one IIFE per section) to add.in-viewwhen the element enters the viewport. - The observer disconnects after the first trigger (one-shot).
(function() {
var stage = document.querySelector('.my-stage');
if (!stage) return;
var io = new IntersectionObserver(function(entries) {
entries.forEach(function(e) {
if (!e.isIntersecting) return;
stage.classList.add('in-view');
io.disconnect();
});
}, { threshold: 0.25, rootMargin: '0px 0px -8% 0px' });
io.observe(stage);
})();
Standard threshold: 0.25 to 0.35 depending on how committed the user should be. Standard rootMargin: -8% to -10% bottom inset so the animation fires after the section is comfortably in view, not the moment its top edge crosses.
No global helper. Each section's IIFE is self-contained. This makes the diff per-section reviewable and prevents one section's animation from breaking another.
3.2 Stagger recipes¶
Use CSS variables on individual elements to drive per-element delays:
For uniform staggers (e.g. compliance shelf exhibits), use nth-child rules with hard-coded delays. For non-uniform staggers (e.g. capability chips that fire when their corresponding code line is typed), use inline --delay vars.
3.3 Reduced motion¶
Every animated component must have a prefers-reduced-motion: reduce override that snaps to the final state. The fallback must be a complete, intentional static composition — not just "animation: none".
All overrides live in the single @media (prefers-reduced-motion: reduce) block at the bottom of the <style> section. Find the existing block and add to it; do not create a second.
4. Voice & tone rules¶
4.1 Banned punctuation¶
Em-dashes (—, —) in user-facing copy. When every section uses one, it becomes the load-bearing punctuation of the page and reads as AI-generated. Use a period, comma, colon, or rewrite. Source-code comments (CSS/HTML/JS) may keep their em-dashes — they're developer prose, not marketing copy.
4.2 Banned words in body copy¶
| Avoid | Use instead |
|---|---|
| leverage | use |
| seamless / seamlessly | (drop the word) |
| robust | specific quality (e.g. "tested across 5 Python versions") |
| comprehensive | complete, full, every |
| navigate (figurative) | move through, find, search |
| delve | look at, read, study |
| elevate | improve, make better |
| unlock (figurative) | enable, give you |
| harness (figurative) | use, take advantage of |
| transformative / game-changer / cutting-edge | (drop and rewrite around what it actually does) |
| boasts (a feature) | has, ships with, includes |
| in the realm of | for, in |
| It's worth noting that | (drop, just say the thing) |
| Moreover / Furthermore | (start a new sentence; the connection is implicit) |
4.3 Sentence structure rules¶
- Short sentences over long ones. A period is better than a comma in most marketing contexts.
- No triple-adjective stacks ("fast, secure, and scalable"). Pick one and prove it.
- Numbers over adjectives. "4612 tests" beats "well-tested". "$0.0012 per call" beats "affordable".
- Active voice over passive. "We tested across Python 3.9-3.13" beats "It has been tested across..."
- No redundant phrases. Don't say "in order to" when "to" works. Don't say "the fact that" when "that" works.
- Don't restate the heading in the description. The h2 says it. The desc adds something new.
4.4 Tone¶
- Senior to senior. The reader is a senior engineer evaluating a framework. Don't explain Python concepts. Don't use marketing-school formulas (Problem → Agitation → Solution).
- Earned confidence, not arrogance. Every claim is followed by a number, a code snippet, or a link to proof. "Production-ready" alone is empty. "Production-ready: 4612 tests, 95% coverage, CycloneDX SBOM, audited" is earned.
- Specific over generic. "PII redaction in the constructor" beats "enterprise security". "50 evaluators, free, local" beats "comprehensive testing".
5. Anti-patterns (what NOT to do)¶
These are real mistakes the project has made and corrected. Don't repeat them.
| Don't | Why |
|---|---|
| Use 6+ rectangular icon-cards in a grid | Templated. Reads as "AI slop". Use distinct visual genres per section instead. |
Pin grid cells with inline style="grid-row:N;grid-column:M / N" without a mobile reset | Inline styles beat media queries via specificity. Cells overflow on mobile. |
Rely on clip-path: inset(0 calc(100% - var(--x)) 0 0) for animated split layers | Browsers freeze the parsed clip-path value and don't re-eval calc() when the var changes. Use CSS Grid or set clip-path inline via JS. |
Use <nav class="my-rail"> for section sub-navigation | The global nav { position: fixed } selector will hoist your element to the top of the page. Use <div role="navigation">. |
Define .hide-mobile { display: none } before .nav-links { display: flex } in source | Equal specificity, source order wins, the hide loses. Use .nav-links.hide-mobile (two-class specificity). |
| Add new keyframes for a new section's animation | Compose the existing exec-* atoms. New keyframes mean new visual vocabulary, which breaks cohesion. |
| Use generic icon SVGs as section accents | Use either real terminal output, real code, or composed CSS shapes (block-drawing, rings, counters). |
Skip the prefers-reduced-motion fallback because "it's just a small animation" | Accessibility requirement. Every animation has a static fallback. |
| Hardcode hex colors in component CSS | Reference var(--cyan) etc. so palette changes propagate. |
Use cubic-bezier() literals in component CSS | Reference var(--ease) or var(--exec-ease-step). |
6. File structure¶
| Path | Purpose |
|---|---|
.impeccable.md | Design context — brand, voice, principles, references. The why. |
docs/design-system.md | Design system — tokens, components, animation, anti-patterns. The what. (this file) |
docs/superpowers/specs/ | Per-feature design specs. Each one references both files above. |
landing/index.html | The source of truth for tokens and components. Contains the inline <style> block (~3500 lines) and JS observers. |
landing/favicon.svg | Default favicon (Wordmark Variant 1). |
landing/favicon-cursor.svg | Alternate favicon (Wordmark Variants 2 & 3). |
7. When to update this file¶
Update docs/design-system.md whenever you:
- Add a new design token to
:root(color, spacing, easing, duration) - Create a new reusable component class (anything with
.foo__*BEM-style children) - Establish a new animation pattern (new IntersectionObserver flow, new keyframe family)
- Catch a new anti-pattern that someone might repeat
- Add a banned word or rewrite rule to the voice section
Keep this file in sync with landing/index.html. The file is the documentation; the HTML is the implementation. If they diverge, the implementation wins and this file gets corrected.