# Claymorphism Design Language System (DLS) v1.0

Production spec for web (CSS / Tailwind CSS) and mobile, with Figma token mapping.

---

## 1. Design Philosophy & Core Principles

**Emotional tone**

| Attribute | In the UI |
|---|---|
| Friendly | Rounded everything; warm, conversational copy |
| Tactile | Every interactive element looks pressable or pick-up-able |
| Playful | Spring motion, soft pastel accents, slightly exaggerated proportions |
| Accessible | Softness lives in surfaces, never in text or state signals |

**Physical metaphor:** each element is soft polymer clay, extruded and pressed onto a sheet of the same material.

- Puffy volume: light hits top-left, so that edge is bright and the bottom-right falls into shade.
- Same-material world: elements are slightly lighter or tinted versions of the background.
- Soft contact shadow: wide, low-opacity, hue-tinted, never grey or black.
- Squishable: hover lifts, press squashes into the surface.
- Inputs and wells are dents in the clay, the inverse of buttons.

**Six principles**

1. One light source: top-left, always.
2. Three shadows, always: outer drop + inner shade + inner highlight.
3. Tint your shadows with the surface hue.
4. Round generously.
5. Depth encodes meaning: raised = actionable, flat = passive, inset = input or pressed.
6. Never let depth carry meaning alone; pair it with a border, icon, label, or color that meets WCAG.

---

## 2. Color Palette & Surface Tokens

### Surfaces

| Token | Light | Dark | Role |
|---|---|---|---|
| `--clay-bg` | `#EFEDFA` | `#1B1930` | Page canvas |
| `--clay-bg-muted` | `#E6E3F6` | `#15132A` | Wells, sidebars, bands |
| `--clay-surface` | `#F7F5FF` | `#2A2748` | Cards, buttons |
| `--clay-surface-raised` | `#FBFAFF` | `#34315A` | Modals, popovers |
| `--clay-surface-sunken` | `#E9E6F8` | `#201E3C` | Inputs, pressed |
| `--clay-border` | `#807BA8` | `#9A96C8` | Functional borders (3:1+) |
| `--clay-border-subtle` | `#D9D5F0` | `#3B3866` | Decorative dividers |

### Text

| Token | Light | Dark | Contrast |
|---|---|---|---|
| `--clay-text` | `#2D2A4A` | `#EDEBFF` | ~11.7:1 / ~14.5:1 |
| `--clay-text-secondary` | `#5B5780` | `#B9B5E0` | ~5.8:1 (light) |
| `--clay-text-disabled` | `#8F8BB0` | `#6E6A99` | Exempt; style visibly disabled |

### Shadow color

| Token | Light | Dark |
|---|---|---|
| `--clay-shadow` | `93 80 170` | `6 4 22` |
| `--clay-highlight` | `255 255 255` | `255 255 255` (alpha 6-10%) |

In dark mode shadows get darker, not lighter; highlights become subtle rim light.

### Pastel accents (dark text on fill)

| Accent | Fill | Deep | Shadow RGB | Contrast |
|---|---|---|---|---|
| Lavender | `#C9C1F7` | `#4A3FC0` | `120 100 220` | ~8.1:1 |
| Mint (success) | `#BFEFD9` | `#1F7A55` | `60 160 120` | ~10.7:1 |
| Peach | `#FFD3BE` | `#B2501F` | `220 130 90` | ~9.9:1 |
| Sky (info) | `#BDE3FF` | `#1D6FA8` | `70 150 220` | ~10.1:1 |
| Pink | `#FFC6DD` | `#B02A66` | `220 100 150` | ~9.3:1 |
| Lemon (warning) | `#FFF0A8` | `#7A5E00` | `200 170 40` | ~11.9:1 |
| Rose (error) | `#FFC9D1` | `#C0264B` | `210 80 100` | ~8.9:1 |

### Solid actions (white text)

