designlang 4.0.0 → 4.0.1

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <h1 align="center">designlang</h1>
2
+ <h1 align="center">DESIGNLANG</h1>
3
3
  <p align="center">Reverse-engineer any website's complete design system in one command.</p>
4
4
  </p>
5
5
 
@@ -7,6 +7,7 @@
7
7
  <a href="https://www.npmjs.com/package/designlang"><img src="https://img.shields.io/npm/v/designlang?color=blue&label=npm" alt="npm version"></a>
8
8
  <a href="https://github.com/Manavarya09/design-extract/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Manavarya09/design-extract" alt="license"></a>
9
9
  <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/designlang" alt="node version"></a>
10
+ <a href="https://website-five-lime-65.vercel.app"><img src="https://img.shields.io/badge/website-live-red" alt="website"></a>
10
11
  </p>
11
12
 
12
13
  ---
@@ -127,6 +128,47 @@ designlang brands stripe.com vercel.com github.com linear.app
127
128
 
128
129
  Generates a matrix with color overlap analysis, typography comparison, spacing systems, and accessibility scores. Outputs both `brands.md` and `brands.html`.
129
130
 
131
+ ### 6. Clone Command
132
+
133
+ Generate a working Next.js app with the extracted design applied:
134
+
135
+ ```bash
136
+ designlang clone https://stripe.com
137
+ cd cloned-design && npm install && npm run dev
138
+ ```
139
+
140
+ One command → a running app with the site's colors, fonts, spacing, and component patterns.
141
+
142
+ ### 7. Design System Scoring
143
+
144
+ Rate any site's design quality across 7 categories:
145
+
146
+ ```bash
147
+ designlang score https://vercel.com
148
+ ```
149
+
150
+ ```
151
+ 68/100 Grade: D
152
+
153
+ Color Discipline ██████████░░░░░░░░░░ 50
154
+ Typography ██████████████░░░░░░ 70
155
+ Spacing System ████████████████░░░░ 80
156
+ Shadows ██████████░░░░░░░░░░ 50
157
+ Border Radii ████████░░░░░░░░░░░░ 40
158
+ Accessibility ███████████████████░ 94
159
+ Tokenization ████████████████████ 100
160
+ ```
161
+
162
+ ### 8. Watch Mode
163
+
164
+ Monitor a site for design changes:
165
+
166
+ ```bash
167
+ designlang watch https://stripe.com --interval 60
168
+ ```
169
+
170
+ Checks hourly and alerts when colors, fonts, or accessibility scores change.
171
+
130
172
  ## All Features
131
173
 
132
174
  | Feature | Flag / Command | Description |
@@ -134,12 +176,16 @@ Generates a matrix with color overlap analysis, typography comparison, spacing s
134
176
  | Base extraction | `designlang <url>` | Colors, typography, spacing, shadows, radii, CSS vars, breakpoints, animations, components |
135
177
  | Layout system | automatic | Grid patterns, flex usage, container widths, gap values |
136
178
  | Accessibility | automatic | WCAG 2.1 contrast ratios for all fg/bg pairs |
179
+ | Design scoring | automatic | 7-category quality rating (A-F) with actionable issues |
137
180
  | Dark mode | `--dark` | Extracts dark color scheme |
138
181
  | Multi-page | `--depth <n>` | Crawl N internal pages for site-wide tokens |
139
182
  | Screenshots | `--screenshots` | Capture buttons, cards, inputs, nav, hero, full page |
140
183
  | Responsive | `--responsive` | Crawl at 4 viewports, map breakpoint changes |
141
184
  | Interactions | `--interactions` | Capture hover/focus/active state transitions |
142
185
  | Everything | `--full` | Enable screenshots + responsive + interactions |
186
+ | Clone | `designlang clone <url>` | Generate a working Next.js starter with extracted design |
187
+ | Score | `designlang score <url>` | Rate design quality with visual bar chart breakdown |
188
+ | Watch | `designlang watch <url>` | Monitor for design changes on interval |
143
189
  | Diff | `designlang diff <A> <B>` | Compare two sites (MD + HTML) |
