---
version: 1.2
name: Lens Design System
last-updated: 2026-05-23
description: |
  Lens is a personal AI accessibility layer for the web — a Chrome / Edge
  extension that rewrites every article to match how the user reads. The
  design system anchors on a warm editorial cream canvas, a two-tone sage
  button system with a deep-forest accent, and a humanist type pair —
  Atkinson Hyperlegible for reading prose, Inter for UI controls.

  The palette belongs to the "Forest Canopy" family (cream + sage + forest
  green) — the same family Anthropic's Claude product runs on. Lens
  deliberately diverges from Claude on one point: the CTA is sage, not
  coral. Sage is calmer and tests better with low-vision and dyslexia
  readers, which is who Lens is built for.

source-of-truth:
  tokens: /Users/ilans/lens/src/tokens.css
  components: /Users/ilans/lens/src/reader.css
  marketing-site: /Users/ilans/lens/site/index.html
  live-reference: https://site-steel-alpha.vercel.app/design/

palette-family: Forest Canopy

colors:
  # surfaces — warm cream hierarchy, no pure white
  canvas:                "#faf9f5"
  surface-soft:          "#f5f0e8"
  surface-card:          "#efe9de"
  surface-cream-strong:  "#e8e0d2"
  surface-dark:          "#181715"
  surface-dark-elevated: "#252320"

  # text — warm-dark hierarchy
  ink:                   "#141413"   # h1, primary
  body-strong:           "#252523"   # emphasized prose
  body:                  "#3d3d3a"   # default running text
  muted:                 "#6c6a64"   # captions, sub-headings
  muted-soft:            "#8e8b82"   # fine print
  on-dark:               "#faf9f5"
  on-dark-soft:          "#a09d96"

  # borders
  hairline:              "#e6dfd8"
  hairline-soft:         "#ebe6df"

  # accent — two-tone sage + deep forest
  btn-bg:                "#b4cbc3"   # primary button surface
  btn-bg-hover:          "#a3bcb3"
  btn-edge:              "#7ea597"   # primary button border (WCAG 3:1)
  btn-edge-hover:        "#6a917f"
  accent:                "#2a4d3a"   # links, focus rings, brand dot
  accent-hover:          "#1f3a2c"
  accent-soft:           "#d8e6db"
  on-accent:             "#ffffff"

  # semantic — warm variants
  success:               "#5b8d6a"
  warning:               "#c69a17"
  error:                 "#b34a3a"

typography:
  display: 'Atkinson Hyperlegible, -apple-system, BlinkMacSystemFont, "Inter", sans-serif'
  body:    'Atkinson Hyperlegible, "Inter", -apple-system, sans-serif'
  ui:      '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
  code:    '"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace'

spacing:  { xxs: 4, xs: 8, sm: 12, md: 16, lg: 24, xl: 32, xxl: 48, section: 96 }
radius:   { xs: 4, sm: 6, md: 8, lg: 12, xl: 16, pill: 9999 }
---

# Lens Design System

A version-controlled, code-truthful spec. Every claim in this document is
backed by what's in `src/tokens.css` and `src/reader.css`. When code and
this doc disagree, **code wins** — update the doc.