| Token | Value | Contrast |
|---|---|---|
| `--clay-primary` | `#6355E0` | ~5.4:1 |
| `--clay-primary-hover` | `#5547D6` | ~6.3:1 |
| `--clay-primary-pressed` | `#4A3FC0` | ~7.7:1 |
| `--clay-danger` | `#C0264B` | ~5.9:1 |
| `--clay-focus` | `#3B2FC9` / `#B5ADFF` (dark) | ~7.4:1 on bg |

For colored objects, override `--clay-shadow` with the accent's triplet so the shading matches the hue.

---

## 3. The Clay Effect Math & Layering

### Formula

```
box-shadow:
  [A] OUTER DROP      ->  D D (2.2*D)  rgb(shadow / 0.16-0.24)
  [B] INNER SHADE     ->  inset -0.6D -0.6D (1.2*D)  rgb(shadow / 0.12-0.20)
  [C] INNER HIGHLIGHT ->  inset  0.6D  0.6D (1.2*D)  rgb(255 255 255 / 0.80-0.95);
```

Spread is always 0. Larger D means lower alpha. Dark mode alphas: A 0.45-0.60, B 0.30-0.40, C 0.06-0.10.

### Elevation tokens

```css
:root {
  --clay-shadow: 93 80 170;
  --clay-highlight: 255 255 255;

  --clay-elev-1: 6px 6px 14px rgb(var(--clay-shadow) / .18),
    inset -4px -4px 8px rgb(var(--clay-shadow) / .14),
    inset 4px 4px 8px rgb(var(--clay-highlight) / .85);
  --clay-elev-2: 10px 10px 24px rgb(var(--clay-shadow) / .20),
    inset -6px -6px 12px rgb(var(--clay-shadow) / .16),
    inset 6px 6px 12px rgb(var(--clay-highlight) / .90);
  --clay-elev-3: 16px 16px 36px rgb(var(--clay-shadow) / .22),
    inset -10px -10px 20px rgb(var(--clay-shadow) / .18),
    inset 10px 10px 20px rgb(var(--clay-highlight) / .90);
  --clay-elev-4: 24px 24px 56px rgb(var(--clay-shadow) / .24),
    inset -14px -14px 28px rgb(var(--clay-shadow) / .18),
    inset 14px 14px 28px rgb(var(--clay-highlight) / .90);
  --clay-flat: inset -4px -4px 10px rgb(var(--clay-shadow) / .10),
    inset 4px 4px 10px rgb(var(--clay-highlight) / .80);
  --clay-pressed: 2px 2px 6px rgb(var(--clay-shadow) / .12),
    inset 6px 6px 14px rgb(var(--clay-shadow) / .28),
    inset -4px -4px 10px rgb(var(--clay-highlight) / .70);
  --clay-inset: inset 5px 5px 12px rgb(var(--clay-shadow) / .20),
    inset -5px -5px 12px rgb(var(--clay-highlight) / .90);
}

[data-theme="dark"] {
  --clay-shadow: 6 4 22;
  --clay-elev-1: 6px 6px 14px rgb(var(--clay-shadow) / .50),
    inset -4px -4px 8px rgb(var(--clay-shadow) / .35),
    inset 4px 4px 8px rgb(var(--clay-highlight) / .08);
  --clay-elev-2: 10px 10px 24px rgb(var(--clay-shadow) / .55),
    inset -6px -6px 12px rgb(var(--clay-shadow) / .38),
    inset 6px 6px 12px rgb(var(--clay-highlight) / .09);
  --clay-elev-3: 16px 16px 36px rgb(var(--clay-shadow) / .58),
    inset -10px -10px 20px rgb(var(--clay-shadow) / .40),
    inset 10px 10px 20px rgb(var(--clay-highlight) / .10);
  --clay-elev-4: 24px 24px 56px rgb(var(--clay-shadow) / .60),
    inset -14px -14px 28px rgb(var(--clay-shadow) / .40),
    inset 14px 14px 28px rgb(var(--clay-highlight) / .10);
  --clay-flat: inset -4px -4px 10px rgb(var(--clay-shadow) / .30),
    inset 4px 4px 10px rgb(var(--clay-highlight) / .06);
  --clay-pressed: 2px 2px 6px rgb(var(--clay-shadow) / .30),
    inset 6px 6px 14px rgb(var(--clay-shadow) / .55),
    inset -4px -4px 10px rgb(var(--clay-highlight) / .06);
  --clay-inset: inset 5px 5px 12px rgb(var(--clay-shadow) / .55),
    inset -5px -5px 12px rgb(var(--clay-highlight) / .07);
}
```

