# Ulties Design System

**Version**: 1.0 | **Last Updated**: 2026-12

This document is the canonical reference for any tool, LLM agent, or contributor
building Ulties-branded experiences. It maps directly to the tokens in
`/app/frontend/src/index.css` and the implemented components under
`/app/frontend/src/components/`.

---

## Table of Contents

1. [Brand Voice](#1-brand-voice)
2. [Logo](#2-logo)
3. [Colors](#3-colors)
4. [Typography](#4-typography)
5. [Spacing & Layout](#5-spacing--layout)
6. [Border & Radius](#6-border--radius)
7. [Shadows, Effects & Motion](#7-shadows-effects--motion)
8. [Buttons](#8-buttons)
9. [Form Elements](#9-form-elements)
10. [Cards](#10-cards)
11. [Sections & Page Patterns](#11-sections--page-patterns)
12. [Responsive Breakpoints](#12-responsive-breakpoints)
13. [CSS Variables](#13-css-variables)
14. [Quick Reference](#14-quick-reference)
15. [Folder Structure](#15-folder-structure)

---

## 1. Brand Voice

| Trait        | We are                                          | We are not                                  |
| ------------ | ----------------------------------------------- | ------------------------------------------- |
| Tone         | Calm, direct, technically literate              | Salesy, hype, slogan-heavy                  |
| Sentence     | Short. Confident. One claim at a time.          | Run-on. Hedged. Buzzword-stacked.           |
| Person       | First-person plural (“we ship”, “we built”)     | Royal “the team”, third-person agency speak |
| Capitalisation | Sentence case for headings                    | Title Case, ALL CAPS in copy                |
| Punctuation  | Em-dash for emphasis (`—`), Oxford commas       | Exclamation marks, ellipses for drama       |

Headlines and section openers may use *occasional* italic emphasis on a single
word for cadence (e.g. *recently.*, *the trenches.*).

---

## 2. Logo

The Ulties mark lives in `/app/frontend/public/logo.svg` and is exposed via the
React component `src/components/Logo.jsx` with three variants:

| Variant     | Use                                              |
| ----------- | ------------------------------------------------ |
| `mark`      | Favicons, 16–24 px UI, monogram-only contexts    |
| `wordmark`  | Nav bars, headers, 40–64 px contexts             |
| `full`      | Footer, brand moments — includes "tech solutions"|

Logo fill = `currentColor`, so a single `<Logo className="text-primary-x" />`
flips automatically with the dark / light theme. **Never** apply gradients,
rainbow fills, drop-shadows, or rotate / stretch the mark. See `/brand` and
`/logo` for the full visual guide.

---

## 3. Colors

Ulties is a **dark-first** palette. Every visual decision should pass in dark
mode before being checked in light. Tokens are CSS custom properties on `:root`
(dark) and `html.light` (light).

### Semantic tokens

| Token              | Dark        | Light       | Use                                  |
| ------------------ | ----------- | ----------- | ------------------------------------ |
| `--canvas`         | `#050505`   | `#fafafa`   | Page background                      |
| `--surface`        | `#070707`   | `#f4f4f5`   | Section "raised" background          |
| `--surface-2`      | `#0a0a0a`   | `#ffffff`   | Card / inline image background       |
| `--text-primary`   | `#fafafa`   | `#0a0a0a`   | Headlines, primary UI, icons         |
| `--text-secondary` | `#a1a1aa`   | `#3f3f46`   | Body copy                            |
| `--text-muted`     | `#52525b`   | `#71717a`   | Labels, eyebrows, captions           |
| `--hairline`       | `rgba(255,255,255,.06)` | `rgba(0,0,0,.07)` | Borders, dividers       |
| `--hairline-strong`| `rgba(255,255,255,.12)` | `rgba(0,0,0,.18)` | Form borders, hover     |
| `--glass`          | `rgba(255,255,255,.02)` | `rgba(0,0,0,.03)` | Glass cards            |
| `--glass-hover`    | `rgba(255,255,255,.06)` | `rgba(0,0,0,.06)` | Glass hover state      |

### Accent / signal

| Use            | Hex         | Notes                              |
| -------------- | ----------- | ---------------------------------- |
| Signal green   | `#34d399`   | "Available" dot, "Systems online"  |
| Glitch cyan    | `#22d3ee`   | Motion treatments only             |
| Glitch magenta | `#ec4899`   | Motion treatments only             |
| Brand purple   | `#a78bfa`   | Hero rim light (Hero 4)            |

Never use **#7c3aed → #ec4899 (the AI-slop purple→pink gradient)** for surface
colour. Brand purple appears only as a 3D rim light, never as a solid.

### Tailwind utility classes

The semantic tokens are exposed via Tailwind utility classes (declared in
`index.css`):

```html
<div class="bg-canvas text-primary-x">…</div>
<div class="bg-surface border border-hairline">…</div>
<button class="bg-glass hover-glass border border-hairline-strong">…</button>
<span class="text-secondary-x">…</span>
<span class="text-muted-x">…</span>
```

---

## 4. Typography

Ulties uses **three** typefaces. No swaps.

| Family            | Class            | Use                                           |
| ----------------- | ---------------- | --------------------------------------------- |
| Cabinet Grotesk   | `.font-display`  | All H1–H3, brand wordmarks                    |
| Manrope           | (body default)   | Paragraphs, UI                                |
| JetBrains Mono    | `.font-mono-x`   | Eyebrows, all-caps labels, footnotes, kbd     |

Loaded via `<link>` in `index.css` (Fontshare + Google Fonts). Do not add a
fourth typeface.

### Tracking & casing

* Display headings: `tracking-[-0.03em]` → `tracking-[-0.05em]` for hero
* Body: tracking normal
* Eyebrows / labels: `tracking-[0.25em]` `uppercase` mono
* Numerals: `font-feature-settings: "ss01" on, "cv11" on` (set globally)

### Hierarchy

| Level          | Tailwind                                                         |
| -------------- | ---------------------------------------------------------------- |
| Hero H1        | `font-display text-[14vw] md:text-[9.5rem] lg:text-[11rem] font-bold tracking-[-0.05em] leading-[0.88]` |
| Page H1        | `font-display text-5xl md:text-7xl lg:text-9xl font-bold tracking-[-0.045em] leading-[0.9]` |
| Section H2     | `font-display text-5xl md:text-7xl font-medium tracking-tight leading-[0.95]` |
| Sub-section H3 | `font-display text-3xl md:text-4xl font-medium tracking-tight`   |
| Body           | `text-base md:text-lg text-secondary-x leading-relaxed`          |
| Eyebrow        | `text-[11px] tracking-[0.25em] uppercase text-muted-x font-mono-x` |

Always wrap headlines in `text-balance` for human linebreaks.

---

## 5. Spacing & Layout

| Token | Pixels | Use                                            |
| ----- | ------ | ---------------------------------------------- |
| 1     | 4 px   | Tight icon gaps                                |
| 2     | 8 px   | Micro padding                                  |
| 3     | 12 px  | Button padding                                 |
| 4     | 16 px  | Default                                        |
| 6     | 24 px  | Card padding / grid gap                        |
| 8     | 32 px  | Card padding (large)                           |
| 12    | 48 px  | Block spacing                                  |
| 16    | 64 px  | Section header → content                       |
| 24    | 96 px  | Section vertical padding (mobile)              |
| 28/32 | 112–128 px | Section vertical padding (desktop)         |

Ulties uses **deliberately generous** vertical rhythm — `py-24 md:py-32` is the
default for primary sections; `py-28 md:py-40` for hero-adjacent sections.

### Container

```css
.container, .max-w-7xl {
  max-width: 80rem;        /* 1280 px */
  margin-inline: auto;
  padding-inline: 1.5rem;  /* px-6 */
}
@media (min-width: 768px) {
  .container { padding-inline: 2.5rem; } /* md:px-10 */
}
```

### Grid

* 12-column responsive grid on every primary section (`grid md:grid-cols-12`)
* Service / work bento uses asymmetric spans (`md:col-span-7`, `md:col-span-5`)
* Card grids: `grid md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-10`

---

## 6. Border & Radius

| Token  | Pixels | Use                                 |
| ------ | ------ | ----------------------------------- |
| `lg`   | 8 px   | Inputs, kbd                         |
| `xl`   | 12 px  | Tags, small buttons                 |
| `2xl`  | 16 px  | Cards, panels, glass surfaces       |
| `3xl`  | 24 px  | Large hero containers (rare)        |
| `full` | 9999px | Buttons, pills, chips, avatars, CTAs|

### Border conventions

* `border border-hairline` everywhere (1 px hair lines)
* `border-hairline-strong` for inputs and hovered cards
* No double-borders. No shadows + borders together on the same element.

---

## 7. Shadows, Effects & Motion

### Shadows

Shadows are used sparingly — depth comes from layered surfaces, glass and grain.

```css
/* Form glow */
.glow-ring {
  box-shadow: 0 0 60px -20px rgba(255,255,255,.08),
              inset 0 1px 0 rgba(255,255,255,.06);
}

/* Light-mode glow */
html.light .glow-ring {
  box-shadow: 0 30px 60px -30px rgba(0,0,0,.15),
              inset 0 1px 0 rgba(255,255,255,.6);
}
```

### Grain & noise

A 0.06-alpha SVG noise overlay (`.grain::after`) sits on hero sections and
brand-heavy pages. Mix-blend-mode is `overlay` in dark, `multiply` in light.

### Glass

`bg-glass backdrop-blur-2xl border border-hairline` — used on nav, cards,
floating dock, and contact form.

### Motion language

* Easing: `cubic-bezier(0.22, 1, 0.36, 1)` (custom "expo-out" feel)
* Entrance reveals: `initial={{ y: 30, opacity: 0 }}` → `whileInView={{ y: 0, opacity: 1 }}`
* Duration: 0.6 – 0.9 s for entrance, ≤ 0.35 s for hover
* Stagger: 0.06 – 0.08 s between siblings
* Library: `framer-motion`. CSS-only for marquees and shimmer.
* Smooth scroll: `lenis` (`/hooks/useLenis.js`)
* Never animate `all`. Always target specific properties (opacity, transform).

---

## 8. Buttons

All buttons are **pill-shaped** (`rounded-full`).

### Primary CTA

```html
<button
  class="inline-flex items-center gap-2 rounded-full px-6 py-3 text-sm font-medium hover:opacity-90 transition-opacity"
  style="background: var(--text-primary); color: var(--canvas);"
>
  Start a project →
</button>
```

The `--text-primary` / `--canvas` swap means the same markup renders as
*white-on-black* in dark mode and *black-on-white* in light mode.

### Secondary / glass

```html
<button class="inline-flex items-center gap-2 rounded-full border border-hairline-strong bg-glass backdrop-blur-xl px-6 py-3 text-sm text-primary-x hover-glass transition-colors">
  View work →
</button>
```

### Tertiary / link

Plain `text-secondary-x hover:text-primary-x transition-colors`.

### Sizes

| Size    | Padding             | Text  |
| ------- | ------------------- | ----- |
| Default | `px-6 py-3`         | `text-sm` |
| Small   | `px-4 py-2`         | `text-xs` |
| Icon    | `h-9 w-9`           | —     |

---

## 9. Form Elements

```css
.input-x {
  width: 100%;
  background: var(--glass);
  border: 1px solid var(--hairline-strong);
  border-radius: 12px;
  padding: 12px 14px;
  color: var(--text-primary);
  font-size: 14px;
  outline: none;
  transition: border-color .2s, background .2s;
}
.input-x::placeholder { color: var(--text-muted); }
.input-x:focus {
  border-color: var(--text-primary);
  background: var(--glass-hover);
}
```

* Labels: `text-[11px] tracking-[0.22em] uppercase text-muted-x font-mono-x mb-2`
* Required marker: small `*` in `text-primary-x`
* Validation: toast via `sonner` (located at `components/ui/sonner.tsx`)
* Success state: replaces the form body with a centered ✓ disc on `--text-primary`

---

## 10. Cards

Every card uses **glass on hairline** by default:

```html
<article class="rounded-2xl border border-hairline bg-glass backdrop-blur-2xl p-8 md:p-10 hover:border-hairline-strong transition-colors">
  <div class="text-[11px] tracking-[0.25em] uppercase text-muted-x font-mono-x mb-8">
    01 — Eyebrow
  </div>
  <h3 class="font-display text-3xl md:text-4xl font-medium tracking-tight text-primary-x">
    Card title
  </h3>
  <p class="mt-5 text-secondary-x leading-relaxed">…</p>
</article>
```

Image cards use `bg-surface-2`. Hover lifts opacity 0.85 → 1 and scales image
1.0 → 1.05 over 700 ms.

---

## 11. Sections & Page Patterns

Every primary page follows the same vertical anatomy:

1. **PageHero** (`components/PageHero.jsx`) — eyebrow + word-staggered H1 + kicker, faint top orb, `grain` overlay
2. **Content sections** — `py-24 md:py-32`, alternate `bg-canvas` ↔ `bg-surface` ↔ `bg-canvas` for rhythm
3. **CallToAction** (`components/CallToAction.jsx`) — final centered CTA before footer
4. **Footer** — 4-column with Studio / Resources / Elsewhere

Every page is wrapped in `<PageShell>` which injects Navigation, Footer,
CookieBanner, Sonner toaster, and Lenis smooth scroll.

Hero pages additionally inject a Three.js `<Canvas>` slot via `<Hero Canvas={…}>`.

---

## 12. Responsive Breakpoints

| Tailwind | Min   | Target          |
| -------- | ----- | --------------- |
| `sm`     | 640   | Large phones    |
| `md`     | 768   | Tablets         |
| `lg`     | 1024  | Small laptops   |
| `xl`     | 1280  | Desktops        |
| `2xl`    | 1536  | Wide desktops   |

* Design mobile-first.
* Tap targets ≥ 44 × 44 px.
* No horizontal scrollbars at 390 × 800 — verified by testing agent.

---

## 13. CSS Variables

Copy directly from `/app/frontend/src/index.css`:

```css
:root {
  color-scheme: dark;
  --canvas:           #050505;
  --surface:          #070707;
  --surface-2:        #0a0a0a;
  --hairline:         rgba(255,255,255,.06);
  --hairline-strong:  rgba(255,255,255,.12);
  --text-primary:     #fafafa;
  --text-secondary:   #a1a1aa;
  --text-muted:       #52525b;
  --glass:            rgba(255,255,255,.02);
  --glass-hover:      rgba(255,255,255,.06);
}

html.light {
  color-scheme: light;
  --canvas:           #fafafa;
  --surface:          #f4f4f5;
  --surface-2:        #ffffff;
  --hairline:         rgba(0,0,0,.07);
  --hairline-strong:  rgba(0,0,0,.18);
  --text-primary:     #0a0a0a;
  --text-secondary:   #3f3f46;
  --text-muted:       #71717a;
  --glass:            rgba(0,0,0,.03);
  --glass-hover:      rgba(0,0,0,.06);
}
```

---

## 14. Quick Reference

```css
/* Page wrapper */
.bg-canvas .text-primary-x

/* Section */
section.py-24.md\:py-32
  > .max-w-7xl.mx-auto.px-6.md\:px-10

/* Eyebrow */
.text-[11px].tracking-[0.25em].uppercase.text-muted-x.font-mono-x

/* H2 */
.font-display.text-5xl.md\:text-7xl.font-medium.tracking-tight.leading-[0.95]

/* Body */
.text-secondary-x.leading-relaxed

/* Glass card */
.rounded-2xl.border.border-hairline.bg-glass.backdrop-blur-2xl.p-8

/* CTA pill */
inline-flex items-center gap-2 rounded-full px-6 py-3 text-sm font-medium
style: background var(--text-primary); color var(--canvas);
```

### Don'ts

1. Don't use Inter / Roboto / system-ui as a primary face.
2. Don't introduce purple → pink gradients on any surface.
3. Don't use square buttons. Pills only.
4. Don't stretch / rotate / drop-shadow the logo.
5. Don't add a fourth typeface or a new shade of grey.
6. Don't animate `transition: all`. Target specific properties.
7. Don't ship a section without an eyebrow + H2 + kicker pattern (or a clear reason not to).
8. Don't write in title case. Sentence case only.

---

## 15. Folder Structure

```
/app/frontend/
├── public/
│   ├── logo.svg                ← brand mark (currentColor)
│   └── design.md               ← this file
├── src/
│   ├── index.css               ← all CSS variables + utility classes
│   ├── App.js                  ← routes (ThemeProvider + BrowserRouter)
│   ├── components/
│   │   ├── Logo.jsx            ← mark | wordmark | full variants
│   │   ├── Navigation.jsx
│   │   ├── Footer.jsx
│   │   ├── PageShell.jsx       ← layout wrapper for every page
│   │   ├── PageHero.jsx        ← reusable hero header
│   │   ├── CallToAction.jsx    ← reusable bottom CTA
│   │   ├── Hero.jsx            ← homepage hero w/ R3F canvas slot
│   │   ├── HeroCanvas.jsx      ← default Three.js scene
│   │   ├── heroes/             ← 5 alternate hero variants
│   │   ├── showcase/           ← interactive component bits
│   │   ├── ThemeToggle.jsx
│   │   ├── CookieBanner.jsx
│   │   └── ui/                 ← shadcn primitives (do not edit)
│   ├── contexts/ThemeContext.jsx
│   ├── data/
│   │   ├── cases.js            ← case-study content
│   │   └── posts.js            ← blog content
│   └── pages/
│       ├── Home.jsx
│       ├── ServicesPage.jsx, WorkPage.jsx, ProcessPage.jsx,
│       │   AboutPage.jsx, ContactPage.jsx, ComponentsPage.jsx,
│       │   BlogPage.jsx, BlogPost.jsx, SponsorPage.jsx,
│       │   CareersPage.jsx, LegalPages.jsx, LogoPage.jsx,
│       │   BrandPage.jsx, HeroVariants.jsx, CaseStudy.jsx
└── tailwind.config.js          ← darkMode: ["class"], shadcn tokens
```

---

**Source**: Tokens distilled from `/app/frontend/src/index.css`,
`tailwind.config.js`, and the implemented component library. Update this file
whenever you ship a token, type style, or component pattern.
