Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions apps/web/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,70 @@ infisical export \
--projectId=87dad7b5-72a6-4791-9228-b3b86b169db1 \
--path="/web"
```

## Design System

All visual tokens live in `src/styles.css` inside the `@theme` block. Never use hardcoded hex values in components — always reference a token.

### Color tokens

| Token | Value | Use for |
|---|---|---|
| `--color-page` | `#F2F3F4` | Page/canvas background (`bg-page`) |
| `--color-surface` | `#ffffff` | Card, panel, modal backgrounds (`bg-surface`) |
| `--color-surface-subtle` | `#fafaf9` | Muted surface variants (`bg-surface-subtle`) |
| `--color-fg` | `#1c1917` | Primary text (`text-fg`) |
| `--color-fg-muted` | `#57534e` | Secondary/body text (`text-fg-muted`) |
| `--color-fg-subtle` | `#a8a29e` | Placeholder, disabled, icons (`text-fg-subtle`) |
| `--color-border` | `#CBC8BD` | Default borders, dividers (`border-border`) |
| `--color-border-subtle` | `#f5f5f5` | Hairline/structural borders (`border-border-subtle`) |
| `--color-brand` | `#78716c` | Brand accent — use sparingly (`bg-brand`, `text-brand`) |
| `--color-brand-dark` | `#57534e` | CTA button gradient end, emphasis (`bg-brand-dark`) |

### Shadow tokens

| Token | Use for |
|---|---|
| `--shadow-ring` | 1px outline border effect — prefer over `border` when stacking borders |
| `--shadow-ring-subtle` | Same, using the subtle border color |

The `.border-shadow` utility class applies `--shadow-ring` as a convenience.

### Typography

- **Display / logo**: `font-serif` (Fraunces) — headings, logo wordmark, editorial emphasis
- **Body / UI text**: `font-sans` (Geist) — all body copy, labels, nav links
- **Code / buttons**: `font-mono` (Geist Mono) — code, button labels, monospaced UI

### CTA button pattern

Primary CTA always uses the warm gradient:

```tsx
"bg-linear-to-t from-brand-dark to-brand rounded-full text-white"
```

Secondary / ghost uses surface + border:

```tsx
"bg-surface border border-border rounded-lg text-fg-muted"
```

## Component structure

Target folder layout. New components must go in the right folder — do not add to the flat root of `components/`.

```
src/components/
layout/ # Header, Footer, Sidebar, SidebarNavigation
ui/ # Primitive brand components: Button, Badge, Card shells
sections/ # Page-level marketing sections (LogoCloud, CtaCard, GithubStars…)
mdx/ # MDX renderer components
notepad/ # Notepad feature
transcription/ # Transcription feature
admin/ # Admin tooling
```

Existing flat-root files are being migrated incrementally. When touching a file, move it to the right folder as part of that PR.

For full brand reference, see `BRAND.md`.
180 changes: 180 additions & 0 deletions apps/web/BRAND.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Char Brand

This document is the single source of truth for Char's visual identity on the web. All tokens referenced here are defined in `src/styles.css` and available as Tailwind utilities.

---

## Color

The palette is warm neutral — rooted in stone and off-white. One moment of warmth (the brand gradient on CTAs). Everything else recedes.

### Palette

| Role | Token | Hex | Tailwind class |
|---|---|---|---|
| Page background | `--color-page` | `#F2F3F4` | `bg-page` |
| Surface | `--color-surface` | `#ffffff` | `bg-surface` |
| Surface muted | `--color-surface-subtle` | `#fafaf9` | `bg-surface-subtle` |
| Primary text | `--color-fg` | `#1c1917` | `text-fg` |
| Secondary text | `--color-fg-muted` | `#57534e` | `text-fg-muted` |
| Placeholder / disabled | `--color-fg-subtle` | `#a8a29e` | `text-fg-subtle` |
| Default border | `--color-border` | `#CBC8BD` | `border-border` |
| Hairline border | `--color-border-subtle` | `#f5f5f5` | `border-border-subtle` |
| Brand accent | `--color-brand` | `#78716c` | `bg-brand` / `text-brand` |
| Brand dark | `--color-brand-dark` | `#57534e` | `bg-brand-dark` |

### Usage rules

- Never introduce a color outside this palette without updating the token set first.
- `color-brand` and `color-brand-dark` are the only "warm accent" moments. Use them exclusively for primary CTAs and interactive focus states.
- Tailwind's `neutral-*` and `stone-*` scales are available as fallback but semantic tokens above take precedence for any brand-facing UI.

---

## Typography

Three typefaces, each with a distinct role. Do not mix roles.

| Face | Font | Variable | Role |
|---|---|---|---|
| Serif | Fraunces | `--font-serif` | Wordmark, editorial pull-quotes |
| Sans | Geist | `--font-sans` | All body copy, UI labels, navigation |
| Mono | Geist Mono | `--font-mono` | Button labels, display headings, code, technical UI |

### Type scale principles

