---
name: Inkwell
palette: Viola
version: 0.2
tagline: Quiet by default. Violet when it matters.
source_of_truth: src/app/globals.css
colors:
  light:
    background: "#f1edf8"
    background-elevated: "#f7ead9"
    background-surface: "#f1dec6"
    foreground: "#211c2e"
    foreground-muted: "#56506c"
    foreground-subtle: "#726788"
    primary: "#7c6fb0"
    primary-light: "#9b8fd0"
    primary-dark: "#5f5389"
    secondary: "#f1dec6"
    accent-warm: "#c98a3d"
    accent-coral: "#d9854f"
    success: "#4d7c5f"
    error: "#b3402c"
    offline: "#c0525a"
    border: "rgba(90, 70, 140, 0.14)"
  dark:
    background: "#14121a"
    background-elevated: "#1b1823"
    background-surface: "#221e2c"
    foreground: "#faebd7"
    foreground-muted: "#b4aec4"
    foreground-subtle: "#837c98"
    primary: "#a89cd8"
    primary-light: "#c3b9e8"
    primary-dark: "#8b7fb8"
    secondary: "#fae3c6"
    accent-warm: "#f5c98a"
    accent-coral: "#eda673"
    success: "#6fae82"
    error: "#d17563"
    offline: "#c0525a"
    border: "rgba(250, 227, 198, 0.12)"
typography:
  display: { family: Fraunces, token: --font-display, use: "Headings, titles, display moments" }
  sans: { family: Synonym, token: --font-sans, use: "Body copy, nav, buttons. The body default" }
  lead: { family: Amulya, token: --font-lead, use: "Lead paragraph under a heading" }
  hero: { family: Bespoke Stencil, token: --font-hero, use: "The hero name lockup ONLY" }
  mono: { family: JetBrains Mono, token: --font-mono, use: "Eyebrows, dates, tech tags, UI chrome" }
font_size:
  heroTitle: "clamp(2.75rem, 8.5vw, 6.5rem)"
  heroTitleCompact: "clamp(2.25rem, 5.4vw, 4.25rem)"
  sectionTitle: "clamp(2.75rem, 6.5vw, 4.5rem)"
  subTitle: "clamp(1.5rem, 3vw, 2.25rem)"
  entryTitle: "clamp(1.2rem, 2.5vw, 1.5rem)"
  contactTitle: "clamp(2rem, 4vw, 3rem)"
  lead: "clamp(1.125rem, 2.4vw, 1.375rem)"
  heroLead: "clamp(1.125rem, 2.4vw, 1.5rem)"
  body: 1rem
rounded:
  xs: 0.375rem
  sm: 0.5rem
  base: 0.75rem
  lg: 1rem
  xl: 1.5rem
  2xl: 2rem
motion:
  settle: "cubic-bezier(0.23, 1, 0.32, 1)"
  rise: "cubic-bezier(0.22, 1, 0.36, 1)"
  slide: "cubic-bezier(0.87, 0, 0.13, 1)"
  ease-out: "ease-out"
  linear: "linear"
---

# Inkwell design system

> Open when: you are about to build, change or review any visible part of this site (the portfolio, the design pages, the learn guides).
> Contains: the tokens, the rules, and what to do and not do. The values above are copied from `src/app/globals.css`, which is the **source of truth**. If they disagree, `globals.css` wins and this file is the bug.

Inkwell is the design system behind sanjeevkujur.dev: lilac paper by day, ink by night, and one violet that speaks only when it has something to say. Its palette is called Viola. The showcase lives at `/design` (served at design.sanjeevkujur.dev).

## 1. How to use this file

1. MUST read this file before you add or change UI, styles, copy tone or motion.
2. MUST take colours, sizes, radii and fonts from the tokens below. NEVER invent a value.
3. If something you need is missing, add it to `globals.css` (or `typography.config.ts` for type), then document it here. NEVER add a one-off.
4. Run `node scripts/check-design-md.mjs` after editing tokens. It fails if the colours in this file drift from `globals.css`.
5. The guide chapters under `/pega` and `/learn` follow the same system. Their extra rules are in `AGENTS.md` and `.claude/skills/learn-guide/`.

## 2. Principles

1. **One source of truth.** Every hex is written once, as a primitive in `globals.css`. Everything else points at it.
2. **Restraint is the brand.** About 70% violet and its surfaces, 20% cream, 10% peach or amber. The accent stays rare so it still means something.
3. **Paper by day, ink by night.** Light and dark are two first-class surfaces, not a filter. Dark overrides only what must differ, through one `data-theme` attribute.
4. **Motion needs a reason.** Things move to explain a change, never to decorate. Anything that loops gets a reduced-motion fallback.