Hue tint overrides: `.clay-tint-mint { --clay-shadow: 60 160 120; }` (repeat per accent triplet above).

### Border radius

| Token | Value | Use |
|---|---|---|
| xs | 8px | Checkbox, code chip |
| sm | 12px | Tags, tooltips |
| md | 20px | Textareas, menus |
| lg | 28px | Standard cards |
| xl | 36px | Feature cards, modals |
| 2xl | 48px | Hero panels, sheets |
| pill | 9999px | Buttons, inputs, badges, toggles |

Rules: radius is at least 35% of the shorter side; controls up to 64px tall are pills; nested radius = outer radius - padding (min 8px); no sharp corners anywhere; inner shadow blur is at most half the shorter side.

### Motion

```css
:root {
  --clay-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --clay-ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
  --clay-dur-fast: 120ms; --clay-dur-base: 200ms; --clay-dur-slow: 320ms;
}
```

### Tailwind config (excerpt)

```js
// tailwind.config.js
module.exports = {
  darkMode: ['class', '[data-theme="dark"]'],
  theme: { extend: {
    colors: { clay: {
      bg: 'var(--clay-bg)', surface: 'var(--clay-surface)', sunken: 'var(--clay-surface-sunken)',
      border: 'var(--clay-border)', text: 'var(--clay-text)', 'text-2': 'var(--clay-text-secondary)',
      primary: '#6355E0', 'primary-hover': '#5547D6', 'primary-pressed': '#4A3FC0',
      danger: '#C0264B', focus: 'var(--clay-focus)',
      lavender: '#C9C1F7', mint: '#BFEFD9', peach: '#FFD3BE', sky: '#BDE3FF',
      pink: '#FFC6DD', lemon: '#FFF0A8', rose: '#FFC9D1' } },
    boxShadow: {
      'clay-1': 'var(--clay-elev-1)', 'clay-2': 'var(--clay-elev-2)',
      'clay-3': 'var(--clay-elev-3)', 'clay-4': 'var(--clay-elev-4)',
      'clay-flat': 'var(--clay-flat)', 'clay-pressed': 'var(--clay-pressed)',
      'clay-inset': 'var(--clay-inset)' },
    borderRadius: { 'clay-xs': '8px', 'clay-sm': '12px', 'clay-md': '20px',
      'clay-lg': '28px', 'clay-xl': '36px', 'clay-2xl': '48px' },
    fontFamily: { display: ['Fredoka', 'Nunito', 'ui-rounded', 'sans-serif'],
      sans: ['Nunito', 'ui-rounded', 'system-ui', 'sans-serif'] },
  } },
};
```

### Figma mapping

| CSS layer | Figma effect |
|---|---|
| Outer drop `X Y B color` | Drop shadow, X, Y, Blur = B, Spread 0 |
| Inner shade `inset -x -y b` | Inner shadow, X = -x, Y = -y, Blur = b |
| Inner highlight `inset x y b white` | Inner shadow, white |

Create one Effect Style per token (`clay/elev-1` ... `clay/inset`) with Light and Dark modes via Variables. On iOS/Android, export a 9-slice image or stack a drop shadow plus two gradient-masked inner layers; keep token names identical.

---

## 4. Typography & Hierarchy

| Role | Family | Fallbacks |
|---|---|---|
| Display | Fredoka (600, 700) | Baloo 2, Nunito, ui-rounded |
| Body / UI | Nunito (500, 600, 700) | Quicksand (600+ only), Poppins, Inter |
| Mono | JetBrains Mono (500) | ui-monospace |

