# Apple Design

> Use this skill whenever the user wants UI built in Apple's aesthetic language: glassmorphism, liquid glass, spring physics, Dynamic Island patterns, SF Pro typography, Control Center widgets, iOS/macOS system chrome, vibrancy, or any explicit "Apple style / iOS style / macOS style" request. Also trigger when the user says "clean and minimal with blur", "frosted glass UI", or references visionOS, watchOS, or iOS 26's new liquid glass system. Do NOT use for generic material design, flat design without glass, or designs where the user has specified a non-Apple aesthetic.

- Skill: `yourpovv/apple-design` (Agent Skill)
- Install (CLI): `npx skillmds@latest add yourpovv/apple-design`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yourpovv/apple-design/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: yourpovv (https://skillmd.com/u/yourpovv)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/yourpovv/apple-design

---


> **Agent load: opt-in only.** Use only when the human asked for Apple / iOS / macOS / liquid glass aesthetic.
> Do not apply this skill to generic web, Material, or flat designs. Section-load: philosophy + material stack + the component patterns you need.
> Pair with project `AGENTS.md`. Not a default for every UI task.

# Apple Design System: Complete Reference

You are implementing UI in Apple's aesthetic language. Every decision, blur radius, spring curve, border opacity, corner radius, type weight, should feel like it shipped on a real Apple platform. Ship nothing that Apple's HIG would call "inconsistent with the system." Before writing a single line of code, internalize this document fully.

---

## 1. The philosophy behind the aesthetic

Apple's UI is a material system, not a color system. The goal is to simulate real-world physical materials, glass, aluminum, fabric, frosted acrylic, rendered with digital light. Every surface has:

- **Translucency**, the background bleeds through, tinted and blurred
- **Depth**, foreground, midground, and background exist simultaneously
- **Light response**, surfaces have highlights (top edge), fill (tint), and shadow (bottom/rim)
- **Physics**, motion follows spring dynamics, not linear easing

If any component looks flat, opaque, or snaps to its final position, it is wrong.

---

## 2. The material stack

Apple uses four named materials. Implement all four and choose the right one per context.

### Thick, primary surfaces (cards, modals, sidebars)

```css
background: rgba(255, 255, 255, 0.72);           /* light mode */
background: rgba(28, 28, 36, 0.72);              /* dark mode  */
backdrop-filter: blur(40px) saturate(180%);
-webkit-backdrop-filter: blur(40px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.55);     /* light */
border: 1px solid rgba(255, 255, 255, 0.10);     /* dark  */
```

### Regular, standard panels, popovers, sheets

```css
background: rgba(255, 255, 255, 0.55);
backdrop-filter: blur(28px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.45);
```

### Thin, pill controls, tab bars, toolbars

```css
background: rgba(255, 255, 255, 0.28);
backdrop-filter: blur(16px) saturate(160%);
border: 1px solid rgba(255, 255, 255, 0.38);
```

### Ultra-thin, hover overlays, transient chrome

```css
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(10px) saturate(140%);
border: 1px solid rgba(255, 255, 255, 0.22);
```

### Liquid Glass (iOS 26 / visionOS), new pill + button material

The newest Apple material. Use for floating controls, pills, and action buttons.

```css
background: rgba(255, 255, 255, 0.18);
backdrop-filter: blur(24px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.52);
/* Required specular highlight, simulates curved glass catching light */
background: linear-gradient(
  180deg,
  rgba(255, 255, 255, 0.30) 0%,   /* top highlight */
  rgba(255, 255, 255, 0.08) 55%,  /* fade */
  rgba(255, 255, 255, 0.00) 100%  /* transparent base */
);
```

Stack these: the `backdrop-filter` div beneath, the gradient div above it (via `::after`), the content on top.

### Dark glass, Control Center, Now Playing, Lock Screen widgets

```css
background: rgba(18, 18, 28, 0.72);
backdrop-filter: blur(30px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.10);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255,255,255,0.08);
```

---

## 3. Corner radius, the Apple radius scale

Never use arbitrary values. Apple uses a specific radius vocabulary.

| Token        | Value   | Use case                                         |
|-------------|---------|--------------------------------------------------|
| `--r-xs`    | `8px`   | small badges, tags, input fields                 |
| `--r-sm`    | `12px`  | buttons, chips, small cards                      |
| `--r-md`    | `16px`  | standard cards, sheets, popovers                 |
| `--r-lg`    | `20px`  | large cards, modal sheets, widgets               |
| `--r-xl`    | `28px`  | floating panels, iPadOS split views              |
| `--r-2xl`   | `36px`  | app icon shape (only for icon containers)        |
| `--r-pill`  | `999px` | toggles, segmented controls, Dynamic Island      |

```css
:root {
  --r-xs: 8px; --r-sm: 12px; --r-md: 16px; --r-lg: 20px;
  --r-xl: 28px; --r-2xl: 36px; --r-pill: 999px;
}
```

---

## 4. Motion, spring physics only

Apple does not use `ease`, `ease-in-out`, or linear curves for interactive elements. Every motion is spring-based.

### The four Apple spring presets

```css
/* 1. Default spring, general interactions, card lifts */
--spring-default:  cubic-bezier(0.34, 1.56, 0.64, 1);

/* 2. Snappy spring, buttons, toggles, taps */
--spring-snappy:   cubic-bezier(0.36, 1.4,  0.64, 1);

/* 3. Gentle spring, sheet presentation, modals */
--spring-gentle:   cubic-bezier(0.16, 1.0,  0.3,  1);

/* 4. Bounce spring, notifications, playful elements */
--spring-bounce:   cubic-bezier(0.22, 1.6,  0.36, 1);
```

The key is the y-values exceeding 1.0, this is what creates the overshoot/bounce that Apple's interfaces are famous for. A spring that never exceeds 1.0 is not a spring.

### Duration guidelines

| Interaction type          | Duration    |
|--------------------------|-------------|
| Button press / tap       | 280-320ms   |
| Toggle on/off            | 360-400ms   |
| Card hover lift          | 300-380ms   |
| Sheet / modal present    | 420-500ms   |
| Context menu pop-in      | 200-260ms   |
| Dynamic Island expand    | 380-440ms   |
| Page transition          | 480-560ms   |

### Always respect reduced motion

```css
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
```

### Press states, the haptic illusion

Every interactive element must have:

1. **Press down**: `scale(0.93-0.96)` + `filter: brightness(0.9)`, fast (120ms, ease-in)
2. **Release**: spring back past 1.0 before settling, uses `--spring-snappy`
3. **Hover**: `scale(1.02-1.05)` + `translateY(-2px)` with subtle shadow increase

```css
.apple-btn {
  transition: transform 360ms var(--spring-snappy),
              box-shadow 200ms ease,
              filter 120ms ease;
}
.apple-btn:hover  { transform: scale(1.04) translateY(-2px); }
.apple-btn:active { transform: scale(0.94); filter: brightness(0.88); transition-duration: 120ms; }
```

---

## 5. Typography, SF Pro system

Apple's type system is precise. Use system fonts and follow the exact scale.

```css
/* Always use system font stack, SF Pro on Apple devices, Inter fallback */
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text',
             'Inter', system-ui, sans-serif;

/* For rounded/friendly contexts (SF Pro Rounded) */
font-variation-settings: 'ROND' 1;
```

### The iOS/macOS type scale

| Role            | Size   | Weight | Line height | Letter spacing   |
|----------------|--------|--------|-------------|-----------------|
| Large Title     | 34px   | 700    | 1.2         | -0.4px          |
| Title 1         | 28px   | 700    | 1.25        | -0.3px          |
| Title 2         | 22px   | 700    | 1.3         | -0.2px          |
| Title 3         | 20px   | 600    | 1.3         | -0.1px          |
| Headline        | 17px   | 600    | 1.4         | -0.05px         |
| Body            | 17px   | 400    | 1.5         | 0               |
| Callout         | 16px   | 400    | 1.5         | 0               |
| Subheadline     | 15px   | 400    | 1.45        | 0               |
| Footnote        | 13px   | 400    | 1.4         | 0               |
| Caption 1       | 12px   | 400    | 1.35        | +0.03px         |
| Caption 2       | 11px   | 400    | 1.3         | +0.06px         |

**Rules:**

- Negative letter spacing on large text (`-0.3px` to `-0.5px`), Apple condenses large type
- Positive letter spacing on tiny text (captions, labels), Apple opens small type
- Never use `font-weight: 300`, Apple does not use light weight in UI
- Sentence case everywhere. Never ALL CAPS in body text.

---

## 6. Color, system palette and vibrancy

### Semantic colors (auto dark/light via CSS)

```css
:root {
  --label:            rgba(0,   0,   0,   1.0);
  --secondary-label:  rgba(60,  60,  67,  0.6);
  --tertiary-label:   rgba(60,  60,  67,  0.3);
  --quaternary-label: rgba(60,  60,  67,  0.18);
  --separator:        rgba(60,  60,  67,  0.29);
  --bg-primary:       rgba(255, 255, 255, 1);
  --bg-secondary:     rgba(242, 242, 247, 1);   /* iOS grouped bg */
  --bg-tertiary:      rgba(255, 255, 255, 1);
  --fill-primary:     rgba(120, 120, 128, 0.2);
  --fill-secondary:   rgba(120, 120, 128, 0.16);
  --fill-tertiary:    rgba(118, 118, 128, 0.12);
  --fill-quaternary:  rgba(116, 116, 128, 0.08);
}

@media (prefers-color-scheme: dark) {
  :root {
    --label:            rgba(255, 255, 255, 1.0);
    --secondary-label:  rgba(235, 235, 245, 0.6);
    --tertiary-label:   rgba(235, 235, 245, 0.3);
    --quaternary-label: rgba(235, 235, 245, 0.18);
    --separator:        rgba(84,  84,  88,  0.65);
    --bg-primary:       rgba(0,   0,   0,   1);
    --bg-secondary:     rgba(28,  28,  30,  1);
    --bg-tertiary:      rgba(44,  44,  46,  1);
    --fill-primary:     rgba(120, 120, 128, 0.36);
    --fill-secondary:   rgba(120, 120, 128, 0.32);
    --fill-tertiary:    rgba(118, 118, 128, 0.24);
    --fill-quaternary:  rgba(116, 116, 128, 0.18);
  }
}
```

### Apple system accent colors (exact hex)

```css
--apple-blue:   #007AFF;
--apple-green:  #34C759;
--apple-indigo: #5856D6;
--apple-orange: #FF9500;
--apple-pink:   #FF2D55;
--apple-purple: #AF52DE;
--apple-red:    #FF3B30;
--apple-teal:   #5AC8FA;
--apple-yellow: #FFCC00;
--apple-mint:   #00C7BE;
--apple-cyan:   #32ADE6;
--apple-brown:  #A2845E;
```

### Vibrancy, how Apple applies color over glass

Never use solid system accent colors over a glass background. Use their alpha versions:

```css
/* Blue tinted glass pill */
background: rgba(0, 122, 255, 0.15);
border: 1px solid rgba(0, 122, 255, 0.35);
color: #007AFF;

/* Green tinted glass badge */
background: rgba(52, 199, 89, 0.15);
border: 1px solid rgba(52, 199, 89, 0.30);
color: #34C759;
```

Alpha values for fills: 0.08 (ultra-thin) → 0.12 (thin) → 0.18 (regular) → 0.24 (thick)

---

## 7. Shadows, the Apple shadow vocabulary

Apple shadows are subtle and directional, never harsh.

```css
/* Elevation 0, flush, no shadow */
box-shadow: none;

/* Elevation 1, cards, list rows */
box-shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.06);

/* Elevation 2, popovers, context menus */
box-shadow: 0 4px 16px rgba(0,0,0,0.12), 0 2px 4px rgba(0,0,0,0.08);

/* Elevation 3, modals, sheets */
box-shadow: 0 10px 40px rgba(0,0,0,0.18), 0 4px 12px rgba(0,0,0,0.10);

/* Elevation 4, floating panels, widgets */
box-shadow: 0 20px 60px rgba(0,0,0,0.22), 0 8px 20px rgba(0,0,0,0.12);

/* Colored shadow, Apple tints shadows with the element's color */
/* e.g. blue button: */
box-shadow: 0 6px 20px rgba(0, 122, 255, 0.38);
```

---

## 8. Component patterns

### Toggle (iOS Switch)

```html
<button class="ios-toggle" role="switch" aria-checked="true">
  <span class="ios-thumb"></span>
</button>

<style>
.ios-toggle {
  width: 51px; height: 31px; border-radius: 999px;
  background: var(--apple-green); border: none; cursor: pointer;
  position: relative; padding: 0;
  transition: background 300ms ease;
}
.ios-toggle[aria-checked="false"] { background: var(--fill-primary); }
.ios-thumb {
  position: absolute; top: 2px; left: 2px;
  width: 27px; height: 27px; border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 6px rgba(0,0,0,0.22), 0 1px 2px rgba(0,0,0,0.14);
  transition: transform 380ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.ios-toggle[aria-checked="true"] .ios-thumb { transform: translateX(20px); }
</style>
```

### Segmented Control

```html
<div class="seg-ctrl" role="tablist">
  <button class="seg-opt active" role="tab">Day</button>
  <button class="seg-opt" role="tab">Week</button>
  <button class="seg-opt" role="tab">Month</button>
</div>

<style>
.seg-ctrl {
  display: inline-flex;
  background: var(--fill-secondary);
  border-radius: var(--r-sm); padding: 2px; gap: 2px;
}
.seg-opt {
  padding: 6px 16px; border-radius: calc(var(--r-sm) - 2px); border: none;
  font-size: 13px; font-weight: 500; cursor: pointer; background: transparent;
  color: var(--label); transition: all 320ms var(--spring-snappy);
}
.seg-opt.active {
  background: #fff; color: var(--label);
  box-shadow: 0 2px 8px rgba(0,0,0,0.10), 0 1px 2px rgba(0,0,0,0.06);
}
</style>
```

### Context Menu (popover)

```css
.ctx-menu {
  background: rgba(255,255,255,0.82);
  backdrop-filter: blur(40px) saturate(200%);
  -webkit-backdrop-filter: blur(40px) saturate(200%);
  border-radius: var(--r-md);
  border: 0.5px solid rgba(0,0,0,0.08);
  box-shadow: 0 20px 60px rgba(0,0,0,0.20), 0 4px 12px rgba(0,0,0,0.10);
  padding: 6px;
  min-width: 180px;
  animation: ctxPopIn 240ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes ctxPopIn {
  from { opacity: 0; transform: scale(0.86) translateY(-6px); }
  to   { opacity: 1; transform: scale(1)    translateY(0);    }
}
.ctx-row {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; border-radius: calc(var(--r-md) - 4px);
  font-size: 13px; cursor: pointer;
  transition: background 120ms ease;
}
.ctx-row:hover        { background: rgba(0, 122, 255, 0.10); color: var(--apple-blue); }
.ctx-row.destructive  { color: var(--apple-red); }
.ctx-row.destructive:hover { background: rgba(255, 59, 48, 0.10); }
.ctx-sep { height: 0.5px; background: var(--separator); margin: 4px 0; }
```

### Dynamic Island pattern

```css
.dynamic-island {
  background: #000; border-radius: 999px;
  padding: 10px 18px;
  display: inline-flex; align-items: center; gap: 10px;
  box-shadow: 0 8px 32px rgba(0,0,0,0.5);
  transition:
    padding    440ms cubic-bezier(0.16, 1, 0.3, 1),
    min-width  440ms cubic-bezier(0.16, 1, 0.3, 1),
    border-radius 440ms cubic-bezier(0.16, 1, 0.3, 1);
  cursor: pointer; min-width: 120px;
}
.dynamic-island:hover {
  padding: 16px 28px;
  min-width: 300px;
  border-radius: 38px;
}
```

### Notification / Alert banner

```css
.banner {
  background: rgba(255,255,255,0.82);
  backdrop-filter: blur(28px) saturate(180%);
  border-radius: var(--r-lg);
  border: 0.5px solid rgba(0,0,0,0.08);
  box-shadow: 0 16px 48px rgba(0,0,0,0.18);
  padding: 12px 16px;
  display: flex; gap: 12px; align-items: flex-start;
  animation: bannerSlide 500ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes bannerSlide {
  from { opacity: 0; transform: translateY(-20px) scale(0.95); }
  to   { opacity: 1; transform: translateY(0)      scale(1);   }
}
```

### SF Symbol-style icon container

Apple's icons sit in rounded square containers ("app icon shape" for apps, `--r-sm` for UI actions).

```html
<div class="sf-icon-wrap" style="--accent: #007AFF;">
  <i class="ti ti-message"></i>
</div>

<style>
.sf-icon-wrap {
  width: 36px; height: 36px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  display: flex; align-items: center; justify-content: center;
  color: var(--accent); font-size: 18px;
}
</style>
```

---

## 9. Interaction recipe, complete button

A fully correct Apple-style button covers all five states:

```html
<button class="apple-cta">Continue</button>

<style>
.apple-cta {
  padding: 14px 28px; border-radius: var(--r-sm); border: none;
  font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif;
  font-size: 17px; font-weight: 600; cursor: pointer;
  background: var(--apple-blue); color: #fff;
  box-shadow: 0 4px 14px rgba(0, 122, 255, 0.38);
  position: relative; overflow: hidden;
  /* specular highlight */
  background-image: linear-gradient(180deg,
    rgba(255,255,255,0.18) 0%,
    rgba(255,255,255,0.00) 60%);
  transition:
    transform   340ms cubic-bezier(0.34, 1.56, 0.64, 1),
    box-shadow  200ms ease,
    filter      120ms ease;
}
/* hover, slight lift */
.apple-cta:hover {
  transform: scale(1.03) translateY(-2px);
  box-shadow: 0 8px 24px rgba(0, 122, 255, 0.44);
}
/* press, haptic compression */
.apple-cta:active {
  transform: scale(0.94);
  filter: brightness(0.88);
  transition-duration: 110ms;
}
/* focus, accessibility ring */
.apple-cta:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.45);
}
/* ripple layer */
.apple-cta .ripple {
  position: absolute; border-radius: 50%;
  background: rgba(255,255,255,0.3);
  transform: scale(0);
  animation: ripple 550ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
  pointer-events: none;
}
@keyframes ripple {
  to { transform: scale(4); opacity: 0; }
}
</style>

<script>
document.querySelector('.apple-cta').addEventListener('click', function(e) {
  const r = this.getBoundingClientRect();
  const d = Math.max(r.width, r.height);
  const rip = document.createElement('div');
  rip.className = 'ripple';
  rip.style.cssText = `width:${d}px;height:${d}px;left:${e.clientX-r.left-d/2}px;top:${e.clientY-r.top-d/2}px`;
  this.appendChild(rip);
  setTimeout(() => rip.remove(), 600);
});
</script>
```

---

## 10. Layout principles from Apple HIG

### Spacing, 8pt grid (Apple's base unit)

Apple designs on an 8-point grid. All spacing is multiples of 8.

```
4px, within a single element (icon gap, tight pair)
8px, between related items (label + input)
12px, compact list row padding
16px, standard content padding (HIG minimum)
20px, section breathing room
24px, between major components
32px, section separation
44px, minimum touch target (absolute requirement)
```

### Content width

- iPhone SE width: 320px minimum
- Standard iPhone: 390px
- iPad: use `max-width: 768px` with centered content
- macOS sidebar: 220-260px fixed
- macOS content: fluid with `max-width: 960px`

### The safe area, always respect it

```css
padding-top:    env(safe-area-inset-top);
padding-bottom: max(env(safe-area-inset-bottom), 20px);
padding-left:   env(safe-area-inset-left);
padding-right:  env(safe-area-inset-right);
```

---

## 11. The five things that immediately break Apple feel

Avoid these at all costs:

1. **Hard borders instead of glass edges**, `border: 1px solid #ccc` is wrong. Borders must be `rgba(255,255,255,0.4-0.6)` on glass or `var(--separator)` on solid surfaces.

2. **Linear/ease animation**, `transition: all 0.3s ease` is wrong for interactions. Springs only.

3. **Wrong corner radius**, `border-radius: 4px` on a card is wrong. Cards are `--r-lg` (20px). Buttons are `--r-sm` (12px). Pills are `999px`.

4. **Opaque backgrounds where glass should be**, any element that floats over content (modal, popover, sheet, toolbar, context menu) must be translucent glass, not `background: white`.

5. **No specular highlight**, any glass element without a `::before` or `::after` top-edge gradient (`rgba(255,255,255,0.25-0.35)`) looks flat and cheap. The highlight is what makes it look like glass vs. just a colored div.

---

## 12. Dark mode, mandatory

Apple dark mode is not just "invert the colors." Dark glass is darker-tinted, not lighter. Follow these rules:

- Light glass → `rgba(255,255,255, 0.55-0.72)` tint
- Dark glass  → `rgba(18,18,28, 0.65-0.78)` tint, never pure black
- Dark mode borders → `rgba(255,255,255, 0.08-0.14)`, very subtle white edge
- Dark mode text hierarchy: primary `rgba(255,255,255,1.0)`, secondary `rgba(235,235,245,0.6)`, tertiary `rgba(235,235,245,0.3)`
- Colored accents stay the same hex, Apple does not shift accent colors in dark mode
- Dark mode shadows are invisible on dark bg, increase opacity to compensate: `rgba(0,0,0,0.5-0.6)`

Always implement both:

```css
@media (prefers-color-scheme: light) { /* light rules */ }
@media (prefers-color-scheme: dark)  { /* dark rules  */ }
```

---

## 13. Complete CSS variable reference

Paste this into any Apple-style project as the foundation:

```css
:root {
  /* Spring curves */
  --spring-default: cubic-bezier(0.34, 1.56, 0.64, 1);
  --spring-snappy:  cubic-bezier(0.36, 1.4,  0.64, 1);
  --spring-gentle:  cubic-bezier(0.16, 1.0,  0.3,  1);
  --spring-bounce:  cubic-bezier(0.22, 1.6,  0.36, 1);

  /* Corner radii */
  --r-xs: 8px; --r-sm: 12px; --r-md: 16px; --r-lg: 20px;
  --r-xl: 28px; --r-2xl: 36px; --r-pill: 999px;

  /* System accent colors */
  --blue:   #007AFF; --green:  #34C759; --indigo: #5856D6;
  --orange: #FF9500; --pink:   #FF2D55; --purple: #AF52DE;
  --red:    #FF3B30; --teal:   #5AC8FA; --yellow: #FFCC00;
  --mint:   #00C7BE; --cyan:   #32ADE6; --brown:  #A2845E;

  /* Typography */
  --font-sf: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Inter', sans-serif;

  /* Touch target */
  --touch-target: 44px;

  /* 8pt grid */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px;
  --s5: 20px; --s6: 24px; --s8: 32px; --s11: 44px;
}
```

---

## 14. Self-check before shipping

Before calling a component done, verify:

- [ ] Every floating element uses `backdrop-filter`, no opaque backgrounds on overlays
- [ ] Every interactive element has hover + press states with spring animation
- [ ] Every glass surface has a specular `::after` top-edge highlight
- [ ] Corner radii come from the scale, no arbitrary values
- [ ] Typography uses negative letter spacing on large text, positive on captions
- [ ] Touch targets are ≥ 44px
- [ ] Dark mode is implemented and tested
- [ ] `prefers-reduced-motion` disables all animations
- [ ] Borders on glass use `rgba(255,255,255, …)`, never solid hex colors
- [ ] Spring curves, not `ease`, not `ease-in-out`