144
190
  | Multi-brand | `designlang brands <urls...>` | N-site comparison matrix |
145
191
  | Sync | `designlang sync <url>` | Update local tokens from live site |
@@ -167,6 +213,9 @@ Options:
167
213
  --verbose Detailed progress output
168
214
 
169
215
  Commands:
216
+ clone <url> Generate a working Next.js starter from extracted design
217
+ score <url> Rate design quality (7 categories, A-F, bar chart)
218
+ watch <url> Monitor for design changes on interval
170
219
  diff <urlA> <urlB> Compare two sites' design languages
171
220
  brands <urls...> Multi-brand comparison matrix
172
221
  sync <url> Sync local tokens with live site
@@ -203,12 +252,13 @@ Running `designlang https://vercel.com --full`:
203
252
  Shadows: 11 unique shadows
204
253
  Radii: 10 unique values
205
254
  Breakpoints: 45 breakpoints
206
- Components: 4 patterns detected
255
+ Components: 11 types detected
207
256
  CSS Vars: 407 custom properties
208
257
  Layout: 55 grids, 492 flex containers
209
258
  Responsive: 4 viewports, 3 breakpoint changes
210
259
  Interactions: 8 state changes captured
211
260
  A11y: 94% WCAG score (7 failing pairs)
261
+ Design Score: 68/100 (D) — 4 issues
212
262
  ```
213
263
 
214
264
  ## How It Works
@@ -230,6 +280,10 @@ npx skills add Manavarya09/design-extract
230
280
 
231
281
  In Claude Code, use `/extract-design <url>`.
232
282
 
283
+ ## Website
284
+
285
+ **[website-five-lime-65.vercel.app](https://website-five-lime-65.vercel.app)** — the brutalist product page.
286
+
233
287
  ## Contributing
234
288
 
235
289
  See [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome!
@@ -28,7 +28,7 @@ const program = new Command();
28
28
  program
29
29
  .name('designlang')
30
30
  .description('Extract the complete design language from any website')
31
- .version('4.0.0');
31
+ .version('4.0.1');
32
32
 
33
33
  // ── Main command: extract ──────────────────────────────────────
34
34
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "designlang",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "Extract the complete design language from any website — colors, typography, spacing, shadows, and more. Outputs AI-optimized markdown, W3C design tokens, Tailwind config, and CSS variables.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/crawler.js CHANGED
@@ -14,9 +14,11 @@ export async function crawlPage(url, options = {}) {
14
14
  });
15
15
  const page = await context.newPage();
16
16
 
17
- await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
17
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
18
+ // Wait for network to settle — but don't hang on sites with persistent connections
19
+ await page.waitForLoadState('networkidle').catch(() => {});
18
20
  if (wait > 0) await page.waitForTimeout(wait);
19
- await page.evaluate(() => document.fonts.ready);
21
+ await page.evaluate(() => document.fonts.ready).catch(() => {});
20
22
 
21
23
  const title = await page.title();
22
24
  const lightData = await extractPageData(page);
@@ -33,8 +35,9 @@ export async function crawlPage(url, options = {}) {
33
35
  const internalLinks = await discoverInternalLinks(page, url, depth);
34
36
  for (const link of internalLinks) {
35
37
  try {
36
- await page.goto(link, { waitUntil: 'networkidle', timeout: 20000 });
37
- await page.evaluate(() => document.fonts.ready);
38
+ await page.goto(link, { waitUntil: 'domcontentloaded', timeout: 20000 });
39
+ await page.waitForLoadState('networkidle').catch(() => {});
40
+ await page.evaluate(() => document.fonts.ready).catch(() => {});
38
41
  const pageData = await extractPageData(page);
39
42
  additionalPages.push({ url: link, data: pageData });
40
43
  } catch { /* skip failed pages */ }
@@ -50,8 +53,9 @@ export async function crawlPage(url, options = {}) {
50
53
  colorScheme: 'dark',
51
54
  });
52
55
  const darkPage = await darkContext.newPage();
53
- await darkPage.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
54
- await darkPage.evaluate(() => document.fonts.ready);
56
+ await darkPage.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
57
+ await darkPage.waitForLoadState('networkidle').catch(() => {});
58
+ await darkPage.evaluate(() => document.fonts.ready).catch(() => {});
55
59
  darkData = await extractPageData(darkPage);
56
60
  await darkContext.close();
57
61
  } else {
@@ -8,9 +8,10 @@ export async function captureInteractions(url, options = {}) {
8
8
  const context = await browser.newContext({ viewport: { width, height } });
9
9
  const page = await context.newPage();
10
10
 
11
- await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
11
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
12
+ await page.waitForLoadState('networkidle').catch(() => {});
12
13
  if (wait > 0) await page.waitForTimeout(wait);
13
- await page.evaluate(() => document.fonts.ready);
14
+ await page.evaluate(() => document.fonts.ready).catch(() => {});
14
15
 
15
16
  const results = { buttons: [], links: [], inputs: [] };
16
17
 
@@ -20,9 +20,10 @@ export async function captureResponsive(url, options = {}) {
20
20
  const page = await context.newPage();
21
21
 
22
22
  try {
23
- await page.goto(url, { waitUntil: 'networkidle', timeout: 20000 });
23
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 20000 });
24
+ await page.waitForLoadState('networkidle').catch(() => {});
24
25
  if (wait > 0) await page.waitForTimeout(wait);
25
- await page.evaluate(() => document.fonts.ready);
26
+ await page.evaluate(() => document.fonts.ready).catch(() => {});
26
27
 
27
28
  const data = await page.evaluate(() => {
28
29
  const body = document.body;
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": "0.0.1",
3
+ "configurations": [
4
+ {
5
+ "name": "website",
6
+ "runtimeExecutable": "npm",
7
+ "runtimeArgs": ["run", "dev"],
8
+ "port": 3000
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,5 @@
1
+ <!-- BEGIN:nextjs-agent-rules -->
2
+ # This is NOT the Next.js you know
3
+
4
+ This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
5
+ <!-- END:nextjs-agent-rules -->
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,36 @@
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
Binary file
@@ -0,0 +1,432 @@
1
+ :root {
2
+ --red: #ff0000;
3
+ --black: #0a0a0a;
4
+ --white: #f5f0e8;
5
+ --cream: #e8e0d0;
6
+ --yellow: #ffdd00;
7
+ --gray: #333;
8
+ }
9
+
10
+ * { margin: 0; padding: 0; box-sizing: border-box; }
11
+
12
+ html { scroll-behavior: smooth; }
13
+
14
+ body {
15
+ background: var(--black);
16
+ color: var(--white);
17
+ font-family: 'Inter', sans-serif;
18
+ overflow-x: hidden;
19
+ cursor: crosshair;
20
+ }
21
+
22
+ ::selection {
23
+ background: var(--red);
24
+ color: var(--black);
25
+ }
26
+
27
+ a { color: inherit; }
28
+
29
+ /* ── HERO ── */
30
+ .hero {
31
+ min-height: 100vh;
32
+ display: flex;
33
+ flex-direction: column;
34
+ justify-content: center;
35
+ align-items: center;
36
+ position: relative;
37
+ border-bottom: 4px solid var(--red);
38
+ overflow: hidden;
39
+ }
40
+
41
+ .hero::before {
42
+ content: 'DESIGNLANG DESIGNLANG DESIGNLANG DESIGNLANG DESIGNLANG ';
43
+ position: absolute;
44
+ top: 0;
45
+ left: 0;
46
+ width: 300%;
47
+ font-family: 'Unbounded', sans-serif;
48
+ font-size: 14vw;
49
+ font-weight: 900;
50
+ color: rgba(255, 0, 0, 0.03);
51
+ white-space: nowrap;
52
+ pointer-events: none;
53
+ animation: scroll-text 20s linear infinite;
54
+ line-height: 1;
55
+ }
56
+
57
+ @keyframes scroll-text {
58
+ 0% { transform: translateX(0); }
59
+ 100% { transform: translateX(-33.33%); }
60
+ }
61
+
62
+ .hero-title {
63
+ font-family: 'Unbounded', sans-serif;
64
+ font-size: clamp(4rem, 12vw, 10rem);
65
+ font-weight: 900;
66
+ text-transform: uppercase;
67
+ letter-spacing: -0.04em;
68
+ line-height: 0.9;
69
+ text-align: center;
70
+ position: relative;
71
+ z-index: 1;
72
+ }
73
+
74
+ .hero-title span {
75
+ display: block;
76
+ color: var(--red);
77
+ font-size: 0.4em;
78
+ letter-spacing: 0.2em;
79
+ margin-top: 16px;
80
+ }
81
+
82
+ .hero-sub {
83
+ font-family: 'JetBrains Mono', monospace;
84
+ font-size: clamp(14px, 1.5vw, 18px);
85
+ color: #888;
86
+ margin-top: 32px;
87
+ text-align: center;
88
+ max-width: 600px;
89
+ line-height: 1.6;
90
+ z-index: 1;
91
+ }
92
+
93
+ .hero-cmd {
94
+ margin-top: 48px;
95
+ background: #111;
96
+ border: 2px solid var(--red);
97
+ padding: 20px 40px;
98
+ font-family: 'JetBrains Mono', monospace;
99
+ font-size: clamp(16px, 2vw, 22px);
100
+ color: var(--yellow);
101
+ position: relative;
102
+ z-index: 1;
103
+ transition: all 0.2s;
104
+ text-decoration: none;
105
+ display: inline-block;
106
+ }
107
+
108
+ .hero-cmd:hover {
109
+ background: var(--red);
110
+ color: var(--black);
111
+ transform: translate(-4px, -4px);
112
+ box-shadow: 4px 4px 0 var(--yellow);
113
+ }
114
+
115
+ .hero-cmd::before {
116
+ content: '$ ';
117
+ color: var(--red);
118
+ }
119
+
120
+ .hero-cmd:hover::before {
121
+ color: var(--black);
122
+ }
123
+
124
+ .scroll-hint {
125
+ position: absolute;
126
+ bottom: 40px;
127
+ font-family: 'JetBrains Mono', monospace;
128
+ font-size: 12px;
129
+ color: #444;
130
+ letter-spacing: 0.3em;
131
+ text-transform: uppercase;
132
+ animation: pulse 2s ease-in-out infinite;
133
+ }
134
+
135
+ @keyframes pulse {
136
+ 0%, 100% { opacity: 0.3; }
137
+ 50% { opacity: 1; }
138
+ }
139
+
140
+ /* ── SECTION LAYOUT ── */
141
+ section {
142
+ padding: 100px 24px;
143
+ max-width: 1400px;
144
+ margin: 0 auto;
145
+ }
146
+
147
+ .section-red {
148
+ background: var(--red);
149
+ color: var(--black);
150
+ max-width: 100%;
151
+ padding: 80px 24px;
152
+ }
153
+
154
+ .section-cream {
155
+ background: var(--cream);
156
+ color: var(--black);
157
+ max-width: 100%;
158
+ padding: 80px 24px;
159
+ }
160
+
161
+ .section-inner {
162
+ max-width: 1400px;
163
+ margin: 0 auto;
164
+ }
165
+
166
+ .section-title {
167
+ font-family: 'Unbounded', sans-serif;
168
+ font-size: clamp(2rem, 5vw, 4rem);
169
+ font-weight: 900;
170
+ text-transform: uppercase;
171
+ letter-spacing: -0.02em;
172
+ margin-bottom: 48px;
173
+ line-height: 1;
174
+ }
175
+
176
+ .section-title::after {
177
+ content: ' ///';
178
+ color: var(--red);
179
+ }
180
+
181
+ .section-red .section-title::after {
182
+ color: var(--black);
183
+ }
184
+
185
+ .section-cream .section-title::after {
186
+ color: var(--red);
187
+ }
188
+
189
+ /* ── OUTPUT FILES ── */
190
+ .files-grid {
191
+ display: grid;
192
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
193
+ gap: 2px;
194
+ }
195
+
196
+ .file-card {
197
+ background: #111;
198
+ border: 2px solid #222;
199
+ padding: 28px;
200
+ transition: all 0.15s;
201
+ position: relative;
202
+ overflow: hidden;
203
+ }
204
+
205
+ .file-card:hover {
206
+ border-color: var(--red);
207
+ transform: translate(-2px, -2px);
208
+ box-shadow: 2px 2px 0 var(--red);
209
+ }
210
+
211
+ .file-card::before {
212
+ content: attr(data-num);
213
+ position: absolute;
214
+ top: -10px;
215
+ right: 10px;
216
+ font-family: 'Unbounded', sans-serif;
217
+ font-size: 80px;
218
+ font-weight: 900;
219
+ color: rgba(255, 0, 0, 0.06);
220
+ line-height: 1;
221
+ }
222
+
223
+ .file-name {
224
+ font-family: 'JetBrains Mono', monospace;
225
+ font-size: 15px;
226
+ color: var(--yellow);
227
+ margin-bottom: 8px;
228
+ }
229
+
230
+ .file-desc {
231
+ font-size: 14px;
232
+ color: #888;
233
+ line-height: 1.5;
234
+ }
235
+
236
+ /* ── FEATURES ── */
237
+ .features-list {
238
+ display: grid;
239
+ grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
240
+ gap: 0;
241
+ }
242
+
243
+ .feature {
244
+ padding: 32px;
245
+ border: 1px solid #222;
246
+ transition: all 0.15s;
247
+ }
248
+
249
+ .feature:hover {
250
+ background: #111;
251
+ border-color: var(--red);
252
+ }
253
+
254
+ .feature-name {
255
+ font-family: 'Unbounded', sans-serif;
256
+ font-size: 18px;
257
+ font-weight: 700;
258
+ text-transform: uppercase;
259
+ margin-bottom: 8px;
260
+ }
261
+
262
+ .feature-cmd {
263
+ font-family: 'JetBrains Mono', monospace;
264
+ font-size: 13px;
265
+ color: var(--yellow);
266
+ margin-bottom: 12px;
267
+ }
268
+
269
+ .feature-desc {
270
+ font-size: 14px;
271
+ color: #888;
272
+ line-height: 1.5;
273
+ }
274
+
275
+ /* ── SCORE DEMO ── */
276
+ .score-demo {
277
+ background: #111;
278
+ border: 2px solid var(--red);
279
+ padding: 40px;
280
+ font-family: 'JetBrains Mono', monospace;
281
+ font-size: 14px;
282
+ line-height: 2;
283
+ overflow-x: auto;
284
+ color: #ccc;
285
+ }
286
+
287
+ .score-grade {
288
+ font-family: 'Unbounded', sans-serif;
289
+ font-size: 64px;
290
+ font-weight: 900;
291
+ color: var(--red);
292
+ display: inline;
293
+ }
294
+
295
+ /* ── COMMANDS ── */
296
+ .commands-grid {
297
+ display: grid;
298
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
299
+ gap: 2px;
300
+ }
301
+
302
+ .command-card {
303
+ background: var(--black);
304
+ padding: 32px;
305
+ border: 2px solid rgba(255,0,0,0.3);
306
+ transition: all 0.15s;
307
+ }
308
+
309
+ .command-card:hover {
310
+ border-color: var(--red);
311
+ background: rgba(255, 0, 0, 0.05);
312
+ }
313
+
314
+ .command-name {
315
+ font-family: 'JetBrains Mono', monospace;
316
+ font-size: 18px;
317
+ font-weight: 700;
318
+ color: var(--yellow);
319
+ margin-bottom: 12px;
320
+ }
321
+
322
+ .command-desc {
323
+ font-size: 14px;
324
+ color: #ccc;
325
+ line-height: 1.5;
326
+ }
327
+
328
+ /* ── STATS STRIP ── */
329
+ .stats-strip {
330
+ display: flex;
331
+ flex-wrap: wrap;
332
+ border-top: 2px solid var(--red);
333
+ border-bottom: 2px solid var(--red);
334
+ }
335
+
336
+ .stat {
337
+ flex: 1;
338
+ min-width: 150px;
339
+ padding: 32px 24px;
340
+ text-align: center;
341
+ border-right: 1px solid #222;
342
+ }
343
+
344
+ .stat:last-child { border-right: none; }
345
+
346
+ .stat-value {
347
+ font-family: 'Unbounded', sans-serif;
348
+ font-size: 48px;
349
+ font-weight: 900;
350
+ color: var(--red);
351
+ line-height: 1;
352
+ }
353
+
354
+ .stat-label {
355
+ font-family: 'JetBrains Mono', monospace;
356
+ font-size: 11px;
357
+ color: #666;
358
+ text-transform: uppercase;
359
+ letter-spacing: 0.15em;
360
+ margin-top: 8px;
361
+ }
362
+
363
+ /* ── FOOTER ── */
364
+ footer {
365
+ padding: 60px 24px;
366
+ text-align: center;
367
+ border-top: 4px solid var(--red);
368
+ }
369
+
370
+ footer a {
371
+ text-decoration: underline;
372
+ text-underline-offset: 4px;
373
+ }
374
+
375
+ footer a:hover {
376
+ color: var(--red);
377
+ }
378
+
379
+ .footer-title {
380
+ font-family: 'Unbounded', sans-serif;
381
+ font-size: 24px;
382
+ font-weight: 900;
383
+ text-transform: uppercase;
384
+ margin-bottom: 16px;
385
+ }
386
+
387
+ .footer-links {
388
+ display: flex;
389
+ gap: 32px;
390
+ justify-content: center;
391
+ margin-top: 24px;
392
+ font-family: 'JetBrains Mono', monospace;
393
+ font-size: 14px;
394
+ }
395
+
396
+ .footer-copy {
397
+ margin-top: 48px;
398
+ font-size: 12px;
399
+ color: #444;
400
+ }
401
+
402
+ /* ── MARQUEE ── */
403
+ .marquee {
404
+ overflow: hidden;
405
+ white-space: nowrap;
406
+ border-top: 2px solid #222;
407
+ border-bottom: 2px solid #222;
408
+ padding: 12px 0;
409
+ font-family: 'JetBrains Mono', monospace;
410
+ font-size: 13px;
411
+ color: #444;
412
+ }
413
+
414
+ .marquee-inner {
415
+ display: inline-block;
416
+ animation: marquee 30s linear infinite;
417
+ }
418
+
419
+ @keyframes marquee {
420
+ 0% { transform: translateX(0); }
421
+ 100% { transform: translateX(-50%); }
422
+ }
423
+
424
+ /* ── RESPONSIVE ── */
425
+ @media (max-width: 768px) {
426
+ .features-list { grid-template-columns: 1fr; }
427
+ .commands-grid { grid-template-columns: 1fr; }
428
+ .files-grid { grid-template-columns: 1fr; }
429
+ .stats-strip { flex-direction: column; }
430
+ .stat { border-right: none; border-bottom: 1px solid #222; }
431
+ .hero-cmd { font-size: 14px; padding: 16px 24px; }
432
+ }