@brewedby/ds 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/bin/fynd-ds.js +10 -0
- package/lib/installer.js +367 -0
- package/package.json +36 -0
- package/templates/FYND_DESIGN_SYSTEM.md +608 -0
- package/templates/fynd-tokens.css +492 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
# FYND_DESIGN_SYSTEM.md
|
|
2
|
+
> Drop this file into any Fynd project repo as `FYND_DESIGN_SYSTEM.md` or `CLAUDE.md`.
|
|
3
|
+
> It instructs AI tools (Claude, Cursor, Copilot) and developers on how to implement the Fynd One Design System correctly.
|
|
4
|
+
> **Last synced:** March 2026 — One Design System (Figma) + devlink/global.css
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Quick Setup
|
|
9
|
+
|
|
10
|
+
### 1 — Fonts
|
|
11
|
+
|
|
12
|
+
Fynd uses **three font families** with distinct roles:
|
|
13
|
+
|
|
14
|
+
| Family | Role | Source |
|
|
15
|
+
|--------|------|--------|
|
|
16
|
+
| **Fynd Sans** | Titles, headings (h1–h2) | Proprietary — self-host only |
|
|
17
|
+
| **Inter Display** | Body text, subtext, descriptions | Google Fonts / @fontsource |
|
|
18
|
+
| **Inter** | Buttons, UI labels, nav, table headers | Google Fonts / @fontsource |
|
|
19
|
+
|
|
20
|
+
#### Fynd Sans (proprietary — required for headings)
|
|
21
|
+
|
|
22
|
+
Fynd Sans is a custom typeface and **cannot be loaded from any public CDN**.
|
|
23
|
+
Obtain the `.woff2` files from the design assets repo or by contacting design@fynd.com.
|
|
24
|
+
|
|
25
|
+
Place files at:
|
|
26
|
+
```
|
|
27
|
+
your-project/
|
|
28
|
+
└── assets/
|
|
29
|
+
└── fonts/
|
|
30
|
+
├── FyndSans-Regular.woff2
|
|
31
|
+
├── FyndSans-Medium.woff2
|
|
32
|
+
├── FyndSans-SemiBold.woff2
|
|
33
|
+
└── FyndSans-Bold.woff2
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then declare via `@font-face` (already included in `fynd-tokens.css`):
|
|
37
|
+
```css
|
|
38
|
+
@font-face {
|
|
39
|
+
font-family: 'Fynd Sans';
|
|
40
|
+
src: url('/assets/fonts/FyndSans-Regular.woff2') format('woff2');
|
|
41
|
+
font-weight: 400;
|
|
42
|
+
font-style: normal;
|
|
43
|
+
font-display: swap;
|
|
44
|
+
}
|
|
45
|
+
@font-face {
|
|
46
|
+
font-family: 'Fynd Sans';
|
|
47
|
+
src: url('/assets/fonts/FyndSans-Medium.woff2') format('woff2');
|
|
48
|
+
font-weight: 500;
|
|
49
|
+
font-style: normal;
|
|
50
|
+
font-display: swap;
|
|
51
|
+
}
|
|
52
|
+
@font-face {
|
|
53
|
+
font-family: 'Fynd Sans';
|
|
54
|
+
src: url('/assets/fonts/FyndSans-SemiBold.woff2') format('woff2');
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
font-style: normal;
|
|
57
|
+
font-display: swap;
|
|
58
|
+
}
|
|
59
|
+
@font-face {
|
|
60
|
+
font-family: 'Fynd Sans';
|
|
61
|
+
src: url('/assets/fonts/FyndSans-Bold.woff2') format('woff2');
|
|
62
|
+
font-weight: 700;
|
|
63
|
+
font-style: normal;
|
|
64
|
+
font-display: swap;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> ⚠️ Adjust the `url()` path to match your project's asset serving path (e.g. `/public/fonts/` in Next.js, `/src/assets/fonts/` in Vite).
|
|
69
|
+
|
|
70
|
+
#### Inter Display + Inter (open source)
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<!-- Add to <head> -->
|
|
74
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
75
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
76
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter+Display:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Or self-hosted via npm (recommended for production):
|
|
80
|
+
```bash
|
|
81
|
+
npm install @fontsource/inter-display @fontsource/inter
|
|
82
|
+
```
|
|
83
|
+
```css
|
|
84
|
+
@import '@fontsource/inter-display/400.css';
|
|
85
|
+
@import '@fontsource/inter-display/500.css';
|
|
86
|
+
@import '@fontsource/inter-display/600.css';
|
|
87
|
+
@import '@fontsource/inter-display/700.css';
|
|
88
|
+
@import '@fontsource/inter/400.css';
|
|
89
|
+
@import '@fontsource/inter/500.css';
|
|
90
|
+
@import '@fontsource/inter/600.css';
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Loaded weights summary:**
|
|
94
|
+
| Family | Weights | Role |
|
|
95
|
+
|--------|---------|------|
|
|
96
|
+
| Fynd Sans | 400, 500, 600, 700 | Headings — h1, h2, large display text |
|
|
97
|
+
| Inter Display | 400, 500, 600, 700 | Body text, subtext, descriptions |
|
|
98
|
+
| Inter | 400, 500, 600 | Buttons, nav links, badges, table headers |
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### 2 — Icons
|
|
103
|
+
**Library: [Lucide Icons](https://lucide.dev)** (MIT license)
|
|
104
|
+
|
|
105
|
+
Chosen for: consistent 1.5px stroke weight, clean geometric style matching Fynd's aesthetic.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# React / Next.js / Astro
|
|
109
|
+
npm install lucide-react
|
|
110
|
+
|
|
111
|
+
# Vanilla / HTML
|
|
112
|
+
<script src="https://unpkg.com/lucide@latest"></script>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Usage:**
|
|
116
|
+
```jsx
|
|
117
|
+
// React / Astro (with React integration)
|
|
118
|
+
import { ShoppingBag, ArrowRight, ChevronDown } from 'lucide-react'
|
|
119
|
+
|
|
120
|
+
<ShoppingBag size={20} strokeWidth={1.5} />
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```html
|
|
124
|
+
<!-- Vanilla HTML -->
|
|
125
|
+
<i data-lucide="shopping-bag"></i>
|
|
126
|
+
<script>lucide.createIcons();</script>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Icon sizing conventions:**
|
|
130
|
+
| Context | Size | Token |
|
|
131
|
+
|---------|------|-------|
|
|
132
|
+
| UI / inline | 16px | `--icon-size-ui` |
|
|
133
|
+
| Feature / cards | 20px | `--icon-size-feature` |
|
|
134
|
+
| Decorative / hero | 24px | `--icon-size-decorative` |
|
|
135
|
+
|
|
136
|
+
**Always use `strokeWidth={1.5}`** — this is the Fynd icon style.
|
|
137
|
+
**Never fill icons** — Lucide icons are outline only.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### 3 — CSS Tokens
|
|
142
|
+
Copy `fynd-tokens.css` to your project and import before all other styles:
|
|
143
|
+
```css
|
|
144
|
+
/* styles/globals.css */
|
|
145
|
+
@import './fynd-tokens.css';
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Or drop into a CDN / shared package and reference via URL.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Typography Rules
|
|
153
|
+
|
|
154
|
+
### Font Families
|
|
155
|
+
```css
|
|
156
|
+
font-family: var(--font-family--primary); /* Fynd Sans — headings, display titles */
|
|
157
|
+
font-family: var(--font-family--secondary); /* Inter Display — body, subtext */
|
|
158
|
+
font-family: var(--font-family--ui); /* Inter — buttons, UI labels */
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
> **Rule:** `--font-family--primary` (Fynd Sans) is for h1, h2, and large marketing/display text only. Body copy, subtext, and descriptions always use `--font-family--secondary` (Inter Display). Interactive elements (buttons, nav, badges) always use `--font-family--ui` (Inter).
|
|
162
|
+
|
|
163
|
+
### Headings
|
|
164
|
+
```css
|
|
165
|
+
h1 {
|
|
166
|
+
font-family: var(--font-family--primary);
|
|
167
|
+
font-size: var(--font-size--h1); /* 72px */
|
|
168
|
+
font-weight: var(--font-weight--regular); /* 400 */
|
|
169
|
+
line-height: var(--line-height--110);
|
|
170
|
+
letter-spacing: var(--letter-spacing--heading-1); /* -0.04em */
|
|
171
|
+
}
|
|
172
|
+
h2 {
|
|
173
|
+
font-family: var(--font-family--primary);
|
|
174
|
+
font-size: var(--font-size--h2); /* 56px */
|
|
175
|
+
font-weight: var(--font-weight--regular);
|
|
176
|
+
line-height: var(--line-height--110);
|
|
177
|
+
letter-spacing: var(--letter-spacing--heading-2); /* -0.04em */
|
|
178
|
+
}
|
|
179
|
+
h3 { font-size: var(--font-size--h5); font-weight: 700; letter-spacing: var(--letter-spacing--heading-3); }
|
|
180
|
+
h4 { font-size: var(--font-size--body-l); font-weight: 700; letter-spacing: var(--letter-spacing--heading-4); }
|
|
181
|
+
h5 { font-size: var(--font-size--body-s); font-weight: 700; letter-spacing: var(--letter-spacing--heading-5); }
|
|
182
|
+
h6 { font-size: var(--font-size--body-xs); font-weight: 700; }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Body Text
|
|
186
|
+
```css
|
|
187
|
+
/* Default body */
|
|
188
|
+
font-family: var(--font-family--secondary);
|
|
189
|
+
font-size: var(--font-size--body-m); /* 16px */
|
|
190
|
+
font-weight: var(--font-weight--regular); /* 400 */
|
|
191
|
+
line-height: var(--line-height--150); /* 1.5 */
|
|
192
|
+
color: var(--text--title); /* #0e0e0e */
|
|
193
|
+
|
|
194
|
+
/* Subtext / captions */
|
|
195
|
+
color: var(--text--subtext); /* #5b5c5d */
|
|
196
|
+
font-size: var(--font-size--body-s); /* 14px */
|
|
197
|
+
line-height: var(--line-height--140);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Verified Figma Type Styles
|
|
201
|
+
| Style | Size | Weight | Line Height | Family |
|
|
202
|
+
|-------|------|--------|-------------|--------|
|
|
203
|
+
| Heading / h1 | 72px | 400 | 110% | **Fynd Sans** |
|
|
204
|
+
| Heading / h2 | 56px | 400 | 110% | **Fynd Sans** |
|
|
205
|
+
| Body XS / Regular | 12px | 400 | 130% | Inter Display |
|
|
206
|
+
| Body S / Regular | 14px | 400 | 140% | Inter Display |
|
|
207
|
+
| Body M / Regular | 16px | 400 | 150% | Inter Display |
|
|
208
|
+
| Body XL / Medium | 20px | 500 | 140% | Inter Display |
|
|
209
|
+
| Button / m-prominent | 14px | 500 | 20px | Inter |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Color Rules
|
|
214
|
+
|
|
215
|
+
### ⚠️ Critical: Two near-blacks
|
|
216
|
+
| Use case | Value | Token |
|
|
217
|
+
|----------|-------|-------|
|
|
218
|
+
| Text, interactive dark fills, buttons | `#0e0e0e` | `--text--title` |
|
|
219
|
+
| Dark section backgrounds (CTA, table headers) | `#101319` | `--neutral--neutral-100--devlink` |
|
|
220
|
+
|
|
221
|
+
**Never swap these.** Text and interactive elements use `#0e0e0e`. Background sections use `#101319`.
|
|
222
|
+
|
|
223
|
+
### Text Colors
|
|
224
|
+
```css
|
|
225
|
+
color: var(--text--title); /* #0e0e0e — headings, body */
|
|
226
|
+
color: var(--text--subtext); /* #5b5c5d — captions, meta */
|
|
227
|
+
color: var(--text--title-inverse); /* #ffffff — text on dark bg */
|
|
228
|
+
color: var(--text--subtext-inverse); /* #a0a1a2 — muted text on dark bg */
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Backgrounds
|
|
232
|
+
```css
|
|
233
|
+
background: var(--background--background-light); /* white */
|
|
234
|
+
background: var(--background--background-medium); /* #f8f8f9 */
|
|
235
|
+
background: var(--background--background-dark); /* #5b5c5d */
|
|
236
|
+
background: var(--background--background-darkest); /* #0e0e0e */
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Brand Color Usage Pattern
|
|
240
|
+
Each brand color (`blue`, `peach`, `green`, `gold`, `lavender`, `red`) exposes four aliases:
|
|
241
|
+
```css
|
|
242
|
+
/* Example — blue */
|
|
243
|
+
background: var(--blue--blue-fill); /* light bg → blue-10 #f9fbff */
|
|
244
|
+
border: var(--blue--blue-stroke); /* border → blue-20 #d8e2f5 */
|
|
245
|
+
color: var(--blue--blue-primary); /* accent → blue-40 #5c98f7 */
|
|
246
|
+
color: var(--blue--blue-text); /* dark text → blue-90 #07285a */
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Rule:** When placing text on a brand-coloured background, always use the `-text` (90-shade) alias. Never use `neutral-100` or `#0e0e0e` on a tinted background.
|
|
250
|
+
|
|
251
|
+
### Status Colors (feature tables, data)
|
|
252
|
+
```css
|
|
253
|
+
color: var(--status--yes); /* #0d7a3a — green, supported */
|
|
254
|
+
color: var(--status--partial); /* #9a6700 — amber, partial */
|
|
255
|
+
color: var(--status--no); /* #c13515 — red, not supported */
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Spacing Rules
|
|
261
|
+
|
|
262
|
+
Always use tokens, never magic numbers:
|
|
263
|
+
```css
|
|
264
|
+
gap: var(--spacing--16); /* 16px */
|
|
265
|
+
padding: var(--spacing--24) var(--spacing--32);
|
|
266
|
+
margin: 0 0 var(--spacing--56);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
> **Figma shorthand:** Components from the Figma file use `var(--16, 16px)` syntax. This resolves via the shorthand aliases defined in `fynd-tokens.css` (e.g. `--16`, `--24`).
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Border Radius Rules
|
|
274
|
+
|
|
275
|
+
```css
|
|
276
|
+
border-radius: var(--border-radius--pill); /* 250px — BUTTONS, chips, fully-rounded elements */
|
|
277
|
+
border-radius: var(--border-radius--tag); /* 2000px — badges, tags (from Figma) */
|
|
278
|
+
border-radius: var(--border-radius--16); /* 16px — cards */
|
|
279
|
+
border-radius: var(--border-radius--24); /* 24px — CTA boxes, large containers */
|
|
280
|
+
border-radius: var(--border-radius--8); /* 8px — inputs, small elements */
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
> **Rule: All buttons use `--border-radius--pill` (250px).** This is the canonical Fynd button shape — fully rounded, not square-cornered.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Component Patterns
|
|
288
|
+
|
|
289
|
+
### Button
|
|
290
|
+
|
|
291
|
+
```css
|
|
292
|
+
.btn {
|
|
293
|
+
display: inline-flex;
|
|
294
|
+
align-items: center;
|
|
295
|
+
gap: var(--spacing--8);
|
|
296
|
+
padding: var(--btn--padding-y) var(--btn--padding-x);
|
|
297
|
+
border-radius: var(--btn--border-radius); /* 250px — pill */
|
|
298
|
+
font-family: var(--btn--font-family); /* Inter */
|
|
299
|
+
font-size: var(--btn--font-size); /* 14px */
|
|
300
|
+
font-weight: var(--btn--font-weight); /* 500 */
|
|
301
|
+
line-height: var(--btn--line-height); /* 20px */
|
|
302
|
+
letter-spacing: var(--btn--letter-spacing); /* 0 */
|
|
303
|
+
transition: var(--btn--transition);
|
|
304
|
+
cursor: pointer;
|
|
305
|
+
border: none;
|
|
306
|
+
white-space: nowrap;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* Variants */
|
|
310
|
+
.btn-primary { background: var(--btn--primary--bg); color: var(--btn--primary--color); }
|
|
311
|
+
.btn-primary:hover { background: var(--btn--primary--bg-hover); }
|
|
312
|
+
|
|
313
|
+
.btn-primary-light { background: var(--btn--primary-light--bg); color: var(--btn--primary-light--color); }
|
|
314
|
+
.btn-primary-light:hover { background: var(--btn--primary-light--bg-hover); }
|
|
315
|
+
|
|
316
|
+
.btn-secondary { background: var(--btn--secondary--bg); color: var(--btn--secondary--color); border: 1px solid var(--btn--secondary--border); }
|
|
317
|
+
.btn-secondary:hover { background: var(--btn--secondary--bg-hover); border-color: var(--btn--secondary--border-hover); }
|
|
318
|
+
|
|
319
|
+
.btn-secondary-light { background: var(--btn--secondary-light--bg); color: var(--btn--secondary-light--color); border: 1px solid var(--btn--secondary-light--border); }
|
|
320
|
+
.btn-secondary-light:hover { background: var(--btn--secondary-light--bg-hover); border-color: var(--btn--secondary-light--border-hover); }
|
|
321
|
+
|
|
322
|
+
/* Size modifier */
|
|
323
|
+
.btn-lg { padding: var(--btn--padding-y-lg) var(--btn--padding-x-lg); font-size: var(--font-size--ui-l); }
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
```jsx
|
|
327
|
+
// React
|
|
328
|
+
<button className="btn btn-primary">Get started</button>
|
|
329
|
+
<button className="btn btn-secondary btn-lg">Learn more</button>
|
|
330
|
+
|
|
331
|
+
// With Lucide icon
|
|
332
|
+
import { ArrowRight } from 'lucide-react'
|
|
333
|
+
<button className="btn btn-primary">
|
|
334
|
+
Get started <ArrowRight size={16} strokeWidth={1.5} />
|
|
335
|
+
</button>
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Badge / Tag
|
|
339
|
+
|
|
340
|
+
```css
|
|
341
|
+
.badge {
|
|
342
|
+
display: inline-flex;
|
|
343
|
+
align-items: center;
|
|
344
|
+
padding: var(--badge--padding-y) var(--badge--padding-x); /* 8px 12px */
|
|
345
|
+
border-radius: var(--badge--border-radius); /* 2000px */
|
|
346
|
+
font-family: var(--font-family--secondary);
|
|
347
|
+
font-size: var(--badge--font-size); /* 12px */
|
|
348
|
+
font-weight: var(--badge--font-weight); /* 400 */
|
|
349
|
+
line-height: var(--line-height--130);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.badge-blue { background: var(--blue--blue-fill); color: var(--blue--blue-text); }
|
|
353
|
+
.badge-green { background: var(--green--green-fill); color: var(--green--green-text); }
|
|
354
|
+
.badge-gold { background: var(--gold--gold-fill); color: var(--gold--gold-text); }
|
|
355
|
+
.badge-lavender { background: var(--lavender--lavender-fill); color: var(--lavender--lavender-text); }
|
|
356
|
+
.badge-peach { background: var(--peach--peach-fill); color: var(--peach--peach-text); }
|
|
357
|
+
.badge-red { background: var(--red--red-fill); color: var(--red--red-text); }
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Card
|
|
361
|
+
|
|
362
|
+
```css
|
|
363
|
+
.card {
|
|
364
|
+
background: var(--background--background-light);
|
|
365
|
+
border: 1px solid var(--neutral--neutral-20);
|
|
366
|
+
border-radius: var(--border-radius--16);
|
|
367
|
+
padding: var(--spacing--32);
|
|
368
|
+
}
|
|
369
|
+
.card:hover {
|
|
370
|
+
border-color: var(--neutral--neutral-30);
|
|
371
|
+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/* Pricing card — recommended variant */
|
|
375
|
+
.card-pricing-recommended {
|
|
376
|
+
border: 2px solid var(--neutral--neutral-100);
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### CTA Dark Section
|
|
381
|
+
```css
|
|
382
|
+
.cta-dark {
|
|
383
|
+
background: #0e0e0e;
|
|
384
|
+
border-radius: var(--border-radius--24);
|
|
385
|
+
padding: var(--spacing--80) var(--spacing--32);
|
|
386
|
+
text-align: center;
|
|
387
|
+
}
|
|
388
|
+
.cta-dark .cta-inner {
|
|
389
|
+
max-width: var(--layout--cta-content-max); /* 640px */
|
|
390
|
+
margin: 0 auto;
|
|
391
|
+
display: flex;
|
|
392
|
+
flex-direction: column;
|
|
393
|
+
align-items: center;
|
|
394
|
+
gap: var(--spacing--24);
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### FAQ Accordion
|
|
399
|
+
```css
|
|
400
|
+
.faq-layout { display: grid; grid-template-columns: 1fr 1.5fr; gap: 3rem; }
|
|
401
|
+
.faq-left { position: sticky; top: 6rem; height: fit-content; }
|
|
402
|
+
.faq-item { border-bottom: 1px solid var(--neutral--neutral-20); }
|
|
403
|
+
.faq-toggle { transition: transform 0.3s ease; }
|
|
404
|
+
.faq-item.open .faq-toggle { transform: rotate(45deg); }
|
|
405
|
+
.faq-answer { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; }
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Layout
|
|
411
|
+
|
|
412
|
+
### Container
|
|
413
|
+
```css
|
|
414
|
+
.container {
|
|
415
|
+
max-width: var(--layout--container-max); /* 1200px */
|
|
416
|
+
margin: 0 auto;
|
|
417
|
+
padding: 0 var(--layout--container-padding); /* 0 2rem */
|
|
418
|
+
}
|
|
419
|
+
@media (max-width: 767px) {
|
|
420
|
+
.container { padding: 0 var(--layout--container-padding-mobile); } /* 0 1.25rem */
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Section Header
|
|
425
|
+
```css
|
|
426
|
+
.section-header {
|
|
427
|
+
max-width: var(--layout--section-header-max); /* 720px */
|
|
428
|
+
margin: 0 auto var(--spacing--56);
|
|
429
|
+
text-align: center;
|
|
430
|
+
display: flex;
|
|
431
|
+
flex-direction: column;
|
|
432
|
+
align-items: center;
|
|
433
|
+
gap: var(--spacing--16);
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Responsive Grid Behaviour
|
|
438
|
+
| Component | Desktop | Tablet (≤991px) | Mobile (≤767px) |
|
|
439
|
+
|-----------|---------|-----------------|-----------------|
|
|
440
|
+
| Feature cards | 3 columns | 2 columns | 1 column |
|
|
441
|
+
| 50/50 narrative | 2 col, 3rem gap | 2 col, 2rem gap | 1 col, 1.5rem gap |
|
|
442
|
+
| Pricing | 2 columns | 2 columns | 1 column |
|
|
443
|
+
| FAQ | 2 col grid | 1 column | 1 column |
|
|
444
|
+
| Verdict cards | 3 columns | 3 columns | 1 column |
|
|
445
|
+
| Button group | horizontal | horizontal | vertical, full-width |
|
|
446
|
+
| Stat font size | 3rem | 3rem | 2.25rem |
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## Dos and Don'ts
|
|
451
|
+
|
|
452
|
+
| ✅ Do | ❌ Don't |
|
|
453
|
+
|-------|---------|
|
|
454
|
+
| Use `--border-radius--pill` (250px) for all buttons | Use `8px` or fixed px on buttons |
|
|
455
|
+
| Use `Inter` (not Inter Display) for button text | Use Inter Display for button labels |
|
|
456
|
+
| Use `#0e0e0e` for text/interactive darks | Mix up `#0e0e0e` and `#101319` |
|
|
457
|
+
| Use `--badge--border-radius` (2000px) for tags | Use smaller radius for badge/pill shapes |
|
|
458
|
+
| Reference `--text--subtext` for secondary text | Hardcode `#5b5c5d` |
|
|
459
|
+
| Use stroke-only Lucide icons at 1.5px | Fill icons or mix icon libraries |
|
|
460
|
+
| Apply `-text` (90-shade) for text on tinted bg | Use black on brand-colour fills |
|
|
461
|
+
| Import `fynd-tokens.css` before all other styles | Override token values inline |
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## CSS Variable Quick Reference
|
|
466
|
+
|
|
467
|
+
### Naming Conventions
|
|
468
|
+
| Pattern | Example | Used in |
|
|
469
|
+
|---------|---------|---------|
|
|
470
|
+
| `--font-family--{role}` | `--font-family--primary` | Typography |
|
|
471
|
+
| `--font-size--{scale}` | `--font-size--body-m` | Typography |
|
|
472
|
+
| `--neutral--neutral-{n}` | `--neutral--neutral-60` | Raw neutrals |
|
|
473
|
+
| `--{color}--{color}-{shade}` | `--blue--blue-40` | Raw brand colors |
|
|
474
|
+
| `--{color}--{color}-{alias}` | `--blue--blue-fill` | Semantic shortcuts |
|
|
475
|
+
| `--text--{role}` | `--text--subtext` | Semantic text |
|
|
476
|
+
| `--background--{role}` | `--background--background-medium` | Semantic bg |
|
|
477
|
+
| `--border--{role}` | `--border--border-medium` | Semantic borders |
|
|
478
|
+
| `--spacing--{n}` | `--spacing--24` | Spacing scale |
|
|
479
|
+
| `--border-radius--{n}` | `--border-radius--pill` | Radius scale |
|
|
480
|
+
| `--btn--{variant}--{prop}` | `--btn--primary--bg` | Button tokens |
|
|
481
|
+
| `--badge--{prop}` | `--badge--border-radius` | Badge tokens |
|
|
482
|
+
| `--card--{type}--{prop}` | `--card--pricing--radius` | Card tokens |
|
|
483
|
+
|
|
484
|
+
### Figma Slash → CSS Dash Mapping
|
|
485
|
+
| Figma variable | CSS token |
|
|
486
|
+
|---------------|-----------|
|
|
487
|
+
| `--text/title` | `--text--title` |
|
|
488
|
+
| `--text/subtext` | `--text--subtext` |
|
|
489
|
+
| `--background/background-light` | `--background--background-light` |
|
|
490
|
+
| `--typeface/font-family/title` | `--font-family--primary` |
|
|
491
|
+
| `--typeface/font-family/body` | `--font-family--secondary` |
|
|
492
|
+
| `--font-family/sans` | `--font-family--ui` |
|
|
493
|
+
| `--font-size/m` | `--font-size--ui-m` |
|
|
494
|
+
| `--line-height/m` | `--line-height--ui-m` |
|
|
495
|
+
| `--letter-spacing/baggy` | `--letter-spacing--baggy` |
|
|
496
|
+
| `--borderradius/core/full` | `--border-radius--pill` |
|
|
497
|
+
| `--16` (Figma shorthand) | `--spacing--16` or `--16` |
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
## Stack-Specific Setup
|
|
502
|
+
|
|
503
|
+
### Astro
|
|
504
|
+
```astro
|
|
505
|
+
---
|
|
506
|
+
// src/layouts/Layout.astro
|
|
507
|
+
---
|
|
508
|
+
<html>
|
|
509
|
+
<head>
|
|
510
|
+
<!-- Inter Display + Inter via Google Fonts -->
|
|
511
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
512
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
513
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter+Display:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
|
514
|
+
<!-- Fynd Sans is declared via @font-face in fynd-tokens.css -->
|
|
515
|
+
</head>
|
|
516
|
+
<body>
|
|
517
|
+
<slot />
|
|
518
|
+
</body>
|
|
519
|
+
</html>
|
|
520
|
+
|
|
521
|
+
<style is:global>
|
|
522
|
+
@import '../styles/fynd-tokens.css'; /* @font-face for Fynd Sans lives here */
|
|
523
|
+
</style>
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Next.js (App Router)
|
|
527
|
+
```tsx
|
|
528
|
+
// app/layout.tsx
|
|
529
|
+
// Note: Fynd Sans must be loaded via @font-face in globals.css (not next/font — proprietary font)
|
|
530
|
+
import { Inter_Display, Inter } from 'next/font/google'
|
|
531
|
+
import './globals.css' // contains @import './fynd-tokens.css' with Fynd Sans @font-face
|
|
532
|
+
|
|
533
|
+
const interDisplay = Inter_Display({
|
|
534
|
+
subsets: ['latin'],
|
|
535
|
+
weight: ['400','500','600','700'],
|
|
536
|
+
variable: '--font-inter-display'
|
|
537
|
+
})
|
|
538
|
+
const inter = Inter({
|
|
539
|
+
subsets: ['latin'],
|
|
540
|
+
weight: ['400','500','600'],
|
|
541
|
+
variable: '--font-inter'
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
export default function RootLayout({ children }) {
|
|
545
|
+
return (
|
|
546
|
+
<html className={`${interDisplay.variable} ${inter.variable}`}>
|
|
547
|
+
<body>{children}</body>
|
|
548
|
+
</html>
|
|
549
|
+
)
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
> Place `FyndSans-*.woff2` files in `public/fonts/` and update the `url()` paths in `fynd-tokens.css` to `/fonts/FyndSans-Regular.woff2` etc.
|
|
554
|
+
|
|
555
|
+
### React (Vite / CRA)
|
|
556
|
+
```tsx
|
|
557
|
+
// main.tsx or index.tsx
|
|
558
|
+
import './styles/fynd-tokens.css' // Fynd Sans @font-face + all tokens
|
|
559
|
+
```
|
|
560
|
+
> Place `FyndSans-*.woff2` files in `src/assets/fonts/` and update the `url()` paths accordingly.
|
|
561
|
+
|
|
562
|
+
### Vanilla HTML
|
|
563
|
+
```html
|
|
564
|
+
<head>
|
|
565
|
+
<!-- Fynd Sans via fynd-tokens.css @font-face (files must be on your server) -->
|
|
566
|
+
<link rel="stylesheet" href="fynd-tokens.css">
|
|
567
|
+
<!-- Inter Display + Inter -->
|
|
568
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
569
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
570
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter+Display:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
|
571
|
+
<!-- Lucide icons -->
|
|
572
|
+
<script src="https://unpkg.com/lucide@latest"></script>
|
|
573
|
+
</head>
|
|
574
|
+
<body>
|
|
575
|
+
<script>lucide.createIcons();</script>
|
|
576
|
+
</body>
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
## File Structure (when adding to a project)
|
|
582
|
+
|
|
583
|
+
```
|
|
584
|
+
your-project/
|
|
585
|
+
├── FYND_DESIGN_SYSTEM.md ← this file (AI context + dev reference)
|
|
586
|
+
├── src/
|
|
587
|
+
│ └── styles/
|
|
588
|
+
│ ├── fynd-tokens.css ← import first, before all other styles
|
|
589
|
+
│ └── globals.css ← @import './fynd-tokens.css' at top
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
## Source References
|
|
595
|
+
|
|
596
|
+
| Resource | URL / Location |
|
|
597
|
+
|----------|---------------|
|
|
598
|
+
| One Design System (Figma) | https://www.figma.com/design/qtxg951KvgNG3jYzQQU2s4/One-Design-system |
|
|
599
|
+
| **Fynd Sans** font files | Internal — contact design@fynd.com or pull from design assets repo |
|
|
600
|
+
| Inter Display (Google Fonts) | https://fonts.google.com/specimen/Inter+Display |
|
|
601
|
+
| Inter (Google Fonts) | https://fonts.google.com/specimen/Inter |
|
|
602
|
+
| @fontsource/inter-display | https://fontsource.org/fonts/inter-display |
|
|
603
|
+
| Lucide Icons | https://lucide.dev |
|
|
604
|
+
| lucide-react (npm) | https://www.npmjs.com/package/lucide-react |
|
|
605
|
+
| devlink/global.css | Internal — `devlink/global.css` (~2800 lines) |
|
|
606
|
+
| compare.css | Internal — `src/styles/compare.css` (501 lines) |
|
|
607
|
+
|
|
608
|
+
*Last synced: March 2026*
|