| Token | Size | Line height | Weight | Use |
|---|---|---|---|---|
| display-2xl | 60px | 1.05 | 700 | Hero |
| display-xl | 48px | 1.1 | 700 | Page title |
| h1 | 36px | 1.15 | 700 | Section |
| h2 | 30px | 1.2 | 600 | Subsection |
| h3 | 24px | 1.25 | 600 | Card title |
| h4 | 20px | 1.3 | 600 | Group title |
| body-lg | 18px | 1.6 | 500 | Lead |
| body | 16px | 1.6 | 500 | Default |
| body-sm | 14px | 1.5 | 500 | Helper text |
| label | 14px | 1.2 | 700 | Buttons, labels |
| caption | 12px | 1.4 | 600 | Badges, meta |

Rules: body weight 500+; never put text on inner-shadow zones (16px+ padding); lines of 60-72 characters; no text-shadow; use `rem` and `min-height` so text scales.

---

## 5. Component Specifications

### Buttons

| State | Transform | Shadow | Surface |
|---|---|---|---|
| Default | none | elev-2 | primary, white text |
| Hover | translateY(-2px) scale(1.02) | elev-3 | primary-hover |
| Active | translateY(2px) scale(0.98) | pressed | primary-pressed |
| Focus-visible | none | unchanged + 3px outline, 3px offset | unchanged |
| Disabled | none | flat | sunken, secondary text |

Sizes: sm 40px, md 48px, lg 60px; always pill; 44px minimum hit area.

```css
.clay-btn {
  height: 48px; padding: 0 28px; border: 0; border-radius: 9999px;
  font: 700 1rem/1.2 'Nunito', sans-serif; color: #fff; background: #6355E0;
  box-shadow: var(--clay-elev-2); --clay-shadow: 85 70 210;
  transition: transform var(--clay-dur-base) var(--clay-ease-spring),
              box-shadow var(--clay-dur-base) var(--clay-ease-soft);
}
.clay-btn:hover  { transform: translateY(-2px) scale(1.02); box-shadow: var(--clay-elev-3); background: #5547D6; }
.clay-btn:active { transform: translateY(2px) scale(.98); box-shadow: var(--clay-pressed); background: #4A3FC0; }
.clay-btn:focus-visible { outline: 3px solid var(--clay-focus); outline-offset: 3px; }
```

### Cards & containers

| Variant | Shadow | Radius | Use |
|---|---|---|---|
| Elevated | elev-2 (hover elev-3) | 28px | Clickable cards, tiles |
| Flat | flat | 28px | Passive groupings |
| Hero / Modal | elev-4 | 36-48px | Dialogs, sheets |
| Well | inset | 20px | Code, quotes |
| Tinted | elev-2 + tint + pastel fill | 28px | Categories, stats |

Keep gutters at 24px or more; nest clay at most one level; inner items use elev-1 or flat.

### Inputs & textareas

Dents in the clay: sunken surface, `--clay-inset`, plus a visible 1.5px `--clay-border` (3.4:1).

| State | Border | Notes |
|---|---|---|
| Default | 1.5px border | Label always visible above |
| Hover | text-secondary | |
| Focus | 2px focus color + 3px outline | Ring mandatory |
| Error | 2px `#C0264B`, bg `#FFF1F3` | Icon + message below |
| Disabled | dashed border-subtle | flat shadow |

```css
.clay-input {
  height: 52px; padding: 0 20px; border-radius: 9999px;
  border: 1.5px solid var(--clay-border);
  background: var(--clay-surface-sunken); box-shadow: var(--clay-inset);
}
.clay-input:focus-visible { outline: 3px solid var(--clay-focus); outline-offset: 2px; }
.clay-textarea { height: auto; min-height: 120px; border-radius: 24px; padding: 16px 20px; }
```

### Badges & tags

Pastel fill with dark text, pill, caption type, elev-1 at reduced alpha. Variants: soft, outline, count (primary + white text), removable (44px touch area), status dot. Never rely on color alone; add an icon or text.

### Other components

Switch: inset track, clay thumb, plus on/off icon. Checkbox: 24px, radius 8px. Tabs: sunken pill container, raised active pill. Modal: elev-4, radius 36-48px. Tooltip: `#2D2A4A` surface, white text. Mobile nav: floating clay pill, active item pressed.

