luciddom 0.0.0-stage → 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 CHANGED
@@ -1,3 +1,128 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # LucidDOM.js
2
+
3
+ LucidDOM.js is a lightweight browser-side DOM helper. It wraps a parent element and provides methods for creating elements, selecting descendants, updating content, handling events, applying inline styles, and removing elements.
4
+
5
+ ## Quick start
6
+
7
+ Copy `lucidDOM.js` into your project and import its default export from a JavaScript module:
8
+
9
+ ```html
10
+ <div id="app"></div>
11
+ <script type="module" src="./app.js"></script>
12
+ ```
13
+
14
+ ```js
15
+ // app.js
16
+ import LucidDOM from './lucidDOM.js';
17
+
18
+ const app = new LucidDOM(document.querySelector('#app'));
19
+
20
+ const message = app.addElement({
21
+ tag: 'p',
22
+ className: 'message',
23
+ value: 'Hello from LucidDOM!'
24
+ });
25
+
26
+ message.styles({ color: 'darkslateblue', fontWeight: 'bold' });
27
+ ```
28
+
29
+ The constructor accepts a DOM element and scopes queries and element creation to it. If no parent is passed, it defaults to `document.body`.
30
+
31
+ ## Common operations
32
+
33
+ ### Select and update
34
+
35
+ ```js
36
+ const heading = app.select('h1');
37
+
38
+ if (heading.current) {
39
+ heading.update({ value: 'Welcome', className: 'page-title' });
40
+ heading.styles({ color: 'darkslateblue' });
41
+ }
42
+ ```
43
+
44
+ `select(selector)` uses `querySelector()` within the configured parent. The returned handle's `current` property is `null` when no element matches, so check it before using the handle.
45
+
46
+ ### Listen for events
47
+
48
+ ```js
49
+ const button = app.select('#submit');
50
+
51
+ function submitForm() {
52
+ console.log('Submitted');
53
+ }
54
+
55
+ button.on('click', submitForm);
56
+ // Later, remove the same listener with the same function reference:
57
+ button.off('click', submitForm);
58
+ ```
59
+
60
+ `on()` and `off()` are also available on the `LucidDOM` instance and act on its parent element.
61
+
62
+ ### Create and remove elements
63
+
64
+ ```js
65
+ const item = app.addElement({
66
+ tag: 'li',
67
+ className: 'todo-item',
68
+ value: 'Read the documentation'
69
+ });
70
+
71
+ item.on('click', () => console.log('Item clicked'));
72
+ item.remove();
73
+ ```
74
+
75
+ ## API overview
76
+
77
+ | API | Description |
78
+ | --- | --- |
79
+ | `new LucidDOM(parent?)` | Create an instance scoped to `parent`, defaulting to `document.body`. |
80
+ | `app.addElement(options)` | Create and append an element; returns a handle for events, styles, and removal. |
81
+ | `app.select(selector)` | Select the first matching descendant and return a handle with `current`. |
82
+ | `app.selectAll(selector)` | Select all matching descendants; `current` is the native `NodeList` and handle methods apply to every match. |
83
+ | `app.on(event, callback)` / `app.off(event, callback)` | Add or remove a listener on the parent element. |
84
+ | `app.styles(styleObject)` | Apply inline CSS styles to the parent element. |
85
+ | `handle.update(options)` | Update content and supported attributes on a selected element. |
86
+ | `handle.addClass(name)` / `handle.removeClass(name)` | Add or remove a class on a selected element. |
87
+ | `handle.remove()` | Remove the handled element from the DOM. |
88
+
89
+ `addElement()` accepts a required `tag` and these optional properties: `className`, `id`, `value`, `type`, `placeholder`, `name`, `htmlFor`, `formAction`, `formMethod`, `formTarget`, `formEnctype`, `formNoValidate`, `ariaLabel`, `title`, and `html`.
90
+
91
+ `value` sets text content (and the element's value property). `html` sets `innerHTML`; only pass trusted or sanitized markup. Prefer `value` for plain text.
92
+
93
+ ## Working with multiple matches
94
+
95
+ The handle from `selectAll()` applies its helper methods to each matching element. It supports event listeners, inline styles, adding and removing classes, updating supported attributes and content, removing the matches, and adding a child to each match.
96
+
97
+ ```js
98
+ const items = app.selectAll('.todo-item');
99
+
100
+ items.styles({ color: 'darkslateblue' });
101
+ items.addClass('is-visible');
102
+ items.update({ title: 'Visible item' });
103
+ items.on('click', (event) => {
104
+ console.log('Clicked:', event.currentTarget);
105
+ });
106
+
107
+ // `current` remains a native NodeList for custom per-element work.
108
+ items.current.forEach((item) => {
109
+ console.log(item.textContent);
110
+ });
111
+ ```
112
+
113
+ When nothing matches, `current` is an empty NodeList and helper methods do nothing. `select()` returns a handle even when there is no match; check `handle.current` before calling methods on that handle.
114
+
115
+ ## Build
116
+
117
+ The package includes an esbuild script to create a minified ES module bundle:
118
+
119
+ ```sh
120
+ npm install
121
+ npm run build
122
+ ```
123
+
124
+ The output is written to `dist/lucid.min.js`.
125
+
126
+ ## Documentation
127
+
128
+ See [docs.html](./docs.html) for the navigable guide, examples, and API reference.
@@ -0,0 +1,2 @@
1
+ (()=>{var x=class{constructor(l=document.querySelector("body")){this.parent=l,this.element}addElement({tag:l,className:e,id:t,value:i,type:r,placeholder:n,name:y,htmlFor:E,formAction:L,formMethod:a,formTarget:f,formEnctype:h,formNoValidate:c,ariaLabel:u,title:p,html:v}){try{return this.element=document.createElement(l),e&&(this.element.className=e),r&&(this.element.type=r),i&&(this.element.innerText=i),this.element.value=i,t&&(this.element.id=t),v&&(this.element.innerHTML=v),n&&(this.element.placeholder=n),this.element.name=y,p&&(this.element.title=p),u&&(this.element.ariaLabel=u),this.element.htmlFor=E,L&&(this.element.formAction=L),h&&(this.element.formEnctype=h),a&&(this.element.formMethod=a),f&&(this.element.formTarget=f),c&&(this.element.formNoValidate=c),this.parent.appendChild(this.element),{on:(d="",o=()=>{})=>{this.element.addEventListener(d,o)},off:(d="",o=()=>{})=>{this.element.removeEventListener(d,o)},styles:(d={})=>{Object.assign(this.element.style,d)},remove:()=>{this.element.remove()},addChildren:({tag:d,className:o,id:s,value:T,type:C,placeholder:g,name:q,htmlFor:S,formAction:F,formMethod:N,formTarget:O,formEnctype:j,formNoValidate:w,ariaLabel:A,title:H,html:V})=>{let m;return m=document.createElement(d),o&&(m.className=o),C&&(m.type=C),T&&(m.innerText=T),m.value=T,s&&(m.id=s),V&&(m.innerHTML=V),g&&(m.placeholder=g),m.name=q,H&&(m.title=H),A&&(m.ariaLabel=A),this.element.htmlFor=S,F&&(m.formAction=F),j&&(m.formEnctype=j),N&&(m.formMethod=N),O&&(m.formTarget=O),w&&(m.formNoValidate=w),this.element.appendChild(m),{on:(b="",M=()=>{})=>{this.element.addEventListener(b,M)},off:(b="",M=()=>{})=>{this.element.removeEventListener(b,M)},styles:(b={})=>{Object.assign(this.element.style,b)},remove:()=>{this.element.remove()}}}}}catch{throw new Error(new DOMException)}}on(l="",e=()=>{}){this.parent.addEventListener(l,e)}off(l="",e=()=>{}){this.parent.removeEventListener(l,e)}styles(l={}){Object.assign(this.parent.style,l)}addClass(l){this.element.classList.add(l)}removeClass(l){this.element.classList.remove(l)}select(l){let e=this.parent.querySelector(l);return{current:e,on:(t="",i=()=>{})=>{e.addEventListener(t,i)},off:(t="",i=()=>{})=>{e.removeEventListener(t,i)},styles:(t={})=>{Object.assign(e.style,t)},remove:()=>{e.remove()},addClass:t=>{e.classList.add(t)},removeClass:t=>{e.classList.remove(t)},update:({className:t,id:i,value:r,type:n,placeholder:y,name:E,htmlFor:L,formAction:a,formMethod:f,formTarget:h,formEnctype:c,formNoValidate:u,ariaLabel:p,title:v,html:d})=>{t&&(e.className=t),i&&(e.id=i),r&&(e.value=r),r&&(e.innerText=r),n&&(e.type=n),d&&(e.innerHTML=d),y&&(e.placeholder=y),e.name=E,e.htmlFor=L,a&&(e.formAction=a),c&&(e.formEnctype=c),f&&(e.formMethod=f),h&&(e.formTarget=h),u&&(e.formNoValidate=u),v&&(e.title=v),p&&(e.ariaLabel=p)},addChildren:({tag:t,className:i,id:r,value:n,type:y,placeholder:E,name:L,htmlFor:a,formAction:f,formMethod:h,formTarget:c,formEnctype:u,formNoValidate:p,ariaLabel:v,title:d,html:o})=>{try{return this.element=document.createElement(t),i&&(this.element.className=i),y&&(this.element.type=y),n&&(this.element.innerText=n),this.element.value=n,r&&(this.element.id=r),o&&(this.element.innerHTML=o),E&&(this.element.placeholder=E),this.element.name=L,d&&(this.element.title=d),v&&(this.element.ariaLabel=v),this.element.htmlFor=a,f&&(this.element.formAction=f),u&&(this.element.formEnctype=u),h&&(this.element.formMethod=h),c&&(this.element.formTarget=c),p&&(this.element.formNoValidate=p),e.appendChild(this.element),{on:(s="",T=()=>{})=>{this.element.addEventListener(s,T)},off:(s="",T=()=>{})=>{this.element.removeEventListener(s,T)},styles:(s={})=>{Object.assign(this.element.style,s)},remove:()=>{this.element.remove()}}}catch{throw new Error(new DOMException)}}}}selectAll(l){let e=this.parent.querySelectorAll(l);return{current:e,on:(t="",i=()=>{})=>{e.forEach(r=>r.addEventListener(t,i))},off:(t="",i=()=>{})=>{e.forEach(r=>r.removeEventListener(t,i))},styles:(t={})=>{e.forEach(i=>Object.assign(i.style,t))},remove:()=>{e.forEach(t=>t.remove())},addClass:t=>{e.forEach(i=>i.classList.add(t))},removeClass:t=>{e.forEach(i=>i.classList.remove(t))},update:(t={})=>{let{className:i,id:r,value:n,type:y,placeholder:E,name:L,htmlFor:a,formAction:f,formMethod:h,formTarget:c,formEnctype:u,formNoValidate:p,ariaLabel:v,title:d,html:o}=t;e.forEach(s=>{i!==void 0&&(s.className=i),r!==void 0&&(s.id=r),n!==void 0&&(s.value=n,s.innerText=n),y!==void 0&&(s.type=y),E!==void 0&&(s.placeholder=E),L!==void 0&&(s.name=L),o!==void 0&&(s.innerHTML=o),a!==void 0&&(s.htmlFor=a),f!==void 0&&(s.formAction=f),u!==void 0&&(s.formEnctype=u),h!==void 0&&(s.formMethod=h),c!==void 0&&(s.formTarget=c),p!==void 0&&(s.formNoValidate=p),d!==void 0&&(s.title=d),v!==void 0&&(s.ariaLabel=v)})},addChildren:t=>{let i=[];return e.forEach(r=>{let n=document.createElement(t.tag),{tag:y,className:E,id:L,value:a,type:f,placeholder:h,name:c,htmlFor:u,formAction:p,formMethod:v,formTarget:d,formEnctype:o,formNoValidate:s,ariaLabel:T,title:C,html:g}=t;E!==void 0&&(n.className=E),L!==void 0&&(n.id=L),a!==void 0&&(n.innerText=a,n.value=a),f!==void 0&&(n.type=f),h!==void 0&&(n.placeholder=h),c!==void 0&&(n.name=c),u!==void 0&&(n.htmlFor=u),p!==void 0&&(n.formAction=p),v!==void 0&&(n.formMethod=v),d!==void 0&&(n.formTarget=d),o!==void 0&&(n.formEnctype=o),s!==void 0&&(n.formNoValidate=s),T!==void 0&&(n.ariaLabel=T),C!==void 0&&(n.title=C),g!==void 0&&(n.innerHTML=g),r.appendChild(n),i.push(n)}),i}}}},k=x;})();
2
+ //# sourceMappingURL=luciddom.min.js.map
package/package.json CHANGED
@@ -1,6 +1,18 @@
1
1
  {
2
2
  "name": "luciddom",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "LucidDOM.js is a lightweight browser-side DOM helper. It wraps a parent element and provides methods for creating elements, selecting descendants, updating content, handling events, applying inline styles, and removing elements.",
5
+ "main": "dist/lucid.min.js",
6
+ "dependencies": {
7
+ "esbuild": "^0.28.2"
8
+ },
9
+ "devDependencies": {},
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1",
12
+ "build": "esbuild lucidDOM.js --bundle --minify --sourcemap --outfile=dist/luciddom.min.js"
13
+ },
14
+ "keywords": [],
15
+ "author": "",
16
+ "license": "ISC",
17
+ "type": "module"
18
+ }
package/styles.css ADDED
@@ -0,0 +1,739 @@
1
+ :root {
2
+ color-scheme: light;
3
+ --bg: #fffaf3;
4
+ --surface: #ffffff;
5
+ --fg: #f6f0e7;
6
+ --text: #25231f;
7
+ --secText: #77736c;
8
+ --accent: #f29a38;
9
+ --accent-deep: #df791b;
10
+ --line: #eee7dc;
11
+ --shadow: 0 20px 55px rgba(79, 57, 29, 0.09);
12
+ font-family: 'Segoe UI', Aptos, Arial, sans-serif;
13
+ color: var(--text);
14
+ background: var(--bg);
15
+ font-synthesis: none;
16
+ text-rendering: optimizeLegibility;
17
+ -webkit-font-smoothing: antialiased;
18
+ }
19
+
20
+ * {
21
+ box-sizing: border-box;
22
+ }
23
+
24
+ html {
25
+ scroll-behavior: smooth;
26
+ scroll-padding-top: 5rem;
27
+ }
28
+
29
+ body {
30
+ min-width: 320px;
31
+ margin: 0;
32
+ background:
33
+ radial-gradient(ellipse at 88% 18%, rgba(255, 227, 186, 0.28), transparent 28rem),
34
+ var(--bg);
35
+ }
36
+
37
+ a {
38
+ color: inherit;
39
+ text-decoration: none;
40
+ }
41
+
42
+ button {
43
+ font: inherit;
44
+ }
45
+
46
+ .site-header {
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: space-between;
50
+ width: min(1120px, calc(100% - 48px));
51
+ min-height: 82px;
52
+ margin: 0 auto;
53
+ }
54
+
55
+ .brand {
56
+ display: inline-flex;
57
+ align-items: center;
58
+ gap: 0.6rem;
59
+ font-family: 'Segoe UI', Aptos, Arial, sans-serif;
60
+ font-size: 1.2rem;
61
+ font-weight: 800;
62
+ letter-spacing: -0.06em;
63
+ }
64
+
65
+ .brand-mark {
66
+ display: grid;
67
+ width: 34px;
68
+ height: 34px;
69
+ place-items: center;
70
+ border-radius: 11px;
71
+ color: #fff;
72
+ background: var(--text);
73
+ font-family: Consolas, monospace;
74
+ font-size: 0.8rem;
75
+ letter-spacing: -0.1em;
76
+ }
77
+
78
+ .brand-dot {
79
+ color: var(--accent-deep);
80
+ }
81
+
82
+ .main-nav {
83
+ display: flex;
84
+ align-items: center;
85
+ gap: 2rem;
86
+ color: var(--secText);
87
+ font-size: 0.9rem;
88
+ font-weight: 600;
89
+ }
90
+
91
+ .main-nav a,
92
+ .site-footer > a:last-child,
93
+ .text-link {
94
+ transition: color 160ms ease;
95
+ }
96
+
97
+ .main-nav a:hover,
98
+ .site-footer > a:last-child:hover,
99
+ .text-link:hover {
100
+ color: var(--accent-deep);
101
+ }
102
+
103
+ .main-nav .nav-cta {
104
+ padding: 0.7rem 1rem;
105
+ border: 1px solid var(--line);
106
+ border-radius: 9px;
107
+ color: var(--text);
108
+ background: rgba(255, 255, 255, 0.65);
109
+ }
110
+
111
+ .main-nav .nav-cta:hover {
112
+ border-color: var(--accent);
113
+ color: var(--accent-deep);
114
+ }
115
+
116
+ .hero {
117
+ position: relative;
118
+ display: grid;
119
+ grid-template-columns: 1.05fr 0.95fr;
120
+ align-items: center;
121
+ gap: clamp(3rem, 8vw, 7rem);
122
+ width: min(1060px, calc(100% - 48px));
123
+ min-height: 570px;
124
+ margin: 0 auto;
125
+ padding: 4.5rem 0 5rem;
126
+ }
127
+
128
+ .hero-copy {
129
+ position: relative;
130
+ z-index: 1;
131
+ }
132
+
133
+ .eyebrow {
134
+ display: flex;
135
+ align-items: center;
136
+ gap: 0.55rem;
137
+ margin: 0 0 1.25rem;
138
+ color: var(--secText);
139
+ font-size: 0.76rem;
140
+ font-weight: 700;
141
+ letter-spacing: 0.12em;
142
+ text-transform: uppercase;
143
+ }
144
+
145
+ .eyebrow-dot {
146
+ width: 8px;
147
+ height: 8px;
148
+ border-radius: 50%;
149
+ background: var(--accent);
150
+ box-shadow: 0 0 0 4px rgba(242, 154, 56, 0.14);
151
+ }
152
+
153
+ h1,
154
+ h2,
155
+ h3,
156
+ p {
157
+ margin-top: 0;
158
+ }
159
+
160
+ h1,
161
+ h2,
162
+ h3 {
163
+ font-family: 'Segoe UI', Aptos, Arial, sans-serif;
164
+ }
165
+
166
+ .hero h1 {
167
+ margin-bottom: 1.15rem;
168
+ font-size: clamp(3.3rem, 6.5vw, 5.25rem);
169
+ font-weight: 800;
170
+ letter-spacing: -0.085em;
171
+ line-height: 0.99;
172
+ }
173
+
174
+ .headline-accent {
175
+ color: var(--accent-deep);
176
+ }
177
+
178
+ .hero-description {
179
+ max-width: 440px;
180
+ margin-bottom: 1.8rem;
181
+ color: var(--secText);
182
+ font-size: 1.05rem;
183
+ line-height: 1.8;
184
+ }
185
+
186
+ .hero-actions {
187
+ display: flex;
188
+ flex-wrap: wrap;
189
+ gap: 0.75rem;
190
+ }
191
+
192
+ .button {
193
+ display: inline-flex;
194
+ min-height: 48px;
195
+ align-items: center;
196
+ justify-content: center;
197
+ gap: 0.7rem;
198
+ padding: 0.8rem 1.1rem;
199
+ border: 1px solid transparent;
200
+ border-radius: 9px;
201
+ font-size: 0.88rem;
202
+ font-weight: 700;
203
+ transition: transform 160ms ease, box-shadow 160ms ease, background 160ms ease;
204
+ }
205
+
206
+ .button:hover {
207
+ transform: translateY(-2px);
208
+ }
209
+
210
+ .button-primary {
211
+ color: #fff;
212
+ background: var(--text);
213
+ box-shadow: 0 8px 20px rgba(37, 35, 31, 0.14);
214
+ }
215
+
216
+ .button-primary:hover {
217
+ background: #403b34;
218
+ box-shadow: 0 12px 24px rgba(37, 35, 31, 0.18);
219
+ }
220
+
221
+ .button-secondary {
222
+ border-color: var(--line);
223
+ background: rgba(255, 255, 255, 0.65);
224
+ }
225
+
226
+ .button-secondary:hover {
227
+ background: var(--surface);
228
+ }
229
+
230
+ .hero-footnote {
231
+ margin: 1.5rem 0 0;
232
+ color: #918b81;
233
+ font-size: 0.8rem;
234
+ }
235
+
236
+ .hero-footnote span {
237
+ margin-right: 0.35rem;
238
+ color: var(--accent-deep);
239
+ }
240
+
241
+ .code-card {
242
+ position: relative;
243
+ z-index: 1;
244
+ overflow: hidden;
245
+ border: 1px solid rgba(255, 255, 255, 0.12);
246
+ border-radius: 16px;
247
+ color: #e8e5df;
248
+ background: #252522;
249
+ box-shadow: 0 28px 70px rgba(55, 43, 28, 0.2);
250
+ transform: rotate(1.2deg);
251
+ }
252
+
253
+ .code-card-top {
254
+ display: flex;
255
+ align-items: center;
256
+ justify-content: space-between;
257
+ padding: 1.1rem 1.25rem;
258
+ border-bottom: 1px solid rgba(255, 255, 255, 0.09);
259
+ }
260
+
261
+ .window-dots {
262
+ display: flex;
263
+ gap: 6px;
264
+ }
265
+
266
+ .window-dots span {
267
+ width: 9px;
268
+ height: 9px;
269
+ border-radius: 50%;
270
+ background: #ff6d5e;
271
+ }
272
+
273
+ .window-dots span:nth-child(2) {
274
+ background: #f6bd4f;
275
+ }
276
+
277
+ .window-dots span:nth-child(3) {
278
+ background: #58c66d;
279
+ }
280
+
281
+ .code-label {
282
+ color: #99978f;
283
+ font-size: 0.72rem;
284
+ }
285
+
286
+ .code-card pre {
287
+ overflow-x: auto;
288
+ margin: 0;
289
+ padding: 1.55rem 1.5rem 1.7rem;
290
+ font-family: Consolas, 'Courier New', monospace;
291
+ font-size: clamp(0.7rem, 1.25vw, 0.85rem);
292
+ line-height: 1.8;
293
+ }
294
+
295
+ .code-purple {
296
+ color: #c5a5ff;
297
+ }
298
+
299
+ .code-green {
300
+ color: #a9d993;
301
+ }
302
+
303
+ .code-blue {
304
+ color: #7bc6e8;
305
+ }
306
+
307
+ .code-yellow {
308
+ color: #f6ce78;
309
+ }
310
+
311
+ .code-card-caption {
312
+ display: flex;
313
+ align-items: center;
314
+ gap: 0.55rem;
315
+ padding: 0.9rem 1.25rem;
316
+ border-top: 1px solid rgba(255, 255, 255, 0.09);
317
+ color: #bdb9ae;
318
+ font-size: 0.76rem;
319
+ }
320
+
321
+ .caption-check {
322
+ display: grid;
323
+ width: 17px;
324
+ height: 17px;
325
+ place-items: center;
326
+ border-radius: 50%;
327
+ color: #252522;
328
+ background: #a9d993;
329
+ font-size: 0.68rem;
330
+ font-weight: 800;
331
+ }
332
+
333
+ .hero-spark {
334
+ position: absolute;
335
+ color: #efb560;
336
+ pointer-events: none;
337
+ }
338
+
339
+ .hero-spark-one {
340
+ top: 13%;
341
+ right: 0;
342
+ font-size: 2rem;
343
+ }
344
+
345
+ .hero-spark-two {
346
+ right: 42%;
347
+ bottom: 10%;
348
+ color: #e5c9a0;
349
+ font-size: 1.2rem;
350
+ }
351
+
352
+ .feature-section {
353
+ padding: 5.5rem 24px 6rem;
354
+ background: var(--surface);
355
+ }
356
+
357
+ .section-heading {
358
+ width: min(650px, 100%);
359
+ margin: 0 auto 2.5rem;
360
+ text-align: center;
361
+ }
362
+
363
+ .section-heading .eyebrow {
364
+ justify-content: center;
365
+ margin-bottom: 0.8rem;
366
+ }
367
+
368
+ .section-heading h2,
369
+ .playground-copy h2 {
370
+ margin-bottom: 0.85rem;
371
+ font-size: clamp(2rem, 4vw, 2.8rem);
372
+ font-weight: 800;
373
+ letter-spacing: -0.065em;
374
+ line-height: 1.15;
375
+ }
376
+
377
+ .section-heading > p:last-child,
378
+ .playground-copy > p:not(.eyebrow) {
379
+ color: var(--secText);
380
+ line-height: 1.75;
381
+ }
382
+
383
+ .feature-grid {
384
+ display: grid;
385
+ grid-template-columns: repeat(3, minmax(0, 1fr));
386
+ gap: 1rem;
387
+ width: min(1000px, 100%);
388
+ margin: 0 auto;
389
+ }
390
+
391
+ .feature-card {
392
+ padding: 1.5rem;
393
+ border: 1px solid var(--line);
394
+ border-radius: 12px;
395
+ background: #fffdfa;
396
+ transition: transform 180ms ease, box-shadow 180ms ease;
397
+ }
398
+
399
+ .feature-card:hover {
400
+ transform: translateY(-4px);
401
+ box-shadow: var(--shadow);
402
+ }
403
+
404
+ .feature-icon {
405
+ display: grid;
406
+ width: 42px;
407
+ height: 42px;
408
+ place-items: center;
409
+ margin-bottom: 1.25rem;
410
+ border-radius: 12px;
411
+ font-size: 1.35rem;
412
+ font-weight: 600;
413
+ }
414
+
415
+ .icon-create {
416
+ color: #c86e1c;
417
+ background: #fff0d9;
418
+ }
419
+
420
+ .icon-select {
421
+ color: #63786a;
422
+ background: #eaf2e8;
423
+ }
424
+
425
+ .icon-update {
426
+ color: #7470a0;
427
+ background: #efedfb;
428
+ }
429
+
430
+ .feature-card h3 {
431
+ margin-bottom: 0.55rem;
432
+ font-size: 1.05rem;
433
+ letter-spacing: -0.025em;
434
+ }
435
+
436
+ .feature-card p {
437
+ min-height: 4.5rem;
438
+ margin-bottom: 1.1rem;
439
+ color: var(--secText);
440
+ font-size: 0.88rem;
441
+ line-height: 1.7;
442
+ }
443
+
444
+ .feature-card code {
445
+ display: inline-block;
446
+ padding: 0.4rem 0.55rem;
447
+ border-radius: 5px;
448
+ color: #a35c16;
449
+ background: #fff2df;
450
+ font-family: Consolas, 'Courier New', monospace;
451
+ font-size: 0.72rem;
452
+ }
453
+
454
+ .playground-section {
455
+ display: grid;
456
+ grid-template-columns: 1fr 0.85fr;
457
+ align-items: center;
458
+ gap: clamp(3rem, 10vw, 8rem);
459
+ width: min(900px, calc(100% - 48px));
460
+ margin: 0 auto;
461
+ padding: 6.5rem 0;
462
+ }
463
+
464
+ .playground-copy .eyebrow {
465
+ margin-bottom: 0.8rem;
466
+ }
467
+
468
+ .playground-copy h2 {
469
+ font-size: clamp(2.2rem, 4vw, 3rem);
470
+ }
471
+
472
+ .playground-copy > p:not(.eyebrow) {
473
+ max-width: 425px;
474
+ margin-bottom: 1rem;
475
+ }
476
+
477
+ .text-link {
478
+ display: inline-flex;
479
+ gap: 0.5rem;
480
+ color: var(--accent-deep);
481
+ font-size: 0.9rem;
482
+ font-weight: 700;
483
+ }
484
+
485
+ .counter-card {
486
+ padding: 1.5rem;
487
+ border: 1px solid var(--line);
488
+ border-radius: 16px;
489
+ background: var(--surface);
490
+ box-shadow: var(--shadow);
491
+ }
492
+
493
+ .counter-card-top {
494
+ display: flex;
495
+ align-items: center;
496
+ justify-content: space-between;
497
+ }
498
+
499
+ .counter-badge {
500
+ display: inline-flex;
501
+ align-items: center;
502
+ gap: 0.45rem;
503
+ padding: 0.35rem 0.55rem;
504
+ border-radius: 5px;
505
+ color: #4d7851;
506
+ background: #edf7eb;
507
+ font-size: 0.65rem;
508
+ font-weight: 800;
509
+ letter-spacing: 0.08em;
510
+ }
511
+
512
+ .counter-badge span {
513
+ color: #58a961;
514
+ font-size: 0.5rem;
515
+ }
516
+
517
+ .counter-spark {
518
+ color: var(--accent);
519
+ font-size: 1.4rem;
520
+ }
521
+
522
+ .counter-label {
523
+ margin: 1.4rem 0 0.1rem;
524
+ color: var(--secText);
525
+ font-size: 0.85rem;
526
+ font-weight: 600;
527
+ }
528
+
529
+ .counter {
530
+ display: block;
531
+ margin-bottom: 1.1rem;
532
+ font-family: 'Manrope', sans-serif;
533
+ font-size: 4.5rem;
534
+ font-weight: 800;
535
+ letter-spacing: -0.08em;
536
+ line-height: 1.15;
537
+ font-variant-numeric: tabular-nums;
538
+ }
539
+
540
+ .counter-controls {
541
+ display: grid;
542
+ grid-template-columns: 1fr 1fr;
543
+ gap: 0.6rem;
544
+ }
545
+
546
+ .counter-controls .btn {
547
+ min-height: 48px;
548
+ border: 1px solid var(--line);
549
+ border-radius: 8px;
550
+ cursor: pointer;
551
+ font-size: 1.25rem;
552
+ font-weight: 700;
553
+ transition: transform 140ms ease, background 140ms ease, border 140ms ease;
554
+ }
555
+
556
+ .counter-controls .btn:hover {
557
+ transform: translateY(-2px);
558
+ }
559
+
560
+ .counter-controls .sec {
561
+ color: var(--text);
562
+ background: #fff;
563
+ }
564
+
565
+ .counter-controls .sec:hover {
566
+ border-color: #d8c8b0;
567
+ background: #fffaf3;
568
+ }
569
+
570
+ .counter-controls .prim {
571
+ border-color: var(--text);
572
+ color: #fff;
573
+ background: var(--text);
574
+ }
575
+
576
+ .counter-controls .prim:hover {
577
+ background: #403b34;
578
+ }
579
+
580
+ .counter-controls .btn:focus-visible,
581
+ a:focus-visible {
582
+ outline: 3px solid rgba(242, 154, 56, 0.6);
583
+ outline-offset: 3px;
584
+ }
585
+
586
+ .counter-hint {
587
+ margin: 0.9rem 0 0;
588
+ color: #969087;
589
+ font-size: 0.75rem;
590
+ text-align: center;
591
+ }
592
+
593
+ .site-footer {
594
+ display: flex;
595
+ align-items: center;
596
+ justify-content: space-between;
597
+ gap: 1rem;
598
+ width: min(1120px, calc(100% - 48px));
599
+ min-height: 88px;
600
+ margin: 0 auto;
601
+ border-top: 1px solid var(--line);
602
+ }
603
+
604
+ .footer-brand {
605
+ font-size: 1rem;
606
+ }
607
+
608
+ .footer-brand .brand-mark {
609
+ width: 29px;
610
+ height: 29px;
611
+ border-radius: 9px;
612
+ font-size: 0.7rem;
613
+ }
614
+
615
+ .site-footer p,
616
+ .site-footer > a:last-child {
617
+ margin: 0;
618
+ color: var(--secText);
619
+ font-size: 0.78rem;
620
+ }
621
+
622
+ .site-footer > a:last-child span {
623
+ color: var(--accent-deep);
624
+ }
625
+
626
+ @media (max-width: 760px) {
627
+ .hero {
628
+ grid-template-columns: 1fr;
629
+ gap: 3rem;
630
+ min-height: auto;
631
+ padding: 4rem 0 4.5rem;
632
+ }
633
+
634
+ .hero-copy {
635
+ max-width: 580px;
636
+ }
637
+
638
+ .code-card {
639
+ width: min(560px, 100%);
640
+ margin: 0 auto;
641
+ transform: none;
642
+ }
643
+
644
+ .hero-spark-one {
645
+ top: 7%;
646
+ right: 2%;
647
+ }
648
+
649
+ .hero-spark-two {
650
+ display: none;
651
+ }
652
+
653
+ .feature-grid {
654
+ grid-template-columns: 1fr;
655
+ max-width: 520px;
656
+ }
657
+
658
+ .feature-card p {
659
+ min-height: 0;
660
+ }
661
+
662
+ .playground-section {
663
+ grid-template-columns: 1fr;
664
+ gap: 2rem;
665
+ max-width: 560px;
666
+ padding: 4.5rem 0;
667
+ }
668
+ }
669
+
670
+ @media (max-width: 520px) {
671
+ .site-header,
672
+ .site-footer,
673
+ .hero,
674
+ .playground-section {
675
+ width: calc(100% - 36px);
676
+ }
677
+
678
+ .site-header {
679
+ min-height: 72px;
680
+ }
681
+
682
+ .main-nav {
683
+ gap: 0.8rem;
684
+ font-size: 0.78rem;
685
+ }
686
+
687
+ .main-nav .nav-cta {
688
+ padding: 0.55rem 0.65rem;
689
+ }
690
+
691
+ .hero {
692
+ padding-top: 3.5rem;
693
+ }
694
+
695
+ .hero h1 {
696
+ font-size: clamp(3rem, 15vw, 4.3rem);
697
+ }
698
+
699
+ .hero-description {
700
+ font-size: 0.96rem;
701
+ }
702
+
703
+ .button {
704
+ min-height: 44px;
705
+ padding: 0.7rem 0.85rem;
706
+ font-size: 0.8rem;
707
+ }
708
+
709
+ .code-card pre {
710
+ padding: 1.2rem 1rem;
711
+ font-size: 0.68rem;
712
+ }
713
+
714
+ .feature-section {
715
+ padding: 4rem 18px;
716
+ }
717
+
718
+ .site-footer {
719
+ flex-wrap: wrap;
720
+ justify-content: space-between;
721
+ padding: 1.25rem 0;
722
+ }
723
+
724
+ .site-footer p {
725
+ order: 1;
726
+ width: 100%;
727
+ }
728
+ }
729
+
730
+ @media (prefers-reduced-motion: reduce) {
731
+ *,
732
+ *::before,
733
+ *::after {
734
+ scroll-behavior: auto !important;
735
+ transition-duration: 0.01ms !important;
736
+ animation-duration: 0.01ms !important;
737
+ animation-iteration-count: 1 !important;
738
+ }
739
+ }