emily-css 1.0.20 → 1.0.22

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/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ All notable changes to `emily-css` are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## v1.0.22 — May 2026
8
+
9
+ **· Improve purge extraction and package robustness tests**
10
+
11
+ ---
12
+ ## v1.0.21 — May 2026
13
+
14
+ **"Include bundled showcase template**
15
+
16
+ ---
7
17
  ## v1.0.20 — May 2026
8
18
 
9
19
  **replace folder structure template to tempalates**
package/bin/emilyui.js CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ const path = require("path");
4
+
3
5
  const command = process.argv[2];
6
+ const packageJson = require(path.join(__dirname, "..", "package.json"));
4
7
 
5
8
  if (command === "init") {
6
9
  require("../src/init.js");
@@ -11,6 +14,8 @@ if (command === "init") {
11
14
  require("../src/watch.js");
12
15
  } else if (command === "showcase") {
13
16
  require("../src/showcase.js");
17
+ } else if (command === "version" || command === "--version" || command === "-v") {
18
+ console.log(packageJson.version);
14
19
  } else if (command === "help") {
15
20
  console.log(`
16
21
  emily-css — Config-driven CSS framework generator
@@ -20,6 +25,7 @@ if (command === "init") {
20
25
  emily-css build Generate production CSS to the configured output path
21
26
  emily-css watch Dev mode: watch for changes and rebuild
22
27
  emily-css showcase Launch the component showcase in your browser
28
+ emily-css version Show installed version
23
29
  emily-css help Show this help text
24
30
 
25
31
  npm scripts (added by init):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emily-css",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "A config-driven utility CSS framework. Define your brand once, generate the CSS.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -52,7 +52,6 @@
52
52
  "chalk": "^4.1.2",
53
53
  "chokidar": "^5.0.0",
54
54
  "cross-spawn": "^7.0.6",
55
- "emily-css": "^1.0.8",
56
55
  "enquirer": "^2.4.1",
57
56
  "fast-glob": "^3.3.3",
58
57
  "ora": "^5.4.1"
package/src/purge.js CHANGED
@@ -57,7 +57,7 @@ function getAllFiles(dir, extensions = DEFAULT_EXTENSIONS) {
57
57
 
58
58
  function extractClassNames(content) {
59
59
  const classNames = new Set();
60
- const classRegex = /class\s*=\s*["']([^"']+)["']/g;
60
+ const classRegex = /(?:class|className)\s*=\s*["']([^"']+)["']/g;
61
61
  let match;
62
62
 
63
63
  while ((match = classRegex.exec(content)) !== null) {
@@ -76,6 +76,24 @@ function extractClassNames(content) {
76
76
  });
77
77
  }
78
78
 
79
+ const templateStringRegex = /`([^`]+)`/g;
80
+
81
+ while ((match = templateStringRegex.exec(content)) !== null) {
82
+ const possibleClasses = match[1].split(/\s+/);
83
+
84
+ possibleClasses.forEach((cls) => {
85
+ const cleaned = cls.trim();
86
+
87
+ if (
88
+ cleaned &&
89
+ /^[a-zA-Z0-9:_./-]+$/.test(cleaned) &&
90
+ /[-:]/.test(cleaned)
91
+ ) {
92
+ classNames.add(cleaned);
93
+ }
94
+ });
95
+ }
96
+
79
97
  return classNames;
80
98
  }
81
99
 
package/src/watch.js CHANGED
@@ -21,7 +21,9 @@ function readConfig() {
21
21
  const configPath = path.join(process.cwd(), "emily.config.json");
22
22
 
23
23
  if (!fs.existsSync(configPath)) {
24
- console.error('\n emily-css: No config found. Run "emily-css init" first.\n');
24
+ console.error(
25
+ '\n emily-css: No config found. Run "emily-css init" first.\n',
26
+ );
25
27
  process.exit(1);
26
28
  }
27
29
 
@@ -46,7 +48,9 @@ function shouldIgnore(filePath) {
46
48
  "coverage/",
47
49
  ".cache/",
48
50
  ".vite/",
49
- ].some((part) => normalised.includes("/" + part) || normalised.startsWith(part));
51
+ ].some(
52
+ (part) => normalised.includes("/" + part) || normalised.startsWith(part),
53
+ );
50
54
  }
51
55
 
52
56
  function runQuietly(fn) {
@@ -83,7 +87,9 @@ function getScanFiles(config) {
83
87
  ".njk",
84
88
  ".liquid",
85
89
  ".hbs",
90
+ ".js",
86
91
  ".jsx",
92
+ ".ts",
87
93
  ".tsx",
88
94
  ".vue",
89
95
  ".php",
@@ -124,7 +130,9 @@ function collectUsedClasses(config) {
124
130
 
125
131
  function getClassDiff(currentClasses) {
126
132
  const added = [...currentClasses].filter((cls) => !previousClasses.has(cls));
127
- const removed = [...previousClasses].filter((cls) => !currentClasses.has(cls));
133
+ const removed = [...previousClasses].filter(
134
+ (cls) => !currentClasses.has(cls),
135
+ );
128
136
 
129
137
  previousClasses = new Set(currentClasses);
130
138
 
@@ -141,35 +149,24 @@ function formatClassList(classes) {
141
149
  }
142
150
 
143
151
  function printSummary({ currentClasses, result, added, removed }) {
144
- const reduction = (
145
- ((result.originalSize - result.outputSize) / result.originalSize) *
146
- 100
147
- ).toFixed(1);
152
+ const reduction =
153
+ currentClasses.size === 0
154
+ ? "100.0"
155
+ : (
156
+ ((result.originalSize - result.outputSize) / result.originalSize) *
157
+ 100
158
+ ).toFixed(1);
159
+
160
+ const sizeKb =
161
+ currentClasses.size === 0 ? "0.0" : (result.outputSize / 1024).toFixed(1);
148
162
 
149
- const sizeKb = (result.outputSize / 1024).toFixed(1);
150
163
  const outputPath = result.outputPath
151
164
  ? path.relative(process.cwd(), result.outputPath)
152
165
  : "emily.min.css";
153
166
 
154
167
  const time = new Date().toLocaleTimeString();
155
168
 
156
- console.log(
157
- chalk.green("✓ " + time + " updated") +
158
- chalk.gray(
159
- " | " +
160
- currentClasses.size +
161
- " classes | " +
162
- sizeKb +
163
- " KB | " +
164
- reduction +
165
- "% reduced | " +
166
- outputPath,
167
- ),
168
- );
169
-
170
- if (!hasRunOnce) return;
171
-
172
- if (removed.length > 0) {
169
+ if (hasRunOnce && removed.length > 0) {
173
170
  console.log(
174
171
  chalk.red(
175
172
  "− removed " +
@@ -180,16 +177,27 @@ function printSummary({ currentClasses, result, added, removed }) {
180
177
  );
181
178
  }
182
179
 
183
- if (added.length > 0) {
180
+ if (hasRunOnce && added.length > 0) {
184
181
  console.log(
185
182
  chalk.green(
186
- "+ added " +
187
- added.length +
188
- " class" +
189
- (added.length === 1 ? "" : "es"),
183
+ "+ added " + added.length + " class" + (added.length === 1 ? "" : "es"),
190
184
  ) + chalk.gray(" (" + formatClassList(added) + ")"),
191
185
  );
192
186
  }
187
+
188
+ console.log(
189
+ chalk.green("✓ " + time + " updated") +
190
+ chalk.gray(
191
+ " | " +
192
+ currentClasses.size +
193
+ " classes | " +
194
+ sizeKb +
195
+ " KB | " +
196
+ reduction +
197
+ "% reduced | " +
198
+ outputPath,
199
+ ),
200
+ );
193
201
  }
194
202
 
195
203
  function runProductionUpdate(filePath) {
@@ -232,10 +240,7 @@ function runProductionUpdate(filePath) {
232
240
  }
233
241
 
234
242
  function getWatchPaths(config) {
235
- return [
236
- config.purge?.sourceDir || ".",
237
- "emily.config.json",
238
- ];
243
+ return [config.purge?.sourceDir || ".", "emily.config.json"];
239
244
  }
240
245
 
241
246
  function queueUpdate(filePath) {
@@ -247,14 +252,19 @@ function runWatch() {
247
252
  const config = readConfig();
248
253
  const watchPaths = getWatchPaths(config);
249
254
 
250
- console.log("\n👀 EmilyUI is watching...");
251
- console.log(chalk.gray(" Project: " + (config.purge?.projectType || "Unknown")));
252
- console.log(chalk.gray(" Output: " + (config.output?.css || "dist/emily.min.css")));
253
- console.log(chalk.gray(" Watching:"));
255
+ console.log("");
256
+ console.log(chalk.cyan("👀 EmilyUI is watching..."));
257
+ console.log(
258
+ chalk.gray(" Project: " + (config.purge?.projectType || "Unknown")),
259
+ );
260
+ console.log(
261
+ chalk.gray(" Output: " + (config.output?.css || "dist/emily.min.css")),
262
+ );
263
+ console.log(chalk.gray(" Watching:"));
254
264
 
255
- watchPaths.forEach((item) => {
256
- console.log(chalk.gray(" - " + item));
257
- });
265
+ watchPaths.forEach((item) => {
266
+ console.log(chalk.gray(" - " + item));
267
+ });
258
268
 
259
269
  runQuietly(() => ensureFullFramework());
260
270
  runProductionUpdate();
@@ -0,0 +1,992 @@
1
+ <!doctype html>
2
+ <html lang="en" style="scroll-behavior: smooth;">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>EmilyUI Showcase</title>
7
+ <link rel="stylesheet" href="./dist/emily.min.css">
8
+ <style>
9
+ html {
10
+ scroll-behavior: smooth;
11
+ }
12
+
13
+ [id] {
14
+ scroll-margin-top: 7rem;
15
+ }
16
+ </style>
17
+ </head>
18
+
19
+ <body class="bg-neutral-10 text-neutral-90 font-inter p-4 md:p-6">
20
+ <header class="sticky top-4 z-50 max-w-7xl mx-auto mb-6">
21
+ <nav class="flex items-center justify-between bg-white border border-neutral-20 rounded-lg shadow-md p-4">
22
+ <div class="flex items-center gap-3">
23
+ <span class="inline-flex items-center justify-center bg-primary-80 text-white rounded-md w-10 h-10 font-bold">
24
+ E
25
+ </span>
26
+ <div>
27
+ <p class="font-bold text-lg">EmilyUI</p>
28
+ <p class="text-sm text-neutral-70">Component showcase</p>
29
+ </div>
30
+ </div>
31
+
32
+ <div class="hidden md:flex items-center gap-4 text-base font-medium">
33
+ <a class="text-neutral-80 hover:text-primary-80" href="#colours">Colours</a>
34
+ <a class="text-neutral-80 hover:text-primary-80" href="#design-tokens">Tokens</a>
35
+ <a class="text-neutral-80 hover:text-primary-80" href="#buttons">Buttons</a>
36
+ <a class="text-neutral-80 hover:text-primary-80" href="#forms">Forms</a>
37
+ <a class="text-neutral-80 hover:text-primary-80" href="#component-library">Components</a>
38
+ <a class="text-neutral-80 hover:text-primary-80" href="#utility-coverage">Utilities</a>
39
+ <a class="text-neutral-80 hover:text-primary-80" href="#accessibility-examples">A11y</a>
40
+ <a class="text-neutral-80 hover:text-primary-80" href="#patterns">Patterns</a>
41
+ </div>
42
+ </nav>
43
+ </header>
44
+
45
+ <main id="main-content" class="max-w-7xl mx-auto">
46
+ <section class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
47
+ <div class="bg-white rounded-lg shadow-md border border-neutral-20 p-8">
48
+ <span class="inline-block bg-primary-20 text-primary-90 px-3 py-1 rounded-full text-sm font-bold mb-4">
49
+ Utility-first CSS
50
+ </span>
51
+
52
+ <h1 class="text-3xl md:text-4xl font-bold mb-4 text-primary-90">
53
+ Build consistent interfaces without a heavy frontend setup.
54
+ </h1>
55
+
56
+ <p class="text-lg leading-relaxed mb-6 text-neutral-80">
57
+ This page tests EmilyUI colour, spacing, typography, layout, buttons, forms, alerts, cards, badges and responsive utilities.
58
+ </p>
59
+
60
+ <div class="flex flex-wrap gap-3">
61
+ <a href="#buttons" class="inline-flex items-center justify-center bg-primary-80 text-white px-5 py-3 rounded-md font-semibold shadow hover:bg-primary-90 focus-visible:ring-2 ring-primary-80">
62
+ View components
63
+ </a>
64
+
65
+ <a href="#colours" class="inline-flex items-center justify-center bg-white text-primary-90 border border-primary-80 px-5 py-3 rounded-md font-semibold hover:bg-primary-10">
66
+ View tokens
67
+ </a>
68
+ </div>
69
+ </div>
70
+
71
+ <div class="bg-primary-90 text-white rounded-lg shadow-md p-8">
72
+ <h2 class="text-2xl font-bold mb-4">Quick stats</h2>
73
+
74
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
75
+ <div class="bg-primary-80 rounded-md p-4">
76
+ <p class="text-sm opacity-75">Components</p>
77
+ <p class="text-3xl font-bold">12</p>
78
+ </div>
79
+
80
+ <div class="bg-primary-80 rounded-md p-4">
81
+ <p class="text-sm opacity-75">Token groups</p>
82
+ <p class="text-3xl font-bold">6</p>
83
+ </div>
84
+
85
+ <div class="bg-primary-80 rounded-md p-4">
86
+ <p class="text-sm opacity-75">Layouts</p>
87
+ <p class="text-3xl font-bold">4</p>
88
+ </div>
89
+
90
+ <div class="bg-primary-80 rounded-md p-4">
91
+ <p class="text-sm opacity-75">Focus states</p>
92
+ <p class="text-3xl font-bold">Yes</p>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </section>
97
+
98
+ <section id="colours" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
99
+ <h2 class="text-2xl font-bold mb-4">Colour tokens</h2>
100
+
101
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
102
+ <div class="rounded-md overflow-hidden border border-neutral-20">
103
+ <div class="bg-primary-80 h-20"></div>
104
+ <div class="p-4">
105
+ <p class="font-bold">Primary</p>
106
+ <p class="text-sm text-neutral-70">bg-primary-80</p>
107
+ </div>
108
+ </div>
109
+
110
+ <div class="rounded-md overflow-hidden border border-neutral-20">
111
+ <div class="bg-secondary-80 h-20"></div>
112
+ <div class="p-4">
113
+ <p class="font-bold">Secondary</p>
114
+ <p class="text-sm text-neutral-70">bg-secondary-80</p>
115
+ </div>
116
+ </div>
117
+
118
+ <div class="rounded-md overflow-hidden border border-neutral-20">
119
+ <div class="bg-success-80 h-20"></div>
120
+ <div class="p-4">
121
+ <p class="font-bold">Success</p>
122
+ <p class="text-sm text-neutral-70">bg-success-80</p>
123
+ </div>
124
+ </div>
125
+
126
+ <div class="rounded-md overflow-hidden border border-neutral-20">
127
+ <div class="bg-warning-80 h-20"></div>
128
+ <div class="p-4">
129
+ <p class="font-bold">Warning</p>
130
+ <p class="text-sm text-neutral-70">bg-warning-80</p>
131
+ </div>
132
+ </div>
133
+
134
+ <div class="rounded-md overflow-hidden border border-neutral-20">
135
+ <div class="bg-error-80 h-20"></div>
136
+ <div class="p-4">
137
+ <p class="font-bold">Error</p>
138
+ <p class="text-sm text-neutral-70">bg-error-80</p>
139
+ </div>
140
+ </div>
141
+
142
+ <div class="rounded-md overflow-hidden border border-neutral-20">
143
+ <div class="bg-neutral-80 h-20"></div>
144
+ <div class="p-4">
145
+ <p class="font-bold">Neutral</p>
146
+ <p class="text-sm text-neutral-70">bg-neutral-80</p>
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </section>
151
+
152
+ <section id="buttons" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6 background-neutral-100">
153
+ <h2 class="text-2xl font-bold mb-4">Buttons</h2>
154
+
155
+ <div class="flex flex-wrap gap-3">
156
+ <button class="bg-primary-80 text-white px-5 py-3 rounded-md font-semibold shadow hover:bg-primary-90 focus-visible:ring-2 ring-primary-80">
157
+ Primary
158
+ </button>
159
+
160
+ <button class="bg-secondary-80 text-white px-5 py-3 rounded-md font-semibold shadow hover:bg-secondary-90 focus-visible:ring-2 ring-secondary-80">
161
+ Secondary
162
+ </button>
163
+
164
+ <button class="bg-white text-primary-90 border border-primary-80 px-5 py-3 rounded-md font-semibold hover:bg-primary-10 focus-visible:ring-2 ring-primary-80">
165
+ Outline
166
+ </button>
167
+
168
+ <button class="bg-neutral-20 text-neutral-90 px-5 py-3 rounded-md font-semibold hover:bg-neutral-30">
169
+ Neutral
170
+ </button>
171
+
172
+ <button class="bg-error-80 text-white px-5 py-3 rounded-md font-semibold hover:bg-error-90">
173
+ Destructive
174
+ </button>
175
+ </div>
176
+ </section>
177
+
178
+ <section id="cards" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6">
179
+ <article class="bg-white rounded-lg shadow-md border border-neutral-20 p-6">
180
+ <span class="inline-block bg-primary-20 text-primary-90 px-3 py-1 rounded-full text-sm font-bold mb-4">Card</span>
181
+ <h2 class="text-xl font-bold mb-2">Reusable patterns</h2>
182
+ <p class="text-neutral-80 mb-4">Use utility classes to build patterns that work across static sites, Nuxt, Drupal and more.</p>
183
+ <a href="#" class="font-semibold text-primary-80 underline">Read more</a>
184
+ </article>
185
+
186
+ <article class="bg-white rounded-lg shadow-md border border-neutral-20 p-6">
187
+ <span class="inline-block bg-success-20 text-success-90 px-3 py-1 rounded-full text-sm font-bold mb-4">Accessible</span>
188
+ <h2 class="text-xl font-bold mb-2">Clear defaults</h2>
189
+ <p class="text-neutral-80 mb-4">Spacing, contrast and focus utilities make common interface decisions quicker and more consistent.</p>
190
+ <a href="#" class="font-semibold text-primary-80 underline">View guidance</a>
191
+ </article>
192
+
193
+ <article class="bg-white rounded-lg shadow-md border border-neutral-20 p-6">
194
+ <span class="inline-block bg-secondary-20 text-secondary-90 px-3 py-1 rounded-full text-sm font-bold mb-4">Portable</span>
195
+ <h2 class="text-xl font-bold mb-2">Framework agnostic</h2>
196
+ <p class="text-neutral-80 mb-4">Generate CSS from one config and use it where build pipelines are awkward or locked down.</p>
197
+ <a href="#" class="font-semibold text-primary-80 underline">See setup</a>
198
+ </article>
199
+ </section>
200
+
201
+ <section id="forms" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6 shadow-lg">
202
+ <h2 class="text-2xl font-bold mb-4">Form elements</h2>
203
+
204
+ <form class="grid grid-cols-1 md:grid-cols-2 gap-4">
205
+ <div>
206
+ <label class="block font-semibold mb-2" for="name">Name</label>
207
+ <input id="name" class="w-full border border-neutral-40 rounded-md p-3 focus-visible:ring-2 ring-primary-80" type="text" value="EmilyUI">
208
+ </div>
209
+
210
+ <div>
211
+ <label class="block font-semibold mb-2" for="type">Project type</label>
212
+ <select id="type" class="w-full border border-neutral-40 rounded-md p-3 pr-12 focus-visible:ring-2 ring-primary-80">
213
+ <option>Nuxt</option>
214
+ <option>Drupal</option>
215
+ <option>Static</option>
216
+ </select>
217
+ </div>
218
+
219
+ <div class="col-span-full">
220
+ <label class="block font-semibold mb-2" for="message">Notes</label>
221
+ <textarea id="message" class="w-full border border-neutral-40 rounded-md p-3 focus-visible:ring-2 ring-primary-80" rows="4">This tests form spacing, borders and focus states.</textarea>
222
+ </div>
223
+
224
+ <div class="col-span-full flex items-center gap-3">
225
+ <input id="check" class="w-6 h-6 accent-primary-80" type="checkbox" checked>
226
+ <label for="check">Use accessible defaults where possible</label>
227
+ </div>
228
+ </form>
229
+ </section>
230
+
231
+ <section id="alerts" class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
232
+ <div class="bg-success-20 border border-success-80 rounded-md p-4">
233
+ <h2 class="text-lg font-bold text-success-90 mb-1">Success alert</h2>
234
+ <p class="text-success-90">Your CSS has been generated successfully.</p>
235
+ </div>
236
+
237
+ <div class="bg-warning-20 border border-warning-80 rounded-md p-4">
238
+ <h2 class="text-lg font-bold text-warning-90 mb-1">Warning alert</h2>
239
+ <p class="text-warning-90">Check your scan paths before publishing.</p>
240
+ </div>
241
+
242
+ <div class="bg-error-20 border border-error-80 rounded-md p-4">
243
+ <h2 class="text-lg font-bold text-error-90 mb-1">Error alert</h2>
244
+ <p class="text-error-90">Something failed during the build step.</p>
245
+ </div>
246
+
247
+ <div class="bg-secondary-20 border border-secondary-80 rounded-md p-4">
248
+ <h2 class="text-lg font-bold text-secondary-90 mb-1">Info alert</h2>
249
+ <p class="text-secondary-90">EmilyUI works best when config is kept simple.</p>
250
+ </div>
251
+ </section>
252
+
253
+ <section class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
254
+ <h2 class="text-2xl font-bold mb-4">Table</h2>
255
+
256
+ <div class="overflow-x-auto">
257
+ <table class="w-full border-collapse">
258
+ <thead>
259
+ <tr class="bg-neutral-20">
260
+ <th class="text-left p-3 border border-neutral-30">Component</th>
261
+ <th class="text-left p-3 border border-neutral-30">Classes tested</th>
262
+ <th class="text-left p-3 border border-neutral-30">Status</th>
263
+ </tr>
264
+ </thead>
265
+ <tbody>
266
+ <tr>
267
+ <td class="p-3 border border-neutral-30">Buttons</td>
268
+ <td class="p-3 border border-neutral-30">bg, text, px, py, rounded, hover</td>
269
+ <td class="p-3 border border-neutral-30"><span class="bg-success-20 text-success-90 px-2 py-1 rounded-full text-sm font-bold">Pass</span></td>
270
+ </tr>
271
+ <tr>
272
+ <td class="p-3 border border-neutral-30">Cards</td>
273
+ <td class="p-3 border border-neutral-30">shadow, border, grid, gap</td>
274
+ <td class="p-3 border border-neutral-30"><span class="bg-success-20 text-success-90 px-2 py-1 rounded-full text-sm font-bold">Pass</span></td>
275
+ </tr>
276
+ <tr>
277
+ <td class="p-3 border border-neutral-30">Forms</td>
278
+ <td class="p-3 border border-neutral-30">input, border, ring, accent</td>
279
+ <td class="p-3 border border-neutral-30"><span class="bg-warning-20 text-warning-90 px-2 py-1 rounded-full text-sm font-bold">Check</span></td>
280
+ </tr>
281
+ </tbody>
282
+ </table>
283
+ </div>
284
+ </section>
285
+
286
+
287
+ <section id="design-tokens" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
288
+ <h2 class="text-2xl font-bold mb-2">Design tokens</h2>
289
+ <p class="text-neutral-80 mb-6">A quick reference for the core EmilyUI tokens generated from your config.</p>
290
+
291
+ <h3 class="text-xl font-bold mb-3">Colour scales</h3>
292
+ <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-8">
293
+ <div><div class="bg-primary-10 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">primary-10</p></div>
294
+ <div><div class="bg-primary-20 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">primary-20</p></div>
295
+ <div><div class="bg-primary-40 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">primary-40</p></div>
296
+ <div><div class="bg-primary-60 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">primary-60</p></div>
297
+ <div><div class="bg-primary-80 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">primary-80</p></div>
298
+ <div><div class="bg-primary-100 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">primary-100</p></div>
299
+ </div>
300
+
301
+ <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-8">
302
+ <div><div class="bg-secondary-10 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">secondary-10</p></div>
303
+ <div><div class="bg-secondary-20 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">secondary-20</p></div>
304
+ <div><div class="bg-secondary-40 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">secondary-40</p></div>
305
+ <div><div class="bg-secondary-60 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">secondary-60</p></div>
306
+ <div><div class="bg-secondary-80 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">secondary-80</p></div>
307
+ <div><div class="bg-secondary-100 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">secondary-100</p></div>
308
+ </div>
309
+
310
+ <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-8">
311
+ <div><div class="bg-success-10 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">success-10</p></div>
312
+ <div><div class="bg-success-20 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">success-20</p></div>
313
+ <div><div class="bg-success-40 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">success-40</p></div>
314
+ <div><div class="bg-success-60 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">success-60</p></div>
315
+ <div><div class="bg-success-80 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">success-80</p></div>
316
+ <div><div class="bg-success-100 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">success-100</p></div>
317
+ </div>
318
+
319
+ <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-8">
320
+ <div><div class="bg-warning-10 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">warning-10</p></div>
321
+ <div><div class="bg-warning-20 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">warning-20</p></div>
322
+ <div><div class="bg-warning-40 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">warning-40</p></div>
323
+ <div><div class="bg-warning-60 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">warning-60</p></div>
324
+ <div><div class="bg-warning-80 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">warning-80</p></div>
325
+ <div><div class="bg-warning-100 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">warning-100</p></div>
326
+ </div>
327
+
328
+ <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-8">
329
+ <div><div class="bg-error-10 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">error-10</p></div>
330
+ <div><div class="bg-error-20 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">error-20</p></div>
331
+ <div><div class="bg-error-40 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">error-40</p></div>
332
+ <div><div class="bg-error-60 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">error-60</p></div>
333
+ <div><div class="bg-error-80 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">error-80</p></div>
334
+ <div><div class="bg-error-100 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">error-100</p></div>
335
+ </div>
336
+
337
+ <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-8">
338
+ <div><div class="bg-neutral-10 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">neutral-10</p></div>
339
+ <div><div class="bg-neutral-20 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">neutral-20</p></div>
340
+ <div><div class="bg-neutral-40 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">neutral-40</p></div>
341
+ <div><div class="bg-neutral-60 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">neutral-60</p></div>
342
+ <div><div class="bg-neutral-80 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">neutral-80</p></div>
343
+ <div><div class="bg-neutral-100 h-12 rounded-md border border-neutral-20"></div><p class="text-sm mt-2">neutral-100</p></div>
344
+ </div>
345
+
346
+ <h3 class="text-xl font-bold mb-3">Spacing</h3>
347
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
348
+ <div class="border border-neutral-20 rounded-md p-4"><div class="bg-primary-80 h-4 w-4 rounded-sm"></div><p class="text-sm mt-2">space 4</p></div>
349
+ <div class="border border-neutral-20 rounded-md p-4"><div class="bg-primary-80 h-6 w-6 rounded-sm"></div><p class="text-sm mt-2">space 6</p></div>
350
+ <div class="border border-neutral-20 rounded-md p-4"><div class="bg-primary-80 h-8 w-8 rounded-sm"></div><p class="text-sm mt-2">space 8</p></div>
351
+ <div class="border border-neutral-20 rounded-md p-4"><div class="bg-primary-80 h-12 w-12 rounded-sm"></div><p class="text-sm mt-2">space 12</p></div>
352
+ </div>
353
+
354
+ <h3 class="text-xl font-bold mb-3">Typography</h3>
355
+ <div class="space-y-3">
356
+ <p class="text-xs">text-xs: Small helper text</p>
357
+ <p class="text-sm">text-sm: Secondary body text</p>
358
+ <p class="text-base">text-base: Default body text</p>
359
+ <p class="text-lg">text-lg: Larger body text</p>
360
+ <p class="text-xl font-semibold">text-xl: Section heading</p>
361
+ <p class="text-2xl font-bold">text-2xl: Component heading</p>
362
+ <p class="text-3xl md:text-4xl font-bold">text-4xl: Page heading</p>
363
+ </div>
364
+ </section>
365
+
366
+ <section id="component-library" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
367
+ <h2 class="text-2xl font-bold mb-2">Component library</h2>
368
+ <p class="text-neutral-80 mb-6">Common interface components built only with EmilyUI utility classes.</p>
369
+
370
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
371
+ <div class="border border-neutral-20 rounded-lg p-5">
372
+ <h3 class="text-xl font-bold mb-3">Breadcrumbs</h3>
373
+ <nav aria-label="Breadcrumb">
374
+ <ol class="flex gap-2 text-sm text-primary-100">
375
+ <li><a href="#" class="text-primary-80 underline">Home</a></li>
376
+ <li>/</li>
377
+ <li><a href="#" class="text-primary-80 underline">Components</a></li>
378
+ <li>/</li>
379
+ <li aria-current="page">Breadcrumbs</li>
380
+ </ol>
381
+ </nav>
382
+ </div>
383
+
384
+ <div class="border border-neutral-20 rounded-lg p-5">
385
+ <h3 class="text-xl font-bold mb-3">Pagination</h3>
386
+ <nav class="flex gap-2" aria-label="Pagination">
387
+ <a href="#" class="border border-neutral-30 px-3 py-2 rounded-md">Previous</a>
388
+ <a href="#" class="bg-primary-80 text-white px-3 py-2 rounded-md">1</a>
389
+ <a href="#" class="border border-neutral-30 px-3 py-2 rounded-md">2</a>
390
+ <a href="#" class="border border-neutral-30 px-3 py-2 rounded-md">Next</a>
391
+ </nav>
392
+ </div>
393
+
394
+ <div class="border border-neutral-20 rounded-lg p-5">
395
+ <h3 class="text-xl font-bold mb-3">Tabs</h3>
396
+ <div class="flex gap-2 border-b border-neutral-30">
397
+ <button class="px-4 py-2 border-b-2 border-primary-80 font-semibold">Overview</button>
398
+ <button class="px-4 py-2 text-neutral-70">Usage</button>
399
+ <button class="px-4 py-2 text-neutral-70">Code</button>
400
+ </div>
401
+ </div>
402
+
403
+ <div class="border border-neutral-20 rounded-lg p-5">
404
+ <h3 class="text-xl font-bold mb-3">Progress</h3>
405
+ <div class="bg-neutral-20 rounded-full h-4 overflow-hidden">
406
+ <div class="bg-primary-80 h-4 w-72"></div>
407
+ </div>
408
+ <p class="text-sm text-neutral-70 mt-2">75% complete</p>
409
+ </div>
410
+
411
+ <div class="border border-neutral-20 rounded-lg p-5">
412
+ <h3 class="text-xl font-bold mb-3">Notification banner</h3>
413
+ <div class="bg-secondary-20 border-l-4 border-secondary-80 p-4">
414
+ <p class="font-bold text-secondary-90">Information</p>
415
+ <p class="text-secondary-90">This is a standard notification message.</p>
416
+ </div>
417
+ </div>
418
+
419
+ <div class="border border-neutral-20 rounded-lg p-5">
420
+ <h3 class="text-xl font-bold mb-3">Modal preview</h3>
421
+ <div class="border border-neutral-30 rounded-lg p-5 shadow-lg">
422
+ <h4 class="text-lg font-bold mb-2">Confirm action</h4>
423
+ <p class="text-neutral-80 mb-4">This shows modal spacing and button grouping.</p>
424
+ <div class="flex gap-3">
425
+ <button class="bg-primary-80 text-white px-4 py-2 rounded-md font-semibold">Confirm</button>
426
+ <button class="border border-neutral-40 px-4 py-2 rounded-md">Cancel</button>
427
+ </div>
428
+ </div>
429
+ </div>
430
+
431
+ <div class="border border-neutral-20 rounded-lg p-5">
432
+ <h3 class="text-xl font-bold mb-3">Accordion</h3>
433
+ <details class="border border-neutral-30 rounded-md p-4">
434
+ <summary class="font-semibold cursor-pointer">What is EmilyUI?</summary>
435
+ <p class="text-neutral-80 mt-3">A config-driven CSS utility system for constrained projects.</p>
436
+ </details>
437
+ </div>
438
+
439
+ <div class="border border-neutral-20 rounded-lg p-5">
440
+ <h3 class="text-xl font-bold mb-3">Stat card</h3>
441
+ <div class="bg-primary-10 border border-primary-30 rounded-lg p-5">
442
+ <p class="text-sm text-primary-90">CSS generated</p>
443
+ <p class="text-3xl md:text-4xl font-bold text-primary-90">100%</p>
444
+ </div>
445
+ </div>
446
+ </div>
447
+ </section>
448
+
449
+
450
+ <section id="utility-coverage" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
451
+ <h2 class="text-2xl font-bold mb-2">Utility coverage</h2>
452
+ <p class="text-neutral-80 mb-6">A practical check that common utility groups are being generated and surviving the production build.</p>
453
+
454
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
455
+ <div class="border border-neutral-20 rounded-md p-4">
456
+ <h3 class="font-bold mb-2">Display</h3>
457
+ <div class="flex gap-2">
458
+ <span class="inline-block bg-primary-20 px-2 py-1 rounded-sm text-sm">block</span>
459
+ <span class="inline-flex bg-primary-20 px-2 py-1 rounded-sm text-sm">inline-flex</span>
460
+ </div>
461
+ </div>
462
+
463
+ <div class="border border-neutral-20 rounded-md p-4">
464
+ <h3 class="font-bold mb-2">Flex</h3>
465
+ <div class="flex items-center justify-between gap-2">
466
+ <span class="bg-secondary-20 p-2 rounded-sm">A</span>
467
+ <span class="bg-secondary-20 p-2 rounded-sm">B</span>
468
+ <span class="bg-secondary-20 p-2 rounded-sm">C</span>
469
+ </div>
470
+ </div>
471
+
472
+ <div class="border border-neutral-20 rounded-md p-4">
473
+ <h3 class="font-bold mb-2">Grid</h3>
474
+ <div class="grid grid-cols-3 gap-2">
475
+ <span class="bg-success-20 h-8 rounded-sm"></span>
476
+ <span class="bg-success-20 h-8 rounded-sm"></span>
477
+ <span class="bg-success-20 h-8 rounded-sm"></span>
478
+ </div>
479
+ </div>
480
+
481
+ <div class="border border-neutral-20 rounded-md p-4 overflow-hidden">
482
+ <h3 class="font-bold mb-2">Overflow</h3>
483
+ <p class="truncate text-sm bg-neutral-20 p-2 rounded-sm">This line is intentionally long so truncate can be tested properly.</p>
484
+ </div>
485
+
486
+ <div class="border border-neutral-20 rounded-md p-4">
487
+ <h3 class="font-bold mb-2">Sizing</h3>
488
+ <div class="w-full bg-neutral-20 rounded-sm">
489
+ <div class="w-48 h-6 bg-primary-80 rounded-sm"></div>
490
+ </div>
491
+ </div>
492
+
493
+ <div class="border border-neutral-20 rounded-md p-4">
494
+ <h3 class="font-bold mb-2">Borders</h3>
495
+ <div class="border-4 border-primary-80 rounded-lg p-3 text-sm">border-4 rounded-lg</div>
496
+ </div>
497
+
498
+ <div class="border border-neutral-20 rounded-md p-4">
499
+ <h3 class="font-bold mb-2">Shadows</h3>
500
+ <div class="shadow-lg rounded-md p-3 text-sm">shadow-lg</div>
501
+ </div>
502
+
503
+ <div class="border border-neutral-20 rounded-md p-4">
504
+ <h3 class="font-bold mb-2">Opacity</h3>
505
+ <div class="flex gap-2">
506
+ <span class="bg-primary-80 opacity-25 h-8 w-8 rounded-sm"></span>
507
+ <span class="bg-primary-80 opacity-50 h-8 w-8 rounded-sm"></span>
508
+ <span class="bg-primary-80 opacity-100 h-8 w-8 rounded-sm"></span>
509
+ </div>
510
+ </div>
511
+ </div>
512
+ </section>
513
+
514
+ <section id="responsive-examples" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
515
+ <h2 class="text-2xl font-bold mb-2">Responsive examples</h2>
516
+ <p class="text-neutral-80 mb-6">These cards change layout using responsive variants.</p>
517
+
518
+ <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
519
+ <div class="bg-primary-10 border border-primary-30 rounded-md p-4">
520
+ <p class="font-bold">Mobile</p>
521
+ <p class="text-sm">1 column by default</p>
522
+ </div>
523
+ <div class="bg-secondary-10 border border-secondary-30 rounded-md p-4">
524
+ <p class="font-bold">Small</p>
525
+ <p class="text-sm">2 columns from sm</p>
526
+ </div>
527
+ <div class="bg-success-10 border border-success-30 rounded-md p-4">
528
+ <p class="font-bold">Large</p>
529
+ <p class="text-sm">4 columns from lg</p>
530
+ </div>
531
+ <div class="bg-warning-10 border border-warning-30 rounded-md p-4">
532
+ <p class="font-bold">Build check</p>
533
+ <p class="text-sm">sm: and lg: classes used</p>
534
+ </div>
535
+ </div>
536
+ </section>
537
+
538
+ <section id="state-examples" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
539
+ <h2 class="text-2xl font-bold mb-2">State examples</h2>
540
+ <p class="text-neutral-80 mb-6">Tests hover, focus-visible, active and disabled variants.</p>
541
+
542
+ <div class="flex flex-wrap gap-3">
543
+ <button class="bg-primary-80 text-white px-5 py-3 rounded-md font-semibold hover:bg-primary-90 active:bg-primary-100 focus-visible:ring-2 ring-primary-80">
544
+ Hover and focus
545
+ </button>
546
+
547
+ <button class="bg-secondary-80 text-white px-5 py-3 rounded-md font-semibold hover:bg-secondary-90 active:bg-secondary-100 focus-visible:ring-2 ring-secondary-80">
548
+ Active state
549
+ </button>
550
+
551
+ <button disabled class="bg-neutral-30 text-neutral-70 px-5 py-3 rounded-md font-semibold disabled:opacity-50">
552
+ Disabled
553
+ </button>
554
+
555
+ <a href="#" class="inline-flex items-center justify-center text-primary-80 underline decoration-2 hover:text-primary-90 focus-visible:ring-2 ring-primary-80 rounded-sm">
556
+ Focusable link
557
+ </a>
558
+ </div>
559
+ </section>
560
+
561
+ <section id="dark-mode" class="bg-white dark:bg-neutral-90 rounded-lg shadow-md border border-neutral-20 dark:border-neutral-70 p-6 mb-6">
562
+ <h2 class="text-2xl font-bold mb-2 text-neutral-90 dark:text-neutral-10">Dark mode example</h2>
563
+ <p class="text-neutral-80 dark:text-neutral-20 mb-6">This section tests dark: colour variants.</p>
564
+
565
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
566
+ <div class="bg-neutral-10 dark:bg-neutral-80 border border-neutral-20 dark:border-neutral-60 rounded-md p-4">
567
+ <p class="font-bold text-neutral-90 dark:text-neutral-10">Panel</p>
568
+ <p class="text-sm text-neutral-70 dark:text-neutral-20">dark:bg-neutral-80</p>
569
+ </div>
570
+
571
+ <div class="bg-primary-10 dark:bg-primary-90 border border-primary-30 dark:border-primary-70 rounded-md p-4">
572
+ <p class="font-bold text-primary-90 dark:text-primary-10">Brand</p>
573
+ <p class="text-sm text-primary-90 dark:text-primary-10">dark:bg-primary-90</p>
574
+ </div>
575
+
576
+ <div class="bg-secondary-10 dark:bg-secondary-90 border border-secondary-30 dark:border-secondary-70 rounded-md p-4">
577
+ <p class="font-bold text-secondary-90 dark:text-secondary-10">Accent</p>
578
+ <p class="text-sm text-secondary-90 dark:text-secondary-10">dark:text-secondary-10</p>
579
+ </div>
580
+ </div>
581
+ </section>
582
+
583
+ <section id="accessibility-examples" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
584
+ <h2 class="text-2xl font-bold mb-2">Accessibility examples</h2>
585
+ <p class="text-neutral-80 mb-6">Examples for focus, labels, errors, hidden text and live messages.</p>
586
+
587
+ <a href="#main-content" class="skip-link">
588
+ Skip to main content
589
+ </a>
590
+
591
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
592
+ <form class="border border-neutral-20 rounded-md p-5">
593
+ <h3 class="text-xl font-bold mb-4">Form error</h3>
594
+
595
+ <label for="email-error" class="block font-semibold mb-2">Email address</label>
596
+ <p id="email-error-message" class="text-error-90 font-semibold mb-2">Enter an email address in the correct format.</p>
597
+ <input id="email-error" aria-describedby="email-error-message" aria-invalid="true" class="w-full border-2 border-error-80 rounded-md p-3 focus-visible:ring-2 ring-error-80" type="email" value="andy">
598
+ </form>
599
+
600
+ <div class="border border-neutral-20 rounded-md p-5">
601
+ <h3 class="text-xl font-bold mb-4">Live region</h3>
602
+ <div role="status" aria-live="polite" class="bg-success-20 border border-success-80 rounded-md p-4 flex items-start justify-between">
603
+ <div>
604
+ <p class="font-bold text-success-90">Saved</p>
605
+ <p class="text-success-90">Your changes have been saved.</p>
606
+ </div>
607
+ <button aria-label="Dismiss" class="touch-target text-success-80 hover:text-success-90 ml-2 text-lg leading-none">&times;</button>
608
+ </div>
609
+ </div>
610
+ </div>
611
+ </section>
612
+
613
+ <section id="page-patterns" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
614
+ <h2 class="text-2xl font-bold mb-2">Real page patterns</h2>
615
+ <p class="text-neutral-80 mb-6">Larger patterns you would actually use on a site or app.</p>
616
+
617
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
618
+ <article class="lg:col-span-2 border border-neutral-20 rounded-lg p-6">
619
+ <p class="text-sm font-bold text-primary-80 mb-2">Article layout</p>
620
+ <h3 class="text-3xl font-bold mb-3">Design systems work best when they reduce decisions.</h3>
621
+ <p class="text-neutral-80 leading-relaxed mb-4">A good utility system should help teams move faster without removing judgement. This pattern tests headings, body copy, spacing and readable measure.</p>
622
+ <a href="#" class="text-primary-80 font-semibold underline">Continue reading</a>
623
+ </article>
624
+
625
+ <aside class="border border-neutral-20 rounded-lg p-6 bg-neutral-10">
626
+ <p class="text-sm font-bold text-neutral-70 mb-2">Dashboard panel</p>
627
+ <p class="text-4xl font-bold text-primary-90 mb-2">64%</p>
628
+ <p class="text-neutral-80">CSS reduction after production build.</p>
629
+ </aside>
630
+
631
+ <div class="border border-primary-30 bg-primary-10 rounded-lg p-6 lg:col-span-3">
632
+ <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
633
+ <div>
634
+ <h3 class="text-2xl font-bold text-primary-90 mb-2">Ready to ship?</h3>
635
+ <p class="text-primary-90">Use this section as a call-to-action pattern.</p>
636
+ </div>
637
+ <a href="#" class="inline-flex justify-center bg-primary-80 text-white px-5 py-3 rounded-md font-semibold hover:bg-primary-90">
638
+ Get started
639
+ </a>
640
+ </div>
641
+ </div>
642
+ </div>
643
+ </section>
644
+
645
+ <section id="code-snippets" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
646
+ <h2 class="text-2xl font-bold mb-2">Code snippets</h2>
647
+ <p class="text-neutral-80 mb-6">Live preview with full copy-ready HTML.</p>
648
+
649
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
650
+
651
+ <!-- Primary button -->
652
+ <div>
653
+ <h3 class="font-bold mb-3">Primary button</h3>
654
+ <div class="bg-neutral-10 border border-neutral-20 rounded-t-lg p-6 flex items-center justify-center" style="min-height: 80px;">
655
+ <button class="bg-primary-80 text-white px-5 py-3 rounded-md font-semibold hover:bg-primary-90 focus-visible:ring-2 ring-primary-80">Get started</button>
656
+ </div>
657
+ <div class="code-window" style="border-top-left-radius: 0; border-top-right-radius: 0; border-top: none;">
658
+ <div class="code-title-bar">
659
+ <span class="code-dot code-dot-red"></span>
660
+ <span class="code-dot code-dot-yellow"></span>
661
+ <span class="code-dot code-dot-green"></span>
662
+ <span class="code-filename">button.html</span>
663
+ </div>
664
+ <pre><code><span class="token-line-number"> 1</span><span class="token-tag">&lt;button</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"</span><span class="token-attr">bg-primary-80</span> <span class="token-attr">text-white</span>
665
+ <span class="token-line-number"> 2</span> <span class="token-attr">px-5</span> <span class="token-attr">py-3</span> <span class="token-utility">rounded-md</span>
666
+ <span class="token-line-number"> 3</span> <span class="token-utility">font-semibold</span>
667
+ <span class="token-line-number"> 4</span> <span class="token-variant">hover:</span><span class="token-attr">bg-primary-90</span>
668
+ <span class="token-line-number"> 5</span> <span class="token-variant">focus-visible:</span><span class="token-attr">ring-2</span>
669
+ <span class="token-line-number"> 6</span> <span class="token-attr">ring-primary-80</span><span class="token-string">"</span><span class="token-tag">&gt;</span>
670
+ <span class="token-line-number"> 7</span> Get started
671
+ <span class="token-line-number"> 8</span><span class="token-tag">&lt;/button&gt;</span></code></pre>
672
+ </div>
673
+ </div>
674
+
675
+ <!-- Responsive card grid -->
676
+ <div>
677
+ <h3 class="font-bold mb-3">Responsive card grid</h3>
678
+ <div class="bg-neutral-10 border border-neutral-20 rounded-t-lg p-4" style="min-height: 80px;">
679
+ <div class="grid grid-cols-3 gap-3">
680
+ <div class="bg-white border border-neutral-20 rounded-md p-3 text-sm text-neutral-70">Card one</div>
681
+ <div class="bg-white border border-neutral-20 rounded-md p-3 text-sm text-neutral-70">Card two</div>
682
+ <div class="bg-white border border-neutral-20 rounded-md p-3 text-sm text-neutral-70">Card three</div>
683
+ </div>
684
+ </div>
685
+ <div class="code-window" style="border-top-left-radius: 0; border-top-right-radius: 0; border-top: none;">
686
+ <div class="code-title-bar">
687
+ <span class="code-dot code-dot-red"></span>
688
+ <span class="code-dot code-dot-yellow"></span>
689
+ <span class="code-dot code-dot-green"></span>
690
+ <span class="code-filename">grid.html</span>
691
+ </div>
692
+ <pre><code><span class="token-line-number"> 1</span><span class="token-tag">&lt;div</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"</span><span class="token-attr">grid</span> <span class="token-attr">grid-cols-1</span>
693
+ <span class="token-line-number"> 2</span> <span class="token-variant">md:</span><span class="token-attr">grid-cols-2</span>
694
+ <span class="token-line-number"> 3</span> <span class="token-variant">lg:</span><span class="token-attr">grid-cols-3</span>
695
+ <span class="token-line-number"> 4</span> <span class="token-attr">gap-6</span><span class="token-string">"</span><span class="token-tag">&gt;</span>
696
+ <span class="token-line-number"> 5</span> <span class="token-tag">&lt;div</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"</span><span class="token-attr">bg-white</span> <span class="token-attr">border</span>
697
+ <span class="token-line-number"> 6</span> <span class="token-attr">border-neutral-20</span> <span class="token-utility">rounded-md</span>
698
+ <span class="token-line-number"> 7</span> <span class="token-attr">p-4</span><span class="token-string">"</span><span class="token-tag">&gt;</span>Card one<span class="token-tag">&lt;/div&gt;</span>
699
+ <span class="token-line-number"> 8</span> <span class="token-comment">&lt;!-- repeat for each card --&gt;</span>
700
+ <span class="token-line-number"> 9</span><span class="token-tag">&lt;/div&gt;</span></code></pre>
701
+ </div>
702
+ </div>
703
+
704
+ <!-- Alert -->
705
+ <div>
706
+ <h3 class="font-bold mb-3">Alert</h3>
707
+ <div class="bg-neutral-10 border border-neutral-20 rounded-t-lg p-6 flex items-center" style="min-height: 80px;">
708
+ <div class="bg-success-20 border border-success-80 rounded-md p-4 text-success-90 w-full"><strong>Success:</strong> Your changes have been saved.</div>
709
+ </div>
710
+ <div class="code-window" style="border-top-left-radius: 0; border-top-right-radius: 0; border-top: none;">
711
+ <div class="code-title-bar">
712
+ <span class="code-dot code-dot-red"></span>
713
+ <span class="code-dot code-dot-yellow"></span>
714
+ <span class="code-dot code-dot-green"></span>
715
+ <span class="code-filename">alert.html</span>
716
+ </div>
717
+ <pre><code><span class="token-line-number"> 1</span><span class="token-tag">&lt;div</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"</span><span class="token-colour">bg-success-20</span>
718
+ <span class="token-line-number"> 2</span> <span class="token-attr">border</span> <span class="token-colour">border-success-80</span>
719
+ <span class="token-line-number"> 3</span> <span class="token-utility">rounded-md</span> <span class="token-attr">p-4</span>
720
+ <span class="token-line-number"> 4</span> <span class="token-colour">text-success-90</span><span class="token-string">"</span><span class="token-tag">&gt;</span>
721
+ <span class="token-line-number"> 5</span> <span class="token-tag">&lt;strong&gt;</span>Success:<span class="token-tag">&lt;/strong&gt;</span>
722
+ <span class="token-line-number"> 6</span> Your changes have been saved.
723
+ <span class="token-line-number"> 7</span><span class="token-tag">&lt;/div&gt;</span></code></pre>
724
+ </div>
725
+ </div>
726
+
727
+ <!-- Dark panel -->
728
+ <div>
729
+ <h3 class="font-bold mb-3">Dark mode panel</h3>
730
+ <div class="bg-neutral-80 border border-neutral-20 rounded-t-lg p-6 flex items-center" style="min-height: 80px;">
731
+ <div class="bg-neutral-90 text-neutral-10 rounded-lg p-5 w-full">
732
+ <p class="font-semibold mb-1">Dark panel</p>
733
+ <p class="text-sm text-neutral-30">Content adapts to dark mode automatically.</p>
734
+ </div>
735
+ </div>
736
+ <div class="code-window" style="border-top-left-radius: 0; border-top-right-radius: 0; border-top: none;">
737
+ <div class="code-title-bar">
738
+ <span class="code-dot code-dot-red"></span>
739
+ <span class="code-dot code-dot-yellow"></span>
740
+ <span class="code-dot code-dot-green"></span>
741
+ <span class="code-filename">dark-panel.html</span>
742
+ </div>
743
+ <pre><code><span class="token-line-number"> 1</span><span class="token-tag">&lt;div</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"</span><span class="token-attr">bg-white</span>
744
+ <span class="token-line-number"> 2</span> <span class="token-variant">dark:</span><span class="token-attr">bg-neutral-90</span>
745
+ <span class="token-line-number"> 3</span> <span class="token-attr">text-neutral-90</span>
746
+ <span class="token-line-number"> 4</span> <span class="token-variant">dark:</span><span class="token-attr">text-neutral-10</span>
747
+ <span class="token-line-number"> 5</span> <span class="token-utility">rounded-lg</span> <span class="token-attr">p-5</span><span class="token-string">"</span><span class="token-tag">&gt;</span>
748
+ <span class="token-line-number"> 6</span> <span class="token-tag">&lt;p</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"</span><span class="token-utility">font-semibold</span><span class="token-string">"</span><span class="token-tag">&gt;</span>Dark panel<span class="token-tag">&lt;/p&gt;</span>
749
+ <span class="token-line-number"> 7</span> <span class="token-tag">&lt;p</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"</span><span class="token-attr">text-sm</span> <span class="token-attr">text-neutral-30</span><span class="token-string">"</span><span class="token-tag">&gt;</span>
750
+ <span class="token-line-number"> 8</span> Content adapts to dark mode.
751
+ <span class="token-line-number"> 9</span> <span class="token-tag">&lt;/p&gt;</span>
752
+ <span class="token-line-number">10</span><span class="token-tag">&lt;/div&gt;</span></code></pre>
753
+ </div>
754
+ </div>
755
+
756
+ </div>
757
+ </section>
758
+
759
+
760
+ <section id="install" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
761
+ <h2 class="text-2xl font-bold mb-2">Install and setup</h2>
762
+ <p class="text-neutral-80 mb-6">Basic commands for getting EmilyCSS running in a project.</p>
763
+
764
+ <div class="flex flex-col gap-3">
765
+
766
+ <div class="code-window">
767
+ <div class="code-title-bar">
768
+ <span class="code-dot code-dot-red"></span>
769
+ <span class="code-dot code-dot-yellow"></span>
770
+ <span class="code-dot code-dot-green"></span>
771
+ <span class="code-filename">terminal</span>
772
+ </div>
773
+ <pre><code><span class="token-keyword">npm</span> install <span class="token-string">emily-css</span></code></pre>
774
+ </div>
775
+
776
+ <div class="code-window">
777
+ <div class="code-title-bar">
778
+ <span class="code-dot code-dot-red"></span>
779
+ <span class="code-dot code-dot-yellow"></span>
780
+ <span class="code-dot code-dot-green"></span>
781
+ <span class="code-filename">terminal</span>
782
+ </div>
783
+ <pre><code><span class="token-keyword">npx</span> <span class="token-string">emily-css</span> init</code></pre>
784
+ </div>
785
+
786
+ <div class="code-window">
787
+ <div class="code-title-bar">
788
+ <span class="code-dot code-dot-red"></span>
789
+ <span class="code-dot code-dot-yellow"></span>
790
+ <span class="code-dot code-dot-green"></span>
791
+ <span class="code-filename">terminal</span>
792
+ </div>
793
+ <pre><code><span class="token-keyword">npm</span> run <span class="token-string">emily:watch</span></code></pre>
794
+ </div>
795
+
796
+ <div class="code-window">
797
+ <div class="code-title-bar">
798
+ <span class="code-dot code-dot-red"></span>
799
+ <span class="code-dot code-dot-yellow"></span>
800
+ <span class="code-dot code-dot-green"></span>
801
+ <span class="code-filename">terminal</span>
802
+ </div>
803
+ <pre><code><span class="token-keyword">npm</span> run <span class="token-string">emily:build</span></code></pre>
804
+ </div>
805
+
806
+ <div class="code-window">
807
+ <div class="code-title-bar">
808
+ <span class="code-dot code-dot-red"></span>
809
+ <span class="code-dot code-dot-yellow"></span>
810
+ <span class="code-dot code-dot-green"></span>
811
+ <span class="code-filename">index.html</span>
812
+ </div>
813
+ <pre><code><span class="token-tag">&lt;link</span> <span class="token-attr">rel</span><span class="token-operator">=</span><span class="token-string">"stylesheet"</span> <span class="token-attr">href</span><span class="token-operator">=</span><span class="token-string">"./dist/emily.min.css"</span><span class="token-tag">&gt;</span></code></pre>
814
+ </div>
815
+
816
+ </div>
817
+ </section>
818
+
819
+ <section id="build-output" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
820
+ <h2 class="text-2xl font-bold mb-2">Build output</h2>
821
+ <p class="text-neutral-80 mb-6">How EmilyCSS handles local development and production output.</p>
822
+
823
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
824
+ <div class="border border-neutral-20 rounded-md p-4">
825
+ <p class="font-bold">dist/emily.min.css</p>
826
+ <p class="text-sm text-neutral-70">Production-ready CSS. This is the file you link to in your project.</p>
827
+ </div>
828
+
829
+ <div class="border border-neutral-20 rounded-md p-4">
830
+ <p class="font-bold">dist/emily.css</p>
831
+ <p class="text-sm text-neutral-70">Temporary local framework source. Watch keeps it available when needed.</p>
832
+ </div>
833
+
834
+ <div class="border border-neutral-20 rounded-md p-4">
835
+ <p class="font-bold">emily-css build</p>
836
+ <p class="text-sm text-neutral-70">Outputs dist/emily.min.css and removes dist/emily.css by default.</p>
837
+ </div>
838
+ </div>
839
+
840
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
841
+ <div class="border border-neutral-20 rounded-md p-4">
842
+ <p class="font-bold">Development</p>
843
+ <p class="text-sm text-neutral-70">Run npm run emily:watch. It keeps a full local emily.css available when needed and regenerates emily.min.css as classes change.</p>
844
+ </div>
845
+
846
+ <div class="border border-neutral-20 rounded-md p-4">
847
+ <p class="font-bold">Production</p>
848
+ <p class="text-sm text-neutral-70">Run npm run emily:build. Production should only ship dist/emily.min.css.</p>
849
+ </div>
850
+ </div>
851
+ </section>
852
+
853
+
854
+ <section id="patterns" class="bg-white rounded-lg shadow-md border border-neutral-20 p-6 mb-6">
855
+ <h2 class="text-2xl font-bold mb-2">Pattern components</h2>
856
+ <p class="text-neutral-80 mb-6">Single classes that combine multiple utilities into named patterns. They live in <code class="text-sm bg-neutral-20 px-1 rounded">@layer components</code> so utility classes always override them.</p>
857
+
858
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
859
+
860
+ <div>
861
+ <h3 class="font-bold mb-3">.prose</h3>
862
+ <div class="bg-neutral-10 border border-neutral-20 rounded-t-lg p-6" style="min-height: 100px;">
863
+ <div class="prose">
864
+ <p class="text-neutral-80 leading-relaxed">Inside <code>.prose</code>. Line length is capped at 65 characters and the block centres itself — ideal for article copy and documentation.</p>
865
+ </div>
866
+ </div>
867
+ <div class="code-window" style="border-top-left-radius: 0; border-top-right-radius: 0; border-top: none;">
868
+ <div class="code-title-bar">
869
+ <span class="code-dot code-dot-red"></span><span class="code-dot code-dot-yellow"></span><span class="code-dot code-dot-green"></span>
870
+ <span class="code-filename">prose.html</span>
871
+ </div>
872
+ <pre><code><span class="token-tag">&lt;div</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"prose"</span><span class="token-tag">&gt;</span>
873
+ <span class="token-tag">&lt;p&gt;</span>Article copy here.<span class="token-tag">&lt;/p&gt;</span>
874
+ <span class="token-tag">&lt;/div&gt;</span>
875
+ <span class="token-comment">&lt;!-- max-width: 65ch; margin-inline: auto --&gt;</span></code></pre>
876
+ </div>
877
+ </div>
878
+
879
+ <div>
880
+ <h3 class="font-bold mb-3">.stack</h3>
881
+ <div class="bg-neutral-10 border border-neutral-20 rounded-t-lg p-6" style="min-height: 100px;">
882
+ <div class="stack">
883
+ <div class="bg-primary-20 text-primary-90 rounded-md p-3 text-sm font-medium">First item</div>
884
+ <div class="bg-primary-20 text-primary-90 rounded-md p-3 text-sm font-medium">Second item</div>
885
+ <div class="bg-primary-20 text-primary-90 rounded-md p-3 text-sm font-medium">Third item</div>
886
+ </div>
887
+ </div>
888
+ <div class="code-window" style="border-top-left-radius: 0; border-top-right-radius: 0; border-top: none;">
889
+ <div class="code-title-bar">
890
+ <span class="code-dot code-dot-red"></span><span class="code-dot code-dot-yellow"></span><span class="code-dot code-dot-green"></span>
891
+ <span class="code-filename">stack.html</span>
892
+ </div>
893
+ <pre><code><span class="token-tag">&lt;div</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"stack"</span><span class="token-tag">&gt;</span>
894
+ <span class="token-tag">&lt;div&gt;</span>Item one<span class="token-tag">&lt;/div&gt;</span>
895
+ <span class="token-tag">&lt;div&gt;</span>Item two<span class="token-tag">&lt;/div&gt;</span>
896
+ <span class="token-tag">&lt;/div&gt;</span>
897
+ <span class="token-comment">&lt;!-- flex-direction: column; gap: var(--space-4) --&gt;</span></code></pre>
898
+ </div>
899
+ </div>
900
+
901
+ <div>
902
+ <h3 class="font-bold mb-3">.cluster</h3>
903
+ <div class="bg-neutral-10 border border-neutral-20 rounded-t-lg p-6" style="min-height: 100px;">
904
+ <div class="cluster">
905
+ <span class="bg-primary-20 text-primary-90 px-3 py-1 rounded-full text-sm font-medium">Design systems</span>
906
+ <span class="bg-secondary-20 text-secondary-90 px-3 py-1 rounded-full text-sm font-medium">Accessible</span>
907
+ <span class="bg-success-20 text-success-90 px-3 py-1 rounded-full text-sm font-medium">No build step</span>
908
+ <span class="bg-warning-20 text-warning-90 px-3 py-1 rounded-full text-sm font-medium">Config-driven</span>
909
+ </div>
910
+ </div>
911
+ <div class="code-window" style="border-top-left-radius: 0; border-top-right-radius: 0; border-top: none;">
912
+ <div class="code-title-bar">
913
+ <span class="code-dot code-dot-red"></span><span class="code-dot code-dot-yellow"></span><span class="code-dot code-dot-green"></span>
914
+ <span class="code-filename">cluster.html</span>
915
+ </div>
916
+ <pre><code><span class="token-tag">&lt;div</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"cluster"</span><span class="token-tag">&gt;</span>
917
+ <span class="token-tag">&lt;span&gt;</span>Tag one<span class="token-tag">&lt;/span&gt;</span>
918
+ <span class="token-tag">&lt;span&gt;</span>Tag two<span class="token-tag">&lt;/span&gt;</span>
919
+ <span class="token-tag">&lt;/div&gt;</span>
920
+ <span class="token-comment">&lt;!-- flex-wrap: wrap; gap: var(--space-4);
921
+ align-items: center --&gt;</span></code></pre>
922
+ </div>
923
+ </div>
924
+
925
+ <div>
926
+ <h3 class="font-bold mb-3">.center-absolute</h3>
927
+ <div class="bg-neutral-10 border border-neutral-20 rounded-t-lg p-6" style="min-height: 160px;">
928
+ <div class="relative bg-neutral-20 rounded-md" style="height: 140px;">
929
+ <div class="center-absolute bg-white rounded-md shadow-md border border-neutral-30 p-4 text-sm text-center" style="width: max-content;">
930
+ <p class="font-semibold">Centred content</p>
931
+ <p class="text-neutral-70 text-xs mt-1">via .center-absolute</p>
932
+ </div>
933
+ </div>
934
+ </div>
935
+ <div class="code-window" style="border-top-left-radius: 0; border-top-right-radius: 0; border-top: none;">
936
+ <div class="code-title-bar">
937
+ <span class="code-dot code-dot-red"></span><span class="code-dot code-dot-yellow"></span><span class="code-dot code-dot-green"></span>
938
+ <span class="code-filename">center-absolute.html</span>
939
+ </div>
940
+ <pre><code><span class="token-tag">&lt;div</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"relative"</span><span class="token-tag">&gt;</span>
941
+ <span class="token-tag">&lt;div</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"center-absolute"</span><span class="token-tag">&gt;</span>
942
+ Centred content
943
+ <span class="token-tag">&lt;/div&gt;</span>
944
+ <span class="token-tag">&lt;/div&gt;</span>
945
+ <span class="token-comment">&lt;!-- position: absolute; top: 50%; left: 50%;
946
+ transform: translate(-50%, -50%) --&gt;</span></code></pre>
947
+ </div>
948
+ </div>
949
+
950
+ <div class="lg:col-span-2">
951
+ <h3 class="font-bold mb-3">.center-screen</h3>
952
+ <div class="border border-neutral-20 rounded-t-lg overflow-hidden bg-neutral-80" style="min-height: 200px; position: relative;">
953
+ <div class="center-screen" style="position: absolute;">
954
+ <div class="bg-white rounded-lg shadow-lg border border-neutral-20 p-6 text-center" style="max-width: 340px; width: 90%;">
955
+ <h4 class="text-lg font-bold mb-2">Confirm action</h4>
956
+ <p class="text-neutral-80 text-sm mb-4">This is how center-screen looks. In production it uses position: fixed and covers the full viewport.</p>
957
+ <div class="flex gap-3" style="justify-content: center;">
958
+ <button class="bg-primary-80 text-white px-4 py-2 rounded-md font-semibold text-sm">Confirm</button>
959
+ <button class="border border-neutral-40 px-4 py-2 rounded-md text-sm">Cancel</button>
960
+ </div>
961
+ </div>
962
+ </div>
963
+ <p class="absolute top-3 right-3 text-xs text-neutral-40 font-medium">Simulated preview</p>
964
+ </div>
965
+ <div class="code-window" style="border-top-left-radius: 0; border-top-right-radius: 0; border-top: none;">
966
+ <div class="code-title-bar">
967
+ <span class="code-dot code-dot-red"></span><span class="code-dot code-dot-yellow"></span><span class="code-dot code-dot-green"></span>
968
+ <span class="code-filename">center-screen.html</span>
969
+ </div>
970
+ <pre><code><span class="token-comment">&lt;!-- Backdrop --&gt;</span>
971
+ <span class="token-tag">&lt;div</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"center-screen bg-neutral-90 opacity-50"</span><span class="token-tag">&gt;&lt;/div&gt;</span>
972
+
973
+ <span class="token-comment">&lt;!-- Modal --&gt;</span>
974
+ <span class="token-tag">&lt;div</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"center-screen"</span><span class="token-tag">&gt;</span>
975
+ <span class="token-tag">&lt;div</span> <span class="token-attr">class</span><span class="token-operator">=</span><span class="token-string">"bg-white rounded-lg p-6 shadow-lg z-modal"</span><span class="token-tag">&gt;</span>
976
+ Modal content
977
+ <span class="token-tag">&lt;/div&gt;</span>
978
+ <span class="token-tag">&lt;/div&gt;</span>
979
+ <span class="token-comment">&lt;!-- position: fixed; inset: 0; display: flex;
980
+ align-items: center; justify-content: center --&gt;</span></code></pre>
981
+ </div>
982
+ </div>
983
+
984
+ </div>
985
+ </section>
986
+
987
+ <footer class="text-center text-sm text-neutral-70 py-6">
988
+ Built with EmilyUI utility classes.
989
+ </footer>
990
+ </main>
991
+ </body>
992
+ </html>