| 1 | --- |
| 2 | name: AiToEarn Web |
| 3 | description: Calm, high-density workspace for AI-assisted multi-platform content publishing |
| 4 | colors: |
| 5 | brand-purple: 'oklch(0.74 0.17 3)' |
| 6 | brand-cyan: 'oklch(0.71 0.17 294)' |
| 7 | gradient-foreground: 'oklch(0.985 0 0)' |
| 8 | background: 'oklch(1 0 0)' |
| 9 | foreground: 'oklch(0.145 0 0)' |
| 10 | card: 'oklch(1 0 0)' |
| 11 | muted: 'oklch(0.97 0 0)' |
| 12 | muted-foreground: 'oklch(0.556 0 0)' |
| 13 | secondary: 'oklch(0.97 0 0)' |
| 14 | accent: 'oklch(0.97 0 0)' |
| 15 | border: 'oklch(0.922 0 0)' |
| 16 | destructive: 'oklch(0.577 0.245 27.325)' |
| 17 | success: 'oklch(0.65 0.2 145)' |
| 18 | warning: 'oklch(0.75 0.18 70)' |
| 19 | info: 'oklch(0.6 0.18 250)' |
| 20 | section-alt-bg: '#f8f9fa' |
| 21 | typography: |
| 22 | display: |
| 23 | fontFamily: 'Suisseintl, sans-serif' |
| 24 | fontSize: '1.5rem' |
| 25 | fontWeight: 600 |
| 26 | lineHeight: 1 |
| 27 | letterSpacing: '-0.01em' |
| 28 | title: |
| 29 | fontFamily: 'Suisseintl, sans-serif' |
| 30 | fontSize: '1.125rem' |
| 31 | fontWeight: 600 |
| 32 | lineHeight: 1.4 |
| 33 | body: |
| 34 | fontFamily: 'Suisseintl, sans-serif' |
| 35 | fontSize: '0.875rem' |
| 36 | fontWeight: 400 |
| 37 | lineHeight: 1.5 |
| 38 | label: |
| 39 | fontFamily: 'Suisseintl, sans-serif' |
| 40 | fontSize: '0.75rem' |
| 41 | fontWeight: 500 |
| 42 | lineHeight: 1.4 |
| 43 | numeric: |
| 44 | fontFamily: 'DIN, Suisseintl, sans-serif' |
| 45 | fontSize: '0.875rem' |
| 46 | fontWeight: 500 |
| 47 | rounded: |
| 48 | sm: '6px' |
| 49 | md: '8px' |
| 50 | lg: '10px' |
| 51 | xl: '14px' |
| 52 | spacing: |
| 53 | xs: '4px' |
| 54 | sm: '8px' |
| 55 | md: '16px' |
| 56 | lg: '24px' |
| 57 | components: |
| 58 | button-primary: |
| 59 | backgroundColor: '{colors.brand-cyan}' |
| 60 | textColor: '{colors.gradient-foreground}' |
| 61 | rounded: '{rounded.md}' |
| 62 | padding: '8px 16px' |
| 63 | height: '36px' |
| 64 | button-outline: |
| 65 | backgroundColor: '{colors.background}' |
| 66 | textColor: '{colors.foreground}' |
| 67 | rounded: '{rounded.md}' |
| 68 | padding: '8px 16px' |
| 69 | height: '36px' |
| 70 | button-secondary: |
| 71 | backgroundColor: '{colors.secondary}' |
| 72 | textColor: '{colors.foreground}' |
| 73 | rounded: '{rounded.md}' |
| 74 | padding: '8px 16px' |
| 75 | height: '36px' |
| 76 | input-default: |
| 77 | backgroundColor: '{colors.background}' |
| 78 | textColor: '{colors.foreground}' |
| 79 | rounded: '{rounded.md}' |
| 80 | padding: '4px 12px' |
| 81 | height: '36px' |
| 82 | card-default: |
| 83 | backgroundColor: '{colors.card}' |
| 84 | textColor: '{colors.foreground}' |
| 85 | rounded: '{rounded.lg}' |
| 86 | padding: '24px' |
| 87 | --- |
| 88 | |
| 89 | # Design System: AiToEarn Web |
| 90 | |
| 91 | ## 1. Overview |
| 92 | |
| 93 | **Creative North Star: "The Calm Growth Studio"** |
| 94 | |
| 95 | AiToEarn is a professional workspace where creators and operators drive AI agents through high-volume, multi-account publishing. The interface should feel like a calm growth studio: capable and efficient, never cold or sterile. Surfaces stay quiet and neutral so dense publishing flows read clearly; the purple→cyan brand gradient appears deliberately across a small set of explicit roles to mark action, state, progress, identity, and page structure. The tool disappears into the task. |
| 96 | |
| 97 | This system rejects decorative interfaces that distract from publishing, inconsistent form and modal patterns, and low-contrast controls that are hard to read in dense surfaces. It is not a marketing canvas: nothing here is drenched in brand color for its own sake. Familiarity is a feature. Forms, dialogs, tabs, and media controls follow well-known product conventions so users trust the interface mid-flow across fourteen-plus social platforms. |
| 98 | |
| 99 | Depth is achieved through near-flat tonal layering, not heavy shadows. Light and dark themes are both first-class; every token resolves through semantic CSS variables so a surface is correct in either theme without a second styling pass. |
| 100 | |
| 101 | **Key Characteristics:** |
| 102 | |
| 103 | - Neutral grayscale surfaces; the brand gradient is the signature, deployed across action, state, progress, brand identity/frame, and seam roles while work surfaces stay neutral. |
| 104 | - High information density, with calm spacing rhythm to keep it legible. |
| 105 | - One sans family (Suisseintl) carries the whole UI; DIN for numerics, HYHanHeiJ for Chinese. |
| 106 | - Near-flat elevation via `shadow-sm` and tonal `color-mix` layering. |
| 107 | - Full light/dark parity through semantic tokens. |
| 108 | |
| 109 | ## 2. Colors |
| 110 | |
| 111 | A neutral grayscale foundation with a single two-stop brand gradient and a standard semantic state vocabulary. |
| 112 | |
| 113 | ### Primary |
| 114 | |
| 115 | - **Brand Cyan-Violet** (`oklch(0.71 0.17 294)`): The primary accent in light theme (`--primary`), the focus ring (`--ring`), and the warm stop of the brand gradient's resolved use. Carries primary actions, current selection, and sidebar active state. |
| 116 | - **Brand Magenta-Rose** (`oklch(0.74 0.17 3)`): The primary accent in dark theme and the second stop of the brand gradient (`--brand-purple`). Pairs with Brand Cyan-Violet in `--gradientBackColor` (`linear-gradient(to right in oklch, ...)`). |
| 117 | - **Gradient Foreground** (`oklch(0.985 0 0)`): Near-white text that sits on top of any brand-gradient or primary surface. The only correct foreground on the gradient. |
| 118 | |
| 119 | ### Neutral |
| 120 | |
| 121 | - **Ink** (`oklch(0.145 0 0)` light / `oklch(0.985 0 0)` dark): Primary text (`--foreground`). |
| 122 | - **Surface** (`oklch(1 0 0)` light / `oklch(0.145 0 0)` dark): Page and card background (`--background`, `--card`, `--popover`). |
| 123 | - **Muted Surface** (`oklch(0.97 0 0)` light / `oklch(0.269 0 0)` dark): Secondary, muted, and accent fills for panels, toolbars, hover states. |
| 124 | - **Muted Ink** (`oklch(0.556 0 0)` light / `oklch(0.708 0 0)` dark): Secondary text, placeholders, descriptions (`--muted-foreground`). |
| 125 | - **Hairline** (`oklch(0.922 0 0)` light / `oklch(1 0 0 / 10%)` dark): Borders, inputs, dividers (`--border`, `--input`). |
| 126 | - **Section Alt** (`#f8f9fa` light / `rgba(255,255,255,0.03)` dark): Alternating section background for zebra rhythm on long surfaces. |
| 127 | |
| 128 | ### Tertiary (semantic states) |
| 129 | |
| 130 | - **Destructive** (`oklch(0.577 0.245 27.325)`): Errors, delete actions. |
| 131 | - **Success** (`oklch(0.65 0.2 145)`): Successful publish, confirmations. |
| 132 | - **Warning** (`oklch(0.75 0.18 70)`): Pending / attention-needed states. |
| 133 | - **Info** (`oklch(0.6 0.18 250)`): Neutral informational callouts. |
| 134 | |
| 135 | ### Named Rules |
| 136 | |
| 137 | **The Signature Gradient Rule.** The purple→cyan brand gradient is the system's signature. It is used deliberately across a defined set of roles, never sprayed as ambient decoration. Allowed roles, and nowhere else: |
| 138 | |
| 139 | 1. **Action** — the primary button, key CTAs. |
| 140 | 2. **State** — selected/active states: the active-nav indicator bar and its text tint, unread/count badges, the active tab indicator, a toggle/switch in its `on` state. |
| 141 | 3. **Progress & emphasis** — progress-bar fills, the emphasized series in a chart or a single highlighted metric. |
| 142 | 4. **Brand identity & frame** — the brand mark, 1px gradient borders (via the `padding-box / border-box` technique) on a focused field or one featured card, an avatar ring, and an empty-state icon stroke. |
| 143 | 5. **Brand seam** — a single thin gradient hairline accenting a page header (a seam, not a band). |
| 144 | |
| 145 | The ceiling that keeps it product-grade: **work surfaces stay neutral.** Cards, tables, forms, list rows, and panel backgrounds are never filled or backed with the gradient. If the gradient is doing more than marking action, state, progress, identity, or a single brand frame/seam, it has crossed into decoration. Hard bans, always: no gradient **text** (`background-clip: text`), no gradient **side-stripe** wider than 1px, no full-bleed gradient on a work surface. |
| 146 | |
| 147 | **The Semantic-Token Rule.** Never hardcode a color (no `text-gray-900`, no `#000`, no arbitrary CSS-variable background utilities). Always use the semantic variable (`bg-background`, `text-muted-foreground`, `border-border`) so light/dark both resolve correctly. |
| 148 | |
| 149 | ## 3. Typography |
| 150 | |
| 151 | **Body Font:** Suisseintl (with `sans-serif` fallback) |
| 152 | **Numeric Font:** DIN (medium; metrics, counts, dates) |
| 153 | **Chinese Font:** HYHanHeiJ (`.btl-source-han-sans-cn` utility) |
| 154 | |
| 155 | **Character:** One neutral, technical-humanist sans carries the entire UI. There is no display/body pairing; hierarchy comes from weight and size, not from a second typeface. DIN handles dense numerics where even-width digits aid scanning. |
| 156 | |
| 157 | ### Hierarchy |
| 158 | |
| 159 | - **Display** (600, 1.5rem/24px, line-height 1): Card titles, section headings, modal titles. The ceiling for in-app headings; product UI does not shout. |
| 160 | - **Title** (600, 1.125rem/18px): Sub-section and panel headers. |
| 161 | - **Body** (400, 0.875rem/14px, line-height 1.5): The base size (`body` is `text-sm`). Default for all running text and controls. Cap prose at 65–75ch. |
| 162 | - **Label** (500, 0.75rem/12px): Form labels, badges, table headers, meta text. |
| 163 | - **Numeric** (DIN 500, 0.875rem): Counts, durations, dates, metrics. |
| 164 | |
| 165 | ### Named Rules |
| 166 | |
| 167 | **The One-Family Rule.** Suisseintl carries headings, buttons, labels, body, and data. DIN and HYHanHeiJ are scoped tools (numerics, Chinese), not a second display voice. Display fonts in UI labels, buttons, or data are forbidden. |
| 168 | |
| 169 | **The Fixed-Scale Rule.** Use the fixed rem scale, not fluid `clamp()` headings. Users view at consistent DPI; a heading that shrinks inside a sidebar looks worse, not better. |
| 170 | |
| 171 | ## 4. Elevation |
| 172 | |
| 173 | Near-flat by default. Depth is conveyed through tonal layering (`color-mix` blends of the brand tint into the background for sidebars and accents) far more than through shadow. The only routine shadow is a soft `shadow-sm`; the primary button adds a tinted `shadow-primary/20` that deepens on hover. |
| 174 | |
| 175 | ### Shadow Vocabulary |
| 176 | |
| 177 | - **Resting** (`box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05)` — Tailwind `shadow-sm`): Cards, inputs, buttons at rest. |
| 178 | - **Hover lift** (`shadow-md`): Primary button and interactive cards on hover only. |
| 179 | - **Brand glow** (`shadow-sm shadow-primary/20` → `shadow-md shadow-primary/25` on hover): Exclusive to the primary gradient button, tying elevation to brand intent. |
| 180 | |
| 181 | ### Named Rules |
| 182 | |
| 183 | **The Flat-By-Default Rule.** Surfaces are flat at rest. Shadows appear as a response to state (hover, focus, the primary action), never as ambient decoration. If a panel needs separation, reach for a hairline border or a tonal `color-mix` layer before a shadow. |
| 184 | |
| 185 | ## 5. Components |
| 186 | |
| 187 | ### Buttons |
| 188 | |
| 189 | - **Shape:** Gently rounded (`rounded-md`, 8px). Default height 36px (`h-9`), padding `8px 16px`. |
| 190 | - **Primary:** Brand gradient fill (`bg-gradient-back`) with `gradient-foreground` text, `shadow-sm shadow-primary/20`. Hover deepens shadow; active scales to `0.98`. The Action role of the Signature Gradient. |
| 191 | - **Outline:** `background` fill, `input` border, `shadow-sm`; hover fills `accent`. |
| 192 | - **Secondary:** `secondary` fill; hover at 80% opacity. |
| 193 | - **Ghost:** Transparent; hover fills `accent`. |
| 194 | - **Destructive:** `destructive` fill with `destructive-foreground`. |
| 195 | - **Link:** `primary` text with underline on hover. |
| 196 | - **Loading:** inline `animate-spin` SVG ring prepended; label text is unchanged, button is disabled. |
| 197 | - **Sizes:** `sm` 32px / `default` 36px / `lg` 40px / `icon` 36×36. |
| 198 | |
| 199 | ### Cards / Containers |
| 200 | |
| 201 | - **Corner Style:** `rounded-lg` (10px). |
| 202 | - **Background:** `card` on `border` hairline. |
| 203 | - **Shadow Strategy:** `shadow-sm` only (see Elevation). |
| 204 | - **Internal Padding:** 24px (`p-6`); header and content share the 6-unit rhythm. |
| 205 | |
| 206 | ### Inputs / Fields |
| 207 | |
| 208 | - **Style:** 36px tall (`h-9`), `rounded-md`, `input` border, transparent background, `px-3 py-1`. |
| 209 | - **Focus:** 1px `ring` outline (`focus-visible:ring-1 ring-ring`), no glow. |
| 210 | - **Placeholder:** `muted-foreground` (must still clear 4.5:1). |
| 211 | - **Disabled:** `cursor-not-allowed`, 50% opacity. |
| 212 | - **Numeric inputs:** always the `NumberInput` component, never native `type="number"`. |
| 213 | |
| 214 | ### Navigation (Sidebar) |
| 215 | |
| 216 | - **Style:** Tonal panel — `--sidebar` is the background with ~3% brand-cyan mixed in (light) / ~7% (dark), one step cooler than the content surface. |
| 217 | - **Active state (State role):** the active row carries a `--sidebar-accent` (10–14% brand-cyan mix) fill, a flush **2px gradient indicator bar** on its leading edge, and a gradient-tinted label (the icon + text shift toward the gradient hue). The indicator bar is the only gradient stripe permitted, and only at the leading edge of an active nav row — never as a card/list side-stripe. |
| 218 | - **Foreground:** inherits `--foreground`; primary/active foreground uses `gradient-foreground`. |
| 219 | |
| 220 | ### Toggles & Tabs (State role) |
| 221 | |
| 222 | - **Switch `on`:** the track fills with the brand gradient (`bg-gradient-back`); the thumb stays `gradient-foreground`. The `off` track is `--input`. |
| 223 | - **Tab / segmented active:** the active segment is marked by a **2px gradient underline** (or a gradient-bordered pill for segmented controls); inactive segments stay `muted-foreground` text on a neutral track. |
| 224 | |
| 225 | ### Progress & Data (Progress role) |
| 226 | |
| 227 | - **Progress bar:** the filled portion uses the brand gradient on a `--muted` track. The one place a horizontal gradient run is correct, because it encodes magnitude. |
| 228 | - **Chart emphasis:** at most one emphasized series or a single highlighted metric may use the gradient (as a stroke or fill); all other series stay neutral/semantic. |
| 229 | |
| 230 | ### Brand Identity & Frames (Brand-frame role) |
| 231 | |
| 232 | - **Brand mark:** the product mark may use the gradient fill because it carries identity, not page decoration. |
| 233 | - **Avatar ring:** a 2px gradient ring around an account/user avatar (via the `padding-box / border-box` border technique), marking identity, not decoration. |
| 234 | - **Featured card / focused field:** a single featured card or a focused input may take a 1px gradient border — never a fill, never a side-stripe. One per surface, maximum. |
| 235 | - **Empty-state icon:** the empty-state glyph may use a gradient stroke to keep an otherwise neutral empty surface on-brand. |
| 236 | |
| 237 | ### Page Header (Brand-seam role) |
| 238 | |
| 239 | - A single thin (1px) gradient hairline may accent a page header as a seam between the header and the content surface. One seam per page; it is an accent line, not a band, and never repeats down the page. |
| 240 | |
| 241 | ### Topic Mention (signature) |
| 242 | |
| 243 | The publish editor's `@topic` / `#tag` chip: inline text with a thin brand-gradient underline (`linear-gradient(90deg, cyan, purple)` at `0.08em`), trigger character in purple-weighted bold, value in cyan-weighted semibold. On focus it gains a gradient border-box and a faint brand ring. A deliberate use of the gradient that obeys the Signature Gradient rule (it marks a live, meaningful token — a State + Brand-frame use), and notably is an underline, not gradient text. |
| 244 | |
| 245 | ## 6. Do's and Don'ts |
| 246 | |
| 247 | ### Do: |
| 248 | |
| 249 | - **Do** deploy the brand gradient across its five defined roles — Action (primary button/CTA), State (active-nav indicator bar + tint, unread/count badge, active tab underline, switch `on`), Progress (progress fill, one emphasized metric/series), Brand identity/frame (brand mark, avatar ring, one featured card/focused-field border, empty-state icon stroke), and Brand-seam (a single page-header hairline) — the Signature Gradient Rule. |
| 250 | - **Do** keep work surfaces neutral: cards, tables, forms, list rows, and panel backgrounds are never filled or backed with the gradient. |
| 251 | - **Do** use semantic tokens (`bg-background`, `text-muted-foreground`, `border-border`) so every surface resolves in both light and dark. |
| 252 | - **Do** keep elevation near-flat: `shadow-sm` at rest, shadows only as a state response. |
| 253 | - **Do** carry the whole UI in Suisseintl; scope DIN to numerics and HYHanHeiJ to Chinese. |
| 254 | - **Do** use the `NumberInput` component for all numeric entry. |
| 255 | - **Do** verify body text and placeholders clear 4.5:1 contrast against tinted near-white surfaces. |
| 256 | |
| 257 | ### Don't: |
| 258 | |
| 259 | - **Don't** use the brand gradient as a work-surface background, ambient decoration, or a flourish beyond its five roles — if it isn't marking action, state, progress, or a single brand frame/seam, drop it. |
| 260 | - **Don't** render gradient **text** (`background-clip: text`), a gradient **side-stripe** wider than 1px on a card/list/alert, or any full-bleed gradient on a work surface — permanent bans regardless of role. |
| 261 | - **Don't** hardcode colors (`text-gray-900`, `#000`, `black`, `bg-[var(--primary-color)]`) — always semantic variables, Tailwind v4 `bg-(--token)` syntax. |
| 262 | - **Don't** put display fonts in UI labels, buttons, or data. |
| 263 | - **Don't** use fluid `clamp()` headings in product UI; use the fixed rem scale. |
| 264 | - **Don't** reach for a modal as the first thought — exhaust inline and progressive alternatives first. |
| 265 | - **Don't** ship inconsistent component vocabulary: the same button, input, and dialog shape must look identical across every platform's flow. |
| 266 | - **Don't** use native `<input type="number">`; it has inconsistent browser behavior (can't clear, spinners). |
| 267 | - **Don't** add gratuitous motion — transitions are 150–250ms and convey state, never choreography. |
| 268 |