---

## 6. Accessibility & Usability Guidelines

**Principle:** shadows are decoration. Contrast, borders, icons, and text carry meaning.

### Contrast

| Requirement | Threshold |
|---|---|
| Body text (1.4.3) | 4.5:1 |
| Large text | 3:1 |
| Component boundaries and states (1.4.11) | 3:1 |
| Focus indicator (2.4.11 / 2.4.13) | 3:1, 2px+ |
| Target size (2.5.8) | 24px minimum; this DLS uses 44px |

| Pair | Ratio | Result |
|---|---|---|
| `#2D2A4A` on `#EFEDFA` | 11.7:1 | Pass |
| `#5B5780` on `#EFEDFA` | 5.8:1 | Pass |
| `#FFFFFF` on `#6355E0` | 5.4:1 | Pass |
| `#2D2A4A` on `#C9C1F7` | 8.1:1 | Pass |
| `#C0264B` on `#EFEDFA` | 5.0:1 | Pass |
| `#1F7A55` on `#EFEDFA` | 4.6:1 | Pass |
| `#807BA8` border on `#EFEDFA` | 3.4:1 | Pass |
| `#3B2FC9` focus on `#EFEDFA` | 7.4:1 | Pass |
| `#FFFFFF` on `#C9C1F7` | ~1.8:1 | Fail: never white on pastel |

Ratios are rounded estimates; re-verify final values with a contrast checker.

### Focus rings

Use `outline`, not `box-shadow`, so the ring is independent of elevation and survives forced-colors mode.

```css
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 3px solid var(--clay-focus);
  outline-offset: 3px;
}
```

### Error, success, warning

Layer four cues: 2px status border, 20px icon, message text linked via `aria-describedby`, and an optional light tint. Status text colors: error `#C0264B`, success `#1F7A55`, warning `#7A5E00`, info `#1D6FA8`.

### Keeping soft surfaces distinguishable

- Add a 3:1 functional border to inputs, toggles, and checkboxes.
- Keep 4-6% luminance difference between surface and background, or add a 1px subtle border to cards.
- Never use elevation alone for hierarchy or selection; pair with fill, icon, or `aria-selected` / `aria-pressed`.

### Media queries

```css
@media (prefers-contrast: more) {
  :root { --clay-border: #4A4570; }
  .clay-card, .clay-btn, .clay-badge { border: 2px solid var(--clay-border); }
}
@media (forced-colors: active) {
  .clay-card, .clay-btn, .clay-input, .clay-badge { border: 2px solid CanvasText; box-shadow: none; }
}
@media (prefers-reduced-motion: reduce) {
  .clay-btn, .clay-card--interactive { transition: none; }
  .clay-btn:hover, .clay-btn:active { transform: none; }
}
```

### Checklist

- Text meets 4.5:1 (3:1 large) on the actual surface, including tinted cards.
- No white text on pastels.
- Inputs, toggles, checkboxes have 3:1+ borders.
- Focus uses outline, 3px+, with offset.
- Errors use border + icon + text.
- Touch targets 44px+ with 8px spacing.
- Dark-mode shadows are darker than the surface.
- Hover, pressed, and focus are distinguishable without shadows.
- Reduced motion, contrast, and forced colors are handled.
- Max 3 shadow layers per element; animate `transform`, not `box-shadow`, on long lists.
- Test on OLED at low brightness and outdoors.

---

## Token summary

| Category | Tokens |
|---|---|
| Color | bg, bg-muted, surface, surface-raised, surface-sunken, border, border-subtle, text, text-secondary, text-disabled, primary, danger, focus, 7 pastels |
| Shadow | elev-1 to elev-4, flat, pressed, inset (light + dark) |
| Radius | 8, 12, 20, 28, 36, 48, pill |
| Type | Fredoka, Nunito, JetBrains Mono; 11-step scale |
| Motion | ease-spring, ease-soft; 120 / 200 / 320ms |
| Spacing | 4px grid: 4, 8, 12, 16, 24, 32, 40, 48, 64 |