## 3. Colour

Three layers, edited in this order: **primitives** (raw values, the only place hex lives), **semantic** (what the UI uses, per theme), **component** (narrow tokens built from semantic).

| Token | Role |
| --- | --- |
| `--background` | The page. Lilac paper in light, ink in dark |
| `--background-elevated` | Cards and raised panels, one step off the page |
| `--background-surface` | Inset wells, the deepest layer |
| `--foreground` | Primary text |
| `--foreground-muted` | Secondary and supporting text |
| `--foreground-subtle` | Metadata and labels. Whispers |
| `--primary` | Violet. Links, focus, hover fills. The voice of the page |
| `--primary-light` / `--primary-dark` | Soft emphasis and gradients / pressed states and strong marks |
| `--secondary` | Cream. Warm secondary surfaces |
| `--accent-warm` / `--accent-coral` | Amber or peach. One small spark, never a theme |
| `--success` / `--error` / `--offline` | Healthy, needs attention, unreachable |
| `--border` / `--border-subtle` | Hairlines |

The 70 / 20 / 10 split (violet, cream, peach) is the measure of the palette. The Color chapter draws it as a measuring scale with the `RatioScale` component (`src/components/design/ratio-scale.tsx`): gap-free segments sized to their share, with a ruler underneath. Use it for any proportion you need to show.

Rules:
- NEVER write a raw hex in a component. Use `var(--token)` or a Tailwind alias (`bg-background`, `text-muted`, `text-primary`, `bg-elevated`, `bg-surface`, `border-border`). The only exception is a third-party brand colour, which lives with the primitives.
- `primary-foreground` is NOT defined. On a `bg-primary` surface use `text-background`.
- Real token names are `--foreground-muted` and `--background-elevated`. `bg-background-elevated` is NOT a Tailwind class; use `bg-elevated`.
- NEVER ask a component which theme is active. Read the token and let the theme decide.
- NEVER spend the accent on decoration. If peach appears three times on a screen it has stopped being a spark.

### Brand scopes

A product with its own identity may wear its own colours inside a scope, without forking the system. A wrapper with `data-brand="<name>"` redefines the same semantic tokens in `globals.css` (a light block, and a `[data-theme="dark"] [data-brand="<name>"]` block), and its raw values live with the primitives. Components never change: they keep reading `--primary`, `--background` and the rest. The theme still decides light or dark.

| Scope | Where | Palette |
| --- | --- | --- |
| `lens` | `/lens` and `/lens/privacy` (`src/app/lens/layout.tsx`) | Terracotta orange taken from the extension's icon, on warm paper (light) and warm ink (dark). Primitives are `--lens-*` |

Rules for a scope:
- Compute the contrast, never guess it. Lens: text on paper 15.2:1, muted 7.0:1, subtle 4.7:1, primary 5.1:1; dark text 16.1:1, muted 9.4:1, subtle 5.0:1, primary 7.6:1.
- Override every derived token that names a primitive directly (the primary tints and glow), or the old colour leaks through.
- On Lens, `.btn-store` is the solid `--primary` in light, because text on the lighter orange falls to 3.6:1, and solid cream with dark text in dark, as elsewhere.
- The `colors.light` and `colors.dark` front matter describe Inkwell only. A scope is documented here, not there.

## 4. Typography

Five voices, each with one job. Reference them through `src/config/typography.config.ts`, not a hard-coded `var(--font-*)` string.

| Voice | Family | Use |
| --- | --- | --- |
| display | Fraunces | Headings, titles, display moments. `h1` to `h6` already use it |
| sans | Synonym | Body copy, nav, buttons. The `body` default |
| lead | Amulya | The lead paragraph under a section heading |
| hero | Bespoke Stencil | The hero name lockup ONLY |
| mono | JetBrains Mono | Eyebrows, dates, tech tags, UI chrome |

Sizes live in `fontSize` in `typography.config.ts` as named `clamp()` values (`heroTitle`, `heroTitleCompact`, `sectionTitle`, `subTitle`, `entryTitle`, `contactTitle`, `lead`, `heroLead`, `body`).
- NEVER use the stencil face for a paragraph.
- NEVER write a new `clamp()` in a component. If a size is missing, add it to the scale.
- The `.label-mono` class is the small-caps mono label: 0.6875rem, uppercase, 0.14em tracking, weight 500, `--foreground-subtle`.

## 5. Shape and surface