- **Heading hierarchy**: h1/h2 use `font-mono` by default (per base styles). Serif is used selectively for editorial or brand moments, not for all headings.
- **Weight contrast**: Pair heavy display weight (`font-semibold` / `font-bold`) with light body weight (`font-normal`). Never use two heavy weights adjacently.
- **Letter spacing**: Tight (`tracking-tight`) on large display type. Open (`tracking-wider`) on all-caps labels and category tags.
- **Minimum readable size**: 14px for body, 12px only for all-caps labels or metadata.

---

## Borders & Shadows

| Token | Value | Class | Use |
|---|---|---|---|
| `--shadow-ring` | `0 0 0 1px #CBC8BD` | `.border-shadow` | Default card/panel outline |
| `--shadow-ring-subtle` | `0 0 0 1px #f5f5f5` | — | Hairline structural outlines |

**Prefer `shadow-ring` over CSS `border`** when an element already has box-shadow — avoids double-border stacking issues.

Border radius conventions:
- `rounded-xs` — tight UI elements (chips, small badges)
- `rounded-md` — cards, inputs, dropdowns
- `rounded-lg` — modals, large cards
- `rounded-full` — pill buttons, avatars, tags

---

## Spacing

The layout uses a base-4 spacing rhythm. Key structural values:

| Purpose | Value |
|---|---|
| Header height | `69px` / `h-17.25` |
| Content max-width | `max-w-6xl` |
| Wide breakpoint | `72rem` (`laptop`) |
| Section vertical padding | `py-12` (mobile) / `py-16` (desktop) |
| Card internal padding | `p-4` (compact) / `p-8` (feature) |

---

## Components

### Primary CTA button

The main action. Warm gradient, pill shape, scales on hover.

```tsx
<button className="flex h-9 items-center rounded-full bg-linear-to-t from-brand-dark to-brand px-4 text-sm text-white shadow-md transition-all hover:scale-[102%] hover:shadow-lg active:scale-[98%]">
Download for free
</button>
```

### Secondary / ghost button

Outline style, no fill. Used for secondary actions.

```tsx
<button className="flex h-9 items-center rounded-lg border border-border bg-surface px-4 text-sm text-fg-muted transition-colors hover:bg-surface-subtle">
Get started
</button>
```

### Nav link

Text-only, dotted underline on hover.

```tsx
<a className="text-sm text-fg-muted decoration-dotted transition-colors hover:text-fg hover:underline">
Link
</a>
```

### Section label (category tag)

All-caps mono, wide tracking, muted.

```tsx
<span className="font-mono text-xs font-semibold tracking-wider text-fg-subtle uppercase">
Features
</span>
```

### Card

No heavy shadow. Border ring or hairline border, surface background.

```tsx
<div className="rounded-md border border-border bg-surface p-4">
</div>
```

Or with `shadow-ring` instead of `border`:

```tsx
<div className="border-shadow rounded-md bg-surface p-4">
</div>
```

---

## Logo

- **Wordmark**: "Char" in `font-serif` (Fraunces), `text-2xl`, `font-semibold`.
- **Do not** render the wordmark in `font-sans` or `font-mono`.
- **Scale animation** on hover: `hover:scale-105 transition-transform` is the only permitted motion on the logo.

---

## Motion

- Scale micro-interactions: `hover:scale-[102%] active:scale-[98%]` — used on all interactive cards and CTA buttons.
- Opacity transitions: `transition-opacity duration-200` — used for fade in/out on dynamic text.
- Page-level slide-in: `animate-in slide-in-from-top duration-300` — used for mobile menu only.
- No bounce, no spring, no decorative keyframes on brand UI.

---

## Component folder structure

```
src/components/
layout/ # Header, Footer, Sidebar — structural chrome
ui/ # Primitive, stateless brand components (Button, Badge, Card)
sections/ # Composed page sections (LogoCloud, CtaCard, GithubStars)
mdx/ # MDX renderer overrides
notepad/ # Notepad product feature
transcription/ # Transcription product feature
admin/ # Internal admin tooling
```

When creating a new component, ask:
- Is it a stateless visual primitive? → `ui/`
- Is it a full page section? → `sections/`
- Is it structural layout chrome? → `layout/`
- Is it tied to a specific product feature? → that feature's folder
4 changes: 2 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"private": true,
"type": "module",
"scripts": {
"dev": "VITE_APP_URL=\"http://localhost:3000\" VITE_API_URL=\"http://localhost:3001\" dotenvx run --ignore MISSING_ENV_FILE -f ../../.env.supabase -f .env -- vite dev --port 3000",
"build": "vite build && cp public/sitemap.xml dist/client/sitemap.xml && pagefind --site ./dist/client",
"dev": "NODE_OPTIONS='--max-old-space-size=8192' VITE_APP_URL=\"http://localhost:3000\" VITE_API_URL=\"http://localhost:3001\" dotenvx run --ignore MISSING_ENV_FILE -f ../../.env.supabase -f .env -- vite dev --port 3000",
"build": "NODE_OPTIONS='--max-old-space-size=8192' vite build && cp public/sitemap.xml dist/client/sitemap.xml && pagefind --site ./dist/client",
"serve": "vite preview",
"test": "playwright test",
"typecheck": "CI=true pnpm -F @hypr/web build && tsc --project tsconfig.json --noEmit",
Expand Down
Loading
Loading