This is the canonical written spec. The visual mirror lives at
[`/design/`](https://site-steel-alpha.vercel.app/design/) on the marketing
site.

---

## 1. Brand

### 1.1 The mark

A single deep-forest dot (`#2a4d3a`) with a radial highlight, set inside a
soft sage halo on warm cream — a reading lens reduced to its essence. The
mark scales linearly from 16px to 512px without redrawing; only the halo
opacity adjusts per surface.

**Sizes shipped:**
| Size | File | Where |
|------|------|-------|
| 16   | `site/favicon-16.png` | browser tab |
| 32   | `site/favicon-32.png` | retina tab |
| 48   | `site/favicon-48.png` / `icons/icon48.png` | extension toolbar |
| 128  | `site/icon-128.png` / `icons/icon128.png` | Chrome Web Store |
| 180  | `site/apple-touch-icon.png` | iOS home screen |
| 512  | `site/icon-512.png` | high-res master |

### 1.2 Wordmark

"Lens" set in **Inter Bold**, letter-spacing `-0.02em`. Always paired with
the dot mark on its left (or right in RTL layouts — direction-aware).

### 1.3 Clear space + minimum size

- **Clear space** = the diameter of the dot's halo on all four sides.
  Don't crowd it.
- **Minimum size** = 16px for the bare dot (no halo — the inner gradient
  carries the recognition). 32px when paired with the wordmark.
- **Background** = always one of the four documented surfaces. Never on
  a busy photo. Never on pure black or pure white.

### 1.4 The dot in CSS

```css
.dot {
  width: 14px; height: 14px; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%,
    #4a6f5a 0%, #2a4d3a 60%, #1c3527 100%);
  box-shadow: 0 0 0 4px rgba(180, 203, 195, 0.55),
              0 1px 2px rgba(20, 20, 19, 0.18);
}
```

The pulsing concentric ring is `.dot::after` — animation runs at 2.6s
infinite loop, paused for `prefers-reduced-motion: reduce`.

---

## 2. Colors

Full palette in the front-matter. Notes on usage:

### 2.1 Surfaces — warm cream hierarchy

Pure white is never used. The four surface tiers create depth through
warmth rather than shadows:

| Token | Hex | Used for |
|-------|-----|----------|
| `canvas` | `#faf9f5` | default page floor |
| `surface-soft` | `#f5f0e8` | section dividers, large sub-areas |
| `surface-card` | `#efe9de` | feature cards, code blocks |
| `surface-cream-strong` | `#e8e0d2` | hover states, deeper depth |
| `surface-dark` | `#181715` | footer, floating panel, dark mockups |

### 2.2 Text — warm-dark, WCAG 2.2 AA verified on canvas

| Token | Hex | Contrast on canvas | Use |
|-------|-----|--------------------|-----|
| `ink` | `#141413` | 16.8:1 | h1, primary headings |
| `body-strong` | `#252523` | 13.4:1 | emphasized body |
| `body` | `#3d3d3a` | 11.1:1 | default running text |
| `muted` | `#6c6a64` |  5.4:1 | captions (passes AA body) |
| `muted-soft` | `#8e8b82` |  3.6:1 | fine print (large-text AA) |

### 2.3 Accent — sage + forest

The two-tone green system satisfies WCAG 2.2 AA on multiple fronts:

- **Button surface** (`btn-bg #b4cbc3`) with **border** (`btn-edge #7ea597`)
  gives the button a **3:1 UI contrast** against canvas, satisfying the
  non-text UI-component requirement. Button **text is dark ink**, so the
  text-contrast is ~12.7:1 — comfortably above AA.
- **Accent** (`#2a4d3a` deep forest) is reserved for links, focus rings,
  and the brand dot. 4.5:1+ on every surface.

### 2.4 Semantic — warm variants

Success / warning / error use warm variants to stay in palette family.
Pure RGB primaries are never used.

### 2.5 Dark / sepia / high-contrast themes

Documented in `src/reader.css`. Lens supports four themes:
- `light` (default, the palette above)
- `dark` — deep `#181715` canvas, ivory text, sage shifts deeper
- `sepia` — `#f4ecd8` canvas, warm brown text
- `highcontrast` — pure black/white/yellow, WCAG AAA

The marketing site (`site/index.html`) is **always cream** —
`color-scheme: only light`, no `prefers-color-scheme: dark` override. The
brand surface stays editorial cream; only the reader-view inside the
extension theme-switches.

---

## 3. Typography

### 3.1 Font choices

- **Atkinson Hyperlegible** — body & display. Designed by the Braille
  Institute for low-vision legibility. Free, OFL-licensed. The single
  most important font in the system — used wherever the user actually
  reads.
- **Inter** — UI labels, buttons, chips, badges, nav links, toolbar
  text. Tighter, more dense, lower stroke-contrast — appropriate for
  controls where reading speed matters less than visual rhythm.
- **JetBrains Mono** — code excerpts only (this doc, the design page).

### 3.2 Scale

| Step | Size | Family | Weight | Use |
|------|------|--------|--------|-----|
| Display | 56px | Atkinson | Bold (800) | hero h1 |
| H1 | 36px | Atkinson | Bold (700) | major page title |
| H2 | 24px | Atkinson | Bold (700) | section heading |
| H3 | 18px | Inter | Semi (600) | sub-section |
| Body L | 18px | Atkinson | Regular | adapted article body |
| Body | 16px | Atkinson | Regular | default running text |
| UI Label | 14px | Inter | Medium (500) | form labels, buttons |
| Caption | 13px | Inter | Regular | metadata, timestamps |
| Eyebrow | 11px | Inter | SemiBold + 0.16em tracking | section eyebrows |

### 3.3 Line height

- Reading prose (Atkinson body): **1.65**
- Headings (Atkinson display): **1.05 – 1.15**
- UI controls (Inter): **1.1 – 1.2**

### 3.4 RTL / multi-script support

The same font stack handles English, Hebrew, Arabic. System fallback
(`-apple-system` → SF Pro / Segoe UI) covers Hebrew + Arabic glyphs where
Atkinson Hyperlegible doesn't ship them. Pages set `dir="rtl"` on `<html>`
for Hebrew + Arabic locales; logical CSS properties (`margin-inline-start`,
`text-align: start`, `padding-inline-start`) keep layout direction-aware.

Locale pages live at:
- `/site/index.html` (`lang="en"`)
- `/site/he/index.html` (`lang="he" dir="rtl"`)
- `/site/ar/index.html` (`lang="ar" dir="rtl"`)

---

## 4. Spacing & layout

### 4.1 Scale

4px base — `xxs:4 / xs:8 / sm:12 / md:16 / lg:24 / xl:32 / xxl:48 / section:96`.

### 4.2 Section padding

| Breakpoint | Section padding (y / x) |
|------------|-------------------------|
| Desktop (>980) | 96 / 32 |
| Tablet (≤980) | 80 / 28 |
| Phone (≤640) | 64 / 20 |
| Small phone (≤420) | 48 / 18 |

### 4.3 Container width

- Marketing site: `max-width: 980px` per section, centered
- Reader view body: `max-width: 720px` for the adapted article column
- Options page card: `max-width: 896px`

### 4.4 Whitespace philosophy

Generous. Don't pack content. The cream canvas earns its keep through
breathing room — let the eye rest between blocks.

---

## 5. Elevation

Subtle. Lens deliberately under-uses shadow. The four documented elevations:

| Level | Shadow | Used for |
|-------|--------|----------|
| Button | `0 1 2 rgba(20,20,19,.06)` + inset highlight | every `.lens-btn` |
| Card | `0 2 4 rgba(20,20,19,.10)` | feature cards, demo card |
| Popover | `0 12 36 rgba(20,20,19,.12)` | settings popover, language picker |
| Modal | `0 24 60 rgba(20,20,19,.16)` + `0 4 10 rgba(20,20,19,.06)` | error / diagnostic dialogs |

Buttons additionally get a subtle inset top highlight:
`inset 0 1px 0 rgba(255, 255, 255, 0.5)` — gives a faint pillow effect
that signals tappability without being skeuomorphic.

---

## 6. Shapes — radius

| Token | Px | Use |
|-------|----|----|
| `xs` | 4 | small chips, code inline |
| `sm` | 6 | tags |
| `md` | 8 | **buttons**, inputs |
| `lg` | 12 | cards, popovers |
| `xl` | 16 | major dialogs, video frames |
| `pill` | 9999 | badges, persona chips, slider thumbs |

---

## 7. Components

Each component below lives in `src/reader.css` (or in the inline `<style>`
of `site/index.html` for marketing). Section anchors match the class
names.

### 7.1 Buttons (`.lens-btn`)

**Spec**: 8px radius · 10/16 padding · 40px min-height · Inter Medium
14px · 1px hairline border · subtle 0 1 2 shadow + inset top highlight.

**Variants:**
- `.lens-btn` (default) — cream canvas, ink text, hairline border
- `.lens-btn-primary` — sage `#b4cbc3` background, deep ink text, `#7ea597` border
- `.lens-btn-secondary` — `#efe9de` surface-card background
- `.lens-btn-icon` — square 40 × 40, gestural-only

**States:**
- `:hover` — surface-card background, slight lift (`translateY(-0.5px)`)
- `:active` — pushed in (`translateY(1px)`, inset shadow)
- `:focus-visible` — 3px accent outline, 2px offset
- `[disabled]` — opacity 0.4, cursor not-allowed

**Don't:** never use a white-on-color primary button. Always dark ink on
sage — that's what gives Lens its calm, editorial voice.

### 7.2 Inputs (`.lens-input` / popover form fields)

40px min-height (matches button). 8px radius. Hairline border. Focus state
uses the deep forest accent for the outline.

### 7.3 Reading-level slider

The most important control in Lens. Five steps:
`Simplest · Simpler · Simple · Light edit · Original`

- Track: 8px tall, `hairline` color, full pill radius
- Fill: `btn-edge` (`#7ea597`)
- Thumb: 24px circle, cream canvas with a 3px `btn-edge` border
- Active label below: bolded in deep forest accent

### 7.4 Toggle

44 × 24 track. Off = `hairline`, on = `accent`. 20px white knob with a
1px shadow. 0.2s ease transition.

### 7.5 Badges (`.lens-badge`, read-only status)

Pill-radius, 4/10 padding, 12px Inter SemiBold. Variants: default, accent
(sage), warn (warm amber), error (warm rose), dark (footer / inverted).

### 7.6 Chips (`.chip`, clickable persona tags)

Pill-radius, 7/14 padding, 13px Inter Medium. `.selected` swaps to sage
fill + ink text + bolder weight.

### 7.7 Segmented control

Sits on `surface-card`. Active option gets a `canvas`-fill pill with a
1px shadow.

### 7.8 Language picker (`.lens-popover` language dropdown)

A chevron-trigger button that opens a popover with searchable language
list. Inside the popover: `font-family: var(--lens-font-ui)`, items have
hover background `surface-card`.

---

## 8. Patterns

### 8.1 Top toolbar (`.lens-header`)

64px min-height. Hairline bottom border. Sticky `top: 0`. Background
`rgba(250, 249, 245, 0.88)` with `backdrop-filter: blur(12px)`.

Layout (LTR):
`[ Brand dot + Lens ] [ usage pills ] [ flex spacer ] [ Listen ] [ Aa ] [ ⚙ ] [ ✕ ]`

On RTL the same order mirrors via the `dir="rtl"` attribute on `<html>`.

### 8.2 Floating selection panel (`.lens-floating-panel`)

Dark surface (`surface-dark` `#181715`). 8px padding. 12px radius. Big
shadow: `0 18px 50px rgba(0,0,0,0.20)`. Sits inline next to a selection.
4 actions (Listen · Simplify · Define · Translate); Listen is the primary
sage button, others are dark transparent.

### 7.9 Save / share row (`.lens-save-share`)

Lives under the article body. Hairline-bordered card. Contains:
`Copy · .md · .html · PDF` — all `.lens-btn` size sm.

**MP3 button is hidden by default.** Only revealed when the user opts in
via Options. Mechanism:
```css
.lens-btn[hidden], .lens-pop-btn[hidden] { display: none !important; }
```
(plain `hidden` attribute doesn't override `display: inline-flex`.)

### 8.3 Error card (`.lens-error`)

Cream canvas with a thin error-colored border. Layout:
`[ red dot ] [ bold title ]` + 14px body paragraph + action row.

For overload / rate-limit errors specifically, the card auto-retries
twice with backoff (2.5s, then 6s) before exposing a manual "Try again"
button.

### 8.4 Loading skeleton (`.lens-skeleton`)

Shimmer animation, 1.4s linear infinite. Heights:
- `.lens-skeleton-h` — 28px (the headline placeholder)
- `.lens-skeleton-p` — 14px

Widths alternate `95% / 78%` for organic rhythm. Last line is `40%`.
Pauses on `prefers-reduced-motion: reduce`.

---

## 9. Screens

### 9.1 Reader view

Full-page overlay (`position: fixed; inset: 0; z-index: 2147483647`).
Lives in a shadow DOM to avoid host-page CSS bleed.

Layout: top toolbar + scrollable article body + floating panel (appears
on selection).

The body uses Atkinson Hyperlegible at 18px / 1.65 line-height, with a
720px column width centered. Headings: 32px Atkinson Bold.

### 9.2 Options page (`src/options.html`)

Standalone page opened from the browser action. Contains:
- API key input (Anthropic; optional OpenAI for MP3)
- Reading-level slider
- Persona tag chips
- Theme picker (segmented)
- Font picker
- "Enable Audio (MP3) export" toggle (reveals OpenAI key field when on)
- Allowed/blocked domains list
- Save / Cancel button row

### 9.3 Onboarding (`src/onboarding.html`)

Opens on first install. Conversational flow:
1. Welcome — "Tell Lens how you read"
2. Free-text description input OR preset chips (ADHD / Dyslexia / etc.)
3. Reading level slider
4. Theme + font preview
5. "Start adapting" CTA

### 9.4 Marketing landing (`site/index.html`)

Three locales: `/`, `/he/`, `/ar/`.

Structure: sticky nav (grid 1fr · auto · 1fr — keeps the language pill
centered) → hero with word-by-word headline reveal → 36-second promo
video → animated demo card cycling 7 languages → features grid → pricing
→ install → footer with language switcher.

---

## 10. Responsive behavior

### 10.1 Breakpoints

Single source of truth — single `@media` block per breakpoint:

| Breakpoint | Trigger | Changes |
|------------|---------|---------|
| Tablet | ≤980px | section padding 80/28; cards tighter; grid 3→2 |
| Phone | ≤640px | nav links hide (logo + CTA only); grid 2→1; CTAs full-width |
| Small phone | ≤420px | section padding 48/18; h1 28-36 fluid; nav padding shrinks |

### 10.2 Touch targets

40px minimum on all interactive elements (matches button min-height).
`:hover` styles disabled on `hover: none` devices so tap-and-hold doesn't
leave buttons stuck in hover.

### 10.3 Fluid type

Hero h1 uses `clamp(38px, 6vw, 60px)` (English) → `clamp(28px, 9vw,
36px)` on small phones. Section titles use `clamp(28px, 4vw, 38px)`.

### 10.4 Nav collapse strategy

Below 640px: `nav-links a:not(.cta)` hides. Logo + language pill (compact
EN / עב / ع) + Get Lens CTA remain. The language pill is *never* hidden
— always reachable on every viewport.

---

## 11. Do's and don'ts

### Do

- Use the Atkinson Hyperlegible font for any text the user will read.
- Reserve Inter for UI controls (buttons, labels, chips, badges).
- Always use dark ink (`#141413`) text on the sage primary button.
- Always use one of the four documented surfaces (canvas / soft / card / dark) — never pure white or pure black.
- Use logical CSS properties (`margin-inline-start`, `text-align: start`) so layouts mirror in RTL locales.
- Set `theme-color` meta to `#faf9f5` on every page so mobile status bars stay cream.
- Test new colors against WCAG 2.2 AA on the `canvas` swatch.

### Don't

- Don't auto-flip the marketing site to dark mode based on `prefers-color-scheme`. The brand is always cream.
- Don't use coral/red CTAs (Anthropic does — Lens deliberately doesn't).
- Don't reach for `display: none` to hide a button — use `[hidden]` and add `.lens-btn[hidden] { display: none !important; }` to defeat the inline-flex override.
- Don't add `prefers-color-scheme: dark` media queries on the marketing site.
- Don't put nav links on multiple lines on mobile — collapse them entirely.
- Don't name specific competitor brands in the marketing copy. Critique the category, not the companies.

---

## 12. File map

Where to change what:

| Concern | File |
|---------|------|
| Color tokens | `src/tokens.css` |
| Component styles | `src/reader.css` |
| Color-blindness filters | `src/color-blindness.css` |
| Reader view DOM | `src/content.js` |
| Service worker / Anthropic streaming | `src/background.js` |
| Options page UI | `src/options.html` + `src/options.js` |
| Onboarding | `src/onboarding.html` + `src/onboarding.js` |
| PDF reader | `src/pdf-adapter.html` + `src/pdf-adapter.js` |
| Marketing site (EN) | `site/index.html` |
| Marketing site (HE) | `site/he/index.html` |
| Marketing site (AR) | `site/ar/index.html` |
| Privacy policy | `site/privacy.html` |
| Promo video source | `/tmp/lens-promo/` (Remotion) |
| Promo video output | `site/video/lens-promo.mp4` |
| Design system reference (this page, rendered) | `site/design/index.html` |
| Design system reference (this doc, written) | `LENS_DESIGN.md` (you are here) |
| Brand assets | `site/icon-*.png`, `site/favicon-*.png`, `site/apple-touch-icon.png` |
| Store listing | `store-listing/` |

---

## 13. Iteration guide

When you change a token:
1. Edit `src/tokens.css` (extension) **and** the matching variable in `site/design/index.html` (mirror) **and** the matching variable in `site/index.html`/`/he/`/`/ar/`.
2. Bump the version at the top of this file.
3. Update the last-updated date.
4. If the token changed enough to affect contrast, re-verify WCAG 2.2 AA on the canvas swatch.

When you change a component metric (button height, slider thumb size, etc.):
1. Edit `src/reader.css`.
2. Update the matching section in this doc.
3. Update the matching mirror in `site/design/index.html`.
4. Rebuild + reload the extension to verify.

When you add a new component:
1. Add styles to `src/reader.css` (or page-local for options/onboarding).
2. Add a section here under "Components".
3. Add a live demo to `site/design/index.html`.
4. Update the sidebar nav in `site/design/index.html`.

---

## 14. Known gaps

Things this system intentionally **doesn't** cover today, in case
contributors are looking for them:

- **No data viz.** Lens doesn't render charts. If we add usage analytics
  later, we'll need to extend the palette.
- **No motion library.** Animations are ad-hoc per component (skeleton
  shimmer, dot pulse, word-by-word reveal). If complexity grows, codify.
- **No icon system beyond Unicode glyphs.** Deliberate (see `/design/`
  page "UI icons" section for rationale). May need to revisit if a future
  feature genuinely needs an icon with no Unicode equivalent.
- **No tokens for animation durations or easings.** They're hardcoded
  per-rule. Should probably extract.
- **No formal component naming convention.** We've been mixing `.lens-`
  (extension) and unprefixed (marketing site). Codify if confusion arises.

---

## 14.5. AI engines

Lens supports four AI engines, selectable in Options. The dispatcher
lives in `src/ai/index.js`; each engine is a sibling module that
implements the same port-message protocol (`start`, `delta`, `usage`,
`done`, `error`, `stop-reason`).

| Engine | Module | Model | Cost | When to use |
|--------|--------|-------|------|-------------|
| `byok` | `ai/byok.js` | Claude Haiku 4.5 | ~$0.003/article | Best editorial voice, what we tuned for |
| `gemini` | `ai/gemini.js` | Gemini 2.5 Flash | **Free** (1,500/day) | No-API-key onboarding, strong multilingual |
| `local` | `ai/local.js` | Chrome built-in (Gemini Nano) | Free | Privacy-first, shorter context |
| `auto` | dispatcher | first available | varies | Default for new installs |

`auto` resolution order: Anthropic key saved → BYOK; else Gemini key
saved → Gemini; else Local. The user can override any time.

All four engines stream tokens into the same shadow-DOM reader view —
content.js doesn't know which provider produced the text. Adding a new
engine = one new sibling module in `src/ai/` + one entry in the
dispatcher + one radio button in Options.

---

## 15. Provenance

- The cream + ink hierarchy is derived from Anthropic's Claude design language (see sibling doc `DESIGN.md` for the reverse-engineered Claude reference).
- Lens diverges from Claude on the CTA: Lens uses sage `#b4cbc3` where Claude uses coral `#cc785c`. Sage tests better with low-vision and dyslexia readers — the primary Lens audience.
- The palette family is **Forest Canopy** in standard design-vocabulary terms (cream + sage + deep forest).
- Atkinson Hyperlegible is the canonical accessibility-first body font; designed by the Braille Institute, released under SIL OFL.

---

*This document is the single source of truth for Lens design. If something here disagrees with what ships in `src/tokens.css` or `src/reader.css`, the code wins — update this doc to match.*
