diff --git a/DESIGN.md b/DESIGN.md
index 400a051..440764a 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -22,6 +22,8 @@ not affiliated with Wix and must never present itself as such.
## 1. Principles
+0. **Mobile only.** One column, one container, thumb-reachable actions, no desktop layout and
+ no marketing site. See §4 — this constrains every other decision here.
1. **Blue means action.** `brand-500` is reserved for things the user can do. It is never
decoration, never a background wash for a whole section, never body text.
2. **Ink is near-black, not grey.** Headings sit at `ink-950` (`#000624`) — black with a blue
@@ -151,19 +153,35 @@ Display sizes use `clamp()` so one token works from 360px to desktop.
## 4. Space and layout
-An **8px grid**. The only legal spacing values are `4 8 12 16 24 32 40 56 72 96 128`. Anything
-else is a bug.
+**Linkder is a mobile-only product.** There is no desktop layout, no marketing site and no
+responsive breakpoint work. Design for a 360–430px viewport and nothing else. On a wider
+screen the app renders as a single 480px column centred on `ink-50` — that is a courtesy for
+someone who opened it on a laptop, not a layout to design for.
-| Container | Width | Used for |
+Concretely, this bans:
+
+- Multi-column grids. Everything is one column. A 2-up grid is allowed only for paired
+ numeric fields (from/to, rate/years) and stat tiles.
+- `md:` / `lg:` variants that change structure. Type already scales with `clamp()`.
+- Sticky desktop nav bars with link lists, mega-footers, hero sections, feature grids.
+
+An **8px grid**. The only legal spacing values are `4 8 12 16 24 32 40 56 72`. Anything else
+is a bug.
+
+| Token | Width | Used for |
|---|---|---|
-| `prose` | 680px | Reading, forms, single-column flows |
-| `app` | 1080px | Dashboards, lists |
-| `wide` | 1280px | Marketing sections |
+| `app` | 480px | Every screen. There is no second container. |
-- Page gutter: **24px** mobile, **32px** ≥768px.
-- Vertical rhythm between page sections: **72px** mobile, **96px** desktop.
+- Page gutter: **20px**. One value, all screens.
+- Vertical rhythm between blocks within a screen: **32px**. Between major groups: **40px**.
- Gap between sibling cards in a list: **12px**.
- Label → control: **8px**. Control → helper text: **8px**. Field → field: **24px**.
+- Bottom of scroll content clears the tab bar by **88px** (`pb-22`) so nothing hides under it.
+
+### 4.1 Safe areas
+
+The tab bar and any fixed footer pad with `env(safe-area-inset-bottom)`. The app bar pads with
+`env(safe-area-inset-top)`. Never assume the viewport edge is reachable.
---
@@ -253,16 +271,26 @@ Selection must never rely on fill alone.
Title in `h4`, body in `body-sm` `ink-600`.
-### 6.6 Navigation
+### 6.6 App bar
-Sticky top bar, 72px tall, `ink-0` at 88% opacity with `backdrop-blur(12px)`, hairline bottom
-border. Wordmark left in Display 700 `-0.02em`. Primary CTA right as a pill. Mobile collapses
-links; the CTA stays visible in the bar.
+Fixed top, **56px** plus top safe-area inset. `page` surface at 88% opacity with
+`backdrop-blur(12px)` and a hairline bottom border.
-### 6.7 Footer
+Two layouts only:
-`ink-950` surface, white text, secondary links `ink-400`, 72px top padding, 40px bottom,
-4-up column grid collapsing to 1-up under 640px.
+- **Root screens** — screen title left in `h4`, at most one icon action right.
+- **Pushed screens** — 44px back chevron left, centred title in `h4`, optional icon action right.
+
+No link lists. No CTA button in the bar — the primary action lives in the screen or the tab bar.
+
+### 6.7 Tab bar
+
+Fixed bottom, **56px** plus bottom safe-area inset, `page` surface, hairline top border. Two
+to five destinations, each a 44px-minimum target with a 24px icon over a `meta` label. Active
+tab is `brand-500` icon + label; inactive is `ink-600`. The active state carries both colour and
+weight, never colour alone.
+
+The tab bar is the app's only persistent navigation. There is no footer.
### 6.8 Deck card
@@ -311,6 +339,10 @@ deck's drag is essential and stays; its spring flattens under reduced motion.
| Placeholder standing in for a label | Visible `
-
-
- Swipe right to send this job to a pro, left to pass. Drag the card or use the buttons.
-
-
+
);
}
diff --git a/apps/web/src/app/jobs/new/form.tsx b/apps/web/src/app/jobs/new/form.tsx
index 9d08432..5bfb4f6 100644
--- a/apps/web/src/app/jobs/new/form.tsx
+++ b/apps/web/src/app/jobs/new/form.tsx
@@ -2,10 +2,20 @@
import { useState } from 'react';
import { useRouter } from 'next/navigation';
-import { Loader2 } from 'lucide-react';
import type { RouterOutputs } from '@/lib/trpc';
import { api } from '@/lib/trpc';
-import { cn } from '@/lib/utils';
+import {
+ Button,
+ Chip,
+ Field,
+ FieldNote,
+ FieldSet,
+ FormError,
+ Input,
+ OptionCard,
+ StickyAction,
+ Textarea,
+} from '@/components/ui';
type Categories = RouterOutputs['job']['categories'];
@@ -65,44 +75,34 @@ export function NewJobForm({ categories }: { categories: Categories }) {
}
return (
-