- Depth is made from **paper**, not shadow: `background`, then `background-elevated`, then `background-surface`.
- Edges are **hairlines** (`border-border`), not boxes. `.rule` is a 1px divider.
- Corners are generous: `xs` 0.375rem, `sm` 0.5rem, `base` 0.75rem, `lg` 1rem, `xl` 1.5rem, `2xl` 2rem.
- `.paper-grain` is the faint paper texture overlay (multiply in light, screen in dark).

## 6. Layout and spacing

- Breakpoints are the Tailwind defaults (`sm` 640, `md` 768, `lg` 1024, `xl` 1280). The guide uses `md:`, `sm:` and `lg:` most.
- Page gutters are most often `px-5 md:px-8` (20px, then 32px); a few pages use `px-4 sm:px-8`. The guide shell is `max-w-[1440px]`; reading text stays at `max-w-2xl` or `max-w-3xl`.
- Chapters are a `div.space-y-28` of sections. Section anchors use `scroll-mt-24` so a heading is not hidden under the sticky header.
- Phones NEVER scroll sideways, on the page or inside it. Code wraps, tables stack into labelled cards, tab and chip rows wrap.
- On touch screens, every control is at least 2.5rem (40px) tall. Prefer `min-h-10` or taller for new controls.

## 7. Motion

Five house easings. Pick by job.

| Name | Curve | Use |
| --- | --- | --- |
| settle | `cubic-bezier(0.23, 1, 0.32, 1)` | Entrances: reveals, the prism mark, most Framer Motion |
| rise | `cubic-bezier(0.22, 1, 0.36, 1)` | CSS keyframe entrances (`.ink-rise`) |
| slide | `cubic-bezier(0.87, 0, 0.13, 1)` | Accordions: a firm start and stop |
| ease-out | `ease-out` | Hover and press, 300 to 500 ms, plain CSS |
| linear | `linear` | Things that never stop: spinners, orbits |

- Motion is Framer Motion, plain CSS transitions and Lenis smooth scroll. GSAP is installed but NOT used; do not claim it.
- MUST add a `prefers-reduced-motion` fallback to anything that loops or travels.
- Flow explainers (the notes, for example the auth note) use three utilities in `globals.css`: `.flow-dash-x` and `.flow-dash-y` (a dashed connector drawn in `currentColor`), `.flow-run` (the dashes march while a step runs; `.flow-rev` reverses the direction) and `.flow-ripple` (a ring that pulses on the active node). All of them stop under reduced motion.

## 8. Components and utilities

- `.label-mono`, `.rule`, `.paper-grain`, `.ink-rise` and `.badge-date` (the "Updated" stamp) are global utilities.
- `.site-link` styles a quiet text link that still reads as a link: accent colour, thin underline that firms up on hover. Use it for the sanjeevkujur.dev link.
- The site's real buttons are custom, not shadcn `ui/Button` (which uses tokens this project does not define). The Components chapter documents the real send button.
- Guide building blocks live in `src/components/pega/parts.tsx` (`Section`, `Prose`, `CodeBlock`, `Callout`, `Term`, `VersionNote`, `Playground`, `Pager`). Design chapters use `src/components/design/chapter-parts.tsx`.

## 9. Accessibility

- Contrast is measured, not assumed. The Accessibility chapter at `/design/accessibility` reports the real ratios from these tokens. Light-theme `primary` on paper is 3.83:1, so it suits large text and UI, not small body text. `accent-warm` on light paper is 2.53:1, so it is decorative only.
- Form inputs are 16px on phones, or iOS zooms the page when one is focused.
- A dialog MUST trap focus, close on Escape and return focus to the control that opened it. The guide search dialog is the reference implementation.
- Never rely on colour alone to carry meaning.

## 10. Do and do not

| Do | Do not |
| --- | --- |
| Use `var(--token)` or a Tailwind alias | Write a raw hex |
| Add a missing size to the scale | Write a new `clamp()` in a component |
| Let the theme decide | Branch on the active theme |
| Give looping motion a reduced-motion fallback | Animate without a way to stop |
| Keep the accent rare | Use peach for decoration |
| Verify a claim against code before writing it on a page | Claim a tool is used when it is not |

## 11. Where things live

| What | Where |
| --- | --- |
| Colour tokens, radii, utilities | `src/app/globals.css` |
| Fonts and type scale | `src/config/typography.config.ts` |
| Design chapters and stack list | `src/config/design-system.config.ts`, `src/app/design/*` |
| Token tables on the design pages | `src/lib/design-tokens.ts` (parses `globals.css` at build time, so they cannot drift) |
| Learn guide rules | `AGENTS.md`, `.claude/skills/learn-guide/` |
