eklavya-design · git:20261016.96f4c9e · 2026-10-16 · sha256 53998d620bb6af62
eklavya-design git:20261016.96f4c9eA
Immutable. This exact content never changes and is served at /api/v1/blob/53998d620bb6af62.
---
name: eklavya-design
description: Visual design system for Eklavya (the "learn while your agent works" Claude Code plugin) — landing page, manual, dashboard, explainer artifacts and any Eklavya-branded UI. Use whenever building, restyling or reviewing an Eklavya surface so colour, type, shape, motion, iconography and voice stay consistent.
---
# Eklavya design system
Eklavya looks calm, dark and engineering-grade: a warm ink ground, one verdigris
accent, square hairline chrome, and enormous tight display type set against tiny
wide-tracked mono. The Ekalavya legend (the self-taught archer) appears only as
bow, arrow and target line art, with the Archer Cloak mascot as a learning companion.
**Values live in `web/public/tokens.css`.** This file states the rules; the CSS
states the numbers. When they disagree the CSS is right and this file is stale —
fix it here. Component recipes are in `references/components.md`.
## Rules at a glance
| # | Rule | A violation looks like |
|---|---|---|
| 1 | Components name a **role** token, never a scale step or raw colour | `color: #A29C93`, `background: var(--vd-300)`, `rgba(25,150,136,.12)` |
| 2 | The chrome is **square** | `border-radius: 8px` on a button, card, chip, tile, input or table |
| 3 | **Hairlines, not shadows** | `box-shadow` on a card that does not float |
| 4 | Three families, through their variables | `font-family: system-ui` or a fourth face |
| 5 | **One accent hue**; state colours only for real state | a blue link, a red "new" badge |
| 6 | No emoji; sentence case; Lucide line icons | `🎯 Your Progress` |
| 7 | No grayscale font smoothing | `-webkit-font-smoothing: antialiased` |
| 8 | Motion on `--ease`, 120–320ms, and reduced motion honoured | a bounce, a loop that ignores `prefers-reduced-motion` |
| 9 | Visible focus, keyboard-operable controls | `outline: none` with no ring, a clickable `<div>` |
| 10 | Text ≥ 4.5:1 on its ground in **both** grounds | `--faint-2` for readable copy |
| 11 | Ink is the default and never follows the OS | a `prefers-color-scheme` switch |
## Grounds and colour
Two grounds, one accent. `data-mode` on `<html>` is `ink` (default) or `paper`,
set by a blocking inline script before styles load, and each ground redefines the
same role tokens. Custom properties inherit, so one attribute re-resolves the tree.
| Role | Ink | Paper | Use |
|---|---|---|---|
| `--bg` | #17171a | #f0ede6 | Page ground. Sections split by a `--line-2` hairline, never by a background change |
| `--panel` | #1e1e22 | #e7e3da | Cards, dials, tables, sidebar |
| `--mass` | #26262a | #ddd8cc | Wells, inline code, spines, filled-button hover |
| `--ink` | #EAE7E1 | #16150f | Primary text |
| `--dim` | #A29C93 | #57534a | Body paragraphs, secondary text |
| `--faint` | #8B857B | #635E52 | Captions, micro-labels (≥4.89:1 on every ground it sits on) |
| `--faint-2` | #565249 | #a09a8c | Disabled and placeholder **only** — fails 4.5:1 by design |
| `--line` / `--line-2` | #26262a / #33333a | #e3dfd4 / #cec8b9 | Hairline inside a block / frame edges, control borders |
| `--spot` | `--vd-300` | `--vd-700` | The accent |
| `--spot-ink` | #17171a | #ffffff | Text on a `--spot` fill |
| `--spot-soft` | spot 14% | spot 10% | Icon tiles, chips, the default dial option |
| `--warning` | #E8920C | #7A4B00 | Real warnings and the "missed" series |
| `--warning-soft` | warning 14% | warning 10% | A warning's icon tile (a gap on the Feedback page), as `--spot-soft` is the accent's |
| `--error` | #FF6166 | #A8201A | Real errors and wrong answers only; always pair with a word or mark |
| `--code-bg`, `--grid`, `--glow`, `--glow-low`, `--shadow-lg`, `--ring` | | | Code wells, the 72px rule grid, terminal halos, floating shadow, focus ring |
- Neither ground is neutral grey — warm ink, warm paper. Ink is the product: Eklavya
is a terminal plugin, so an absent choice stays ink and the OS setting is ignored.
- **Verdigris** (aged bronze on a bow fitting) is the only hue. Ramp `--vd-50` …
`--vd-900`; `--spot` picks 300 on ink (10.33:1) and 700 on paper (7.20:1). Spend
it about six times a page: one headline word in `<em>`, the `$`, link text, the
active nav item, the primary button. A `--vd-*` step in a component is a bug.
- A new colour need is a new role in **both** ground blocks of `tokens.css`, never
a literal in the component.
- **Exception — the terminal.** It is a picture of a terminal, and terminals are
dark, so it keeps one fixed palette on both grounds (`--term-*` in tokens.css,
plus the terminal-only neutrals declared at the top of `styles.css`). The macOS
window dots (#FF5F57, #FEBC2E, #28C840) depict another app's chrome. Nothing
outside the terminal may use either set.
- **Exception — the mark.** `web/public/brand/mark.svg` is a white bow and arrow on a
`#0E6E66` (`--vd-600`) square, fixed. Never recolour, round or frame it.
- **Exception — the home hero.** Its mascot and introduction keep a dark
ground in both themes, using fixed `--hero-*` roles. The rest of the page
follows the reader's selected ground.
## Mascot
The approved Archer Cloak character has a coral face and verdigris clothes. Its identity colors are semantic `--mascot-*` roles in `tokens.css`, fixed across both grounds. This artwork exception does not change the bow mark or surrounding square chrome. Use the shared `<eklavya-mascot>` component and state mapping from `web/public/brand/mascot/mascot.js`; keep status text visible and artwork decorative unless it needs its own label. Use excited loading, encouraging tips after learner misses, and sad faces for system errors. The 2.8-second loading loop respects reduced motion; the studio also offers a pause control. See `docs/design/mascot-system.md` for reuse.
## Type
- **Archivo** (`--font-disp`, 600/800/900) for display; **Inter** (`--font-body`,
400/500/600) for text; **JetBrains Mono** (`--font-mono`, 400/500/600) for code,
slash commands, concept slugs, tier labels, terminal content and micro-labels.
Always through the variables. Surfaces that must make no outbound request (the
dashboard) strip the Google Fonts import and fall back to the stacks in the vars.
- Two extremes, no middle. Display: weight 900, `--tracking-display` (-0.055em),
`--leading-display` (0.9), sized fluidly (`--display-hero`, `--display-1`,
`--display-2`). Micro-labels: mono 10–11.5px, uppercase, 0.16–0.24em
(`--tracking-caps`, `--tracking-caps-wide`), in `--faint` or `--spot`. They replace
pill eyebrows. Body: 14.5px / 1.65, quiet.
- Sentence case in prose and headings; micro-labels are uppercased by CSS. No emoji.
- Never set `-webkit-font-smoothing: antialiased`: it thins light-on-dark glyphs.
## Shape, space and layout
- 4px grid: `--space-1` (4) … `--space-24` (96). Content 1140px wide with 32px side
padding inside a 1440px ruled frame (`--max`). Sections pad 96px vertically (72px
under 900px). Check layouts at 1280, 900 and 560px in both grounds.
- **Square chrome.** Buttons, cards, chips, dials, tables, tiles, badges, inputs and
code blocks are `border-radius: 0`. Circles (`50%`) only for dots, step numbers and
ripples; `--radius-pill` only for scrollbar thumbs. `--radius-term` (16px) belongs
to the demo terminal and the arrow scene alone. `--radius-sm/md/lg` are for small
data marks (legend swatches, chart bars), never chrome.
- **Hairlines, not shadows.** Separation is 1px `--line` or `--line-2`. `--shadow-lg`
is for things that float (menus, popovers, dialogs).
- Backgrounds are flat, plus the 72px `--grid` rule grid on `body` and at most one
soft `--glow` radial per band.
## Motion
- `--ease` (`cubic-bezier(.2,.6,.2,1)`) on 120 / 200 / 320ms (`--duration-fast`,
`-base`, `-slow`); micro-interactions 140–200ms. No bounce, no spring.
- Scroll reveals: opacity 0 + translateY(18px) → visible over 0.7s, staggered
100–400ms, fired once by an IntersectionObserver at threshold 0.15.
- The terminal replay uses the timing in its script. Start only when its whole
box is visible; pause offscreen, in a hidden tab and when the window is
inactive. The replay must not steal focus. Under reduced motion, remove
animated typing, cursor blinking and panel transitions.
- The home legend's decorative bow, arrow and target scene animates once each time
it enters view, including after returning with Back. The arrow launches quickly and slows near its target; the bow
recoils and the target responds. Under reduced motion, show the static art.
- `prefers-reduced-motion: reduce` collapses every animation and transition so
content lands in its final state.
## Signature motifs
- **Nav brand:** the bow glyph (`web/src/assets/bow.svg`, 24px grid, stroke 1.8) in
`--spot-ink` on a 30px square `--spot` tile, then "Eklavya" in Archivo 900 19px.
- **Arrow flight:** line bow → dashed trajectory → concentric target. Full strength
in story sections; as ambient art at `--shot-op` with a slight blur.
- **Terminal window:** use the terminal palette and mono type for a browser
replay of Claude Code's transcript and Eklavya panel. Product strings come
from the shipped code; example task output must be clearly an example.
- **Home hero and embedded demo:** keep the Archer Cloak mascot on a dark ground
with a short introduction and a faint bow-to-target animation in its
background. Repeat the hero animation only while visible and the browser is
active; reduced motion shows still artwork. Keep it clear of copy and controls.
Only this mascot has green eyes using the hero's `--spot` role. Pupils follow
the mouse within the eye shapes; touch and reduced motion keep them centered.
Apply this decoration locally, without changing shared mascot artwork.
The automatic replay belongs on the home page at
`/#demo`. `/demo/` redirects there. Detailed feature explanations stay in docs.
Keep the GitHub star box visible and use a real count from GitHub.
- **The name:** preserve the compact `#legend` section, its "The name" eyebrow,
"The archer who taught himself" heading, three short story columns and the
bow, arrow and target art. The SVG is decorative and stays on the home page.
- **Replay sequence:** type the prompt, show Enter and the work, open the panel
only when the question arrives, then select and submit the example answer,
show its result, show Done and finish the task. Match the panel's labels and
states to the shipped mod. Visitors do not type, choose or submit anything;
the pictured controls must not take keyboard focus.
## States, focus and icons
- Focus is `--ring` (2px `--bg` gap, then 2px `--spot`) via `:focus-visible`. Never
remove it without a replacement.
- Hover moves an outlined control's colour or border to `--spot`, and steps a filled
one (`--panel` → `--mass`). A selected segment inverts: `--ink` fill, `--bg` text.
- Every control is a real `<button>` or `<a>`; state lives in `aria-pressed` /
`aria-current`, never colour alone.
- Icons: Lucide line icons only, inlined — stroke `currentColor`, width 2 (1.8 for
the bow), round caps and joins; 16px in buttons, 20–22px in a 44px `--spot-soft`
tile. No icon fonts, filled icons or emoji.
## Voice
Direct, concise, technically credible. Lead with the outcome; verbs first on buttons
("View on GitHub", "Star on GitHub"). Numbers over adjectives. Address the reader as
"you". Reference the legend factually ("he practiced before a statue of his guru —
here, the statue talks back"), never cutely. Keep the product's own words verbatim:
the tagline "Learn while your agent works — and carry project context forward.",
command names, config keys, concept slugs.
## Before you ship a surface
- `grep -nE '#[0-9a-fA-F]{3,8}\b|rgba?\(|--vd-' <file>` outside tokens.css returns
only the terminal block and documented exceptions.
- `grep -n 'border-radius' <file>` shows 0, 50%, or a documented exception.
- Toggle ink/paper; tab through every control; run once with reduced motion.