elvish-css 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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +518 -0
  3. package/dist/elvish.css +2194 -0
  4. package/dist/elvish.d.ts +78 -0
  5. package/dist/elvish.esm.js +2185 -0
  6. package/dist/elvish.iife.js +2169 -0
  7. package/dist/elvish.min.css +9 -0
  8. package/dist/elvish.umd.js +2173 -0
  9. package/elvish.css +28 -0
  10. package/elvish.js +81 -0
  11. package/global/global.css +16 -0
  12. package/global/modern.css +305 -0
  13. package/global/reset.css +507 -0
  14. package/global/tokens.css +154 -0
  15. package/global/transitions.css +288 -0
  16. package/global/transitions.js +289 -0
  17. package/global/utilities.css +151 -0
  18. package/package.json +61 -0
  19. package/primitives/adleithian/adleithian.css +16 -0
  20. package/primitives/adleithian/adleithian.js +63 -0
  21. package/primitives/bau/bau.css +86 -0
  22. package/primitives/bau/bau.js +127 -0
  23. package/primitives/enedh/enedh.css +38 -0
  24. package/primitives/enedh/enedh.js +110 -0
  25. package/primitives/esgal/esgal.css +39 -0
  26. package/primitives/esgal/esgal.js +115 -0
  27. package/primitives/fano/fano.css +28 -0
  28. package/primitives/fano/fano.js +108 -0
  29. package/primitives/gant-thala/gant-thala.css +32 -0
  30. package/primitives/gant-thala/gant-thala.js +69 -0
  31. package/primitives/glan-tholl/glan-tholl.css +71 -0
  32. package/primitives/glan-tholl/glan-tholl.js +147 -0
  33. package/primitives/glan-veleg/glan-veleg.css +45 -0
  34. package/primitives/glan-veleg/glan-veleg.js +138 -0
  35. package/primitives/gonath/gonath.css +57 -0
  36. package/primitives/gonath/gonath.js +113 -0
  37. package/primitives/gwistindor/gwistindor.css +52 -0
  38. package/primitives/gwistindor/gwistindor.js +96 -0
  39. package/primitives/hath/hath.css +39 -0
  40. package/primitives/hath/hath.js +107 -0
  41. package/primitives/him/him.css +43 -0
  42. package/primitives/him/him.js +169 -0
  43. package/primitives/miriant/miriant.css +75 -0
  44. package/primitives/miriant/miriant.js +158 -0
  45. package/primitives/thann/thann.css +57 -0
  46. package/primitives/thann/thann.js +96 -0
  47. package/primitives/tiniath/tiniath.css +16 -0
  48. package/primitives/tiniath/tiniath.js +88 -0
  49. package/primitives/vircantie/vircantie.css +24 -0
  50. package/primitives/vircantie/vircantie.js +83 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mark Manley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,518 @@
1
+ # Elvish - Intrinsic CSS Layout System
2
+
3
+ A complete implementation of the [Every Layout](https://every-layout.dev) design system as custom elements, with **Sindarin names** from Tolkien's Elvish languages. Extended with modern CSS features including `@function`, `if()`, `sibling-index()`, and typed `attr()`.
4
+
5
+ *Mae govannen!* (Well met!)
6
+
7
+ ## Philosophy
8
+
9
+ Elvish is built on three principles:
10
+
11
+ 1. **Intrinsic design** - Layouts that respond to their content and context, not arbitrary breakpoints
12
+ 2. **Composition over inheritance** - Simple primitives that combine to create complex layouts
13
+ 3. **Algorithmic CSS** - Let the browser calculate optimal layouts
14
+
15
+ ## Sindarin Naming Convention
16
+
17
+ All layout primitives use Sindarin (Grey-Elvish) names:
18
+
19
+ | Sindarin | English | Meaning |
20
+ |----------|---------|---------|
21
+ | `<i-hath>` | Stack | "row, series" |
22
+ | `<i-bau>` | Box | "container" |
23
+ | `<i-enedh>` | Center | "middle" |
24
+ | `<i-tiniath>` | Cluster | "small sparks" |
25
+ | `<i-glan-veleg>` | Sidebar | "clear + mighty" |
26
+ | `<i-gwistindor>` | Switcher | "change-watcher" |
27
+ | `<i-esgal>` | Cover | "screen, hiding" |
28
+ | `<i-vircantie>` | Grid | "jewel-pattern" |
29
+ | `<i-gant-thala>` | Frame | "harp-foot (ratio)" |
30
+ | `<i-glan-tholl>` | Reel | "open + hollow" |
31
+ | `<i-fano>` | Imposter | "white phantom" |
32
+ | `<i-thann>` | Icon | "sign, token" |
33
+ | `<i-adleithian>` | Container | "liberator" |
34
+ | `<i-him>` | Sticky | "steadfast" |
35
+ | `<i-miriant>` | Grid-placed | "jewel-work" |
36
+ | `<i-gonath>` | Masonry | "stone collection" |
37
+
38
+ **Special attributes for `<i-thann>` (Icon):**
39
+ - `echuiol` = "awakening" (active state)
40
+ - `dhoren` = "hidden" (visually hidden)
41
+
42
+ ## Installation
43
+
44
+ ### CDN (Quickest)
45
+
46
+ ```html
47
+ <!-- unpkg -->
48
+ <link rel="stylesheet" href="https://unpkg.com/elvish-layout@2.0.0/dist/elvish.min.css">
49
+ <script src="https://unpkg.com/elvish-layout@2.0.0/dist/elvish.iife.js"></script>
50
+
51
+ <!-- jsDelivr -->
52
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/elvish-layout@2.0.0/dist/elvish.min.css">
53
+ <script src="https://cdn.jsdelivr.net/npm/elvish-layout@2.0.0/dist/elvish.iife.js"></script>
54
+ ```
55
+
56
+ ### npm
57
+
58
+ ```bash
59
+ npm install elvish-layout
60
+ ```
61
+
62
+ ```javascript
63
+ // ES Modules
64
+ import 'elvish-layout/dist/elvish.css';
65
+ import { transition, transitionTheme } from 'elvish-layout';
66
+ ```
67
+
68
+ ### Self-Hosted / Ignition
69
+
70
+ Download the `dist/` folder and serve from your own CDN or Ignition gateway. See [DEPLOYMENT.md](./DEPLOYMENT.md) for detailed instructions including Ignition Perspective integration.
71
+
72
+ ### Development (Source Files)
73
+
74
+ ```html
75
+ <link rel="stylesheet" href="global/global.css">
76
+ <link rel="stylesheet" href="elvish.css">
77
+ <script type="module" src="elvish.js"></script>
78
+ ```
79
+
80
+ ## Modern CSS Features
81
+
82
+ Elvish includes support for cutting-edge CSS features. Browser support as of January 2026:
83
+
84
+ | Feature | Chrome | Edge | Safari | Firefox |
85
+ |---------|--------|------|--------|---------|
86
+ | `light-dark()` | ✅ 123+ | ✅ | ✅ 17.5+ | ✅ 120+ |
87
+ | Relative Colors | ✅ 119+ | ✅ | ✅ 16.4+ | ✅ 128+ |
88
+ | `@function` | ✅ 139+ | ✅ 139+ | ❌ | ❌ |
89
+ | `if()` | ✅ 137+ | ✅ 137+ | ❌ | ❌ |
90
+ | `sibling-index()` | ✅ 138+ | ✅ 138+ | ❌ | ❌ |
91
+ | Typed `attr()` | ✅ 133+ | ✅ 133+ | ⚠️ | ❌ |
92
+ | View Transitions | ✅ 111+ | ✅ 111+ | ✅ 18+ | ✅ 144+ |
93
+
94
+ ### `light-dark()` - Automatic Theme Colors
95
+
96
+ Single declarations that respond to color scheme:
97
+
98
+ ```css
99
+ :root {
100
+ color-scheme: light dark;
101
+ --color-surface: light-dark(#ffffff, #1a1a2e);
102
+ --color-text: light-dark(#1a1a2e, #f0f0f5);
103
+ }
104
+ ```
105
+
106
+ ### Relative Color Syntax - Derived Palettes
107
+
108
+ Define one brand color, derive entire palettes:
109
+
110
+ ```css
111
+ :root {
112
+ --brand: oklch(55% 0.18 250);
113
+
114
+ /* Lighten/darken */
115
+ --brand-light: oklch(from var(--brand) calc(l + 0.15) c h);
116
+ --brand-dark: oklch(from var(--brand) calc(l - 0.1) c h);
117
+
118
+ /* Hue shift for complementary */
119
+ --brand-complement: oklch(from var(--brand) l c calc(h + 180));
120
+
121
+ /* Transparency */
122
+ --brand-ghost: oklch(from var(--brand) l c h / 0.1);
123
+ }
124
+ ```
125
+
126
+ ### `@function` - Reusable CSS Logic (Chrome 139+)
127
+
128
+ Define custom functions for reusable calculations:
129
+
130
+ ```css
131
+ @function --negate(--value) {
132
+ result: calc(-1 * var(--value));
133
+ }
134
+
135
+ @function --alpha(--color, --opacity) {
136
+ result: oklch(from var(--color) l c h / var(--opacity));
137
+ }
138
+
139
+ .element {
140
+ margin-left: --negate(20px); /* -20px */
141
+ background: --alpha(var(--brand), 0.5); /* 50% opacity */
142
+ }
143
+ ```
144
+
145
+ ### `if()` - Inline Conditionals (Chrome 137+)
146
+
147
+ Conditional values without class toggling:
148
+
149
+ ```css
150
+ .card {
151
+ --variant: default;
152
+
153
+ padding: if(
154
+ style(--variant: compact): var(--s-1);
155
+ style(--variant: spacious): var(--s2);
156
+ else: var(--s1)
157
+ );
158
+ }
159
+
160
+ .grid {
161
+ grid-template-columns: if(
162
+ media(width >= 900px): repeat(4, 1fr);
163
+ media(width >= 600px): repeat(2, 1fr);
164
+ else: 1fr
165
+ );
166
+ }
167
+ ```
168
+
169
+ ### `sibling-index()` - Position Awareness (Chrome 138+)
170
+
171
+ Elements know their position. No JavaScript needed for staggers:
172
+
173
+ ```css
174
+ .stagger > * {
175
+ animation-delay: calc((sibling-index() - 1) * 100ms);
176
+ }
177
+
178
+ .rainbow > * {
179
+ --hue: calc(sibling-index() * (360 / sibling-count()));
180
+ background: oklch(70% 0.15 var(--hue));
181
+ }
182
+ ```
183
+
184
+ ### Typed `attr()` - HTML to CSS Bridge (Chrome 133+)
185
+
186
+ Use HTML attributes as typed CSS values:
187
+
188
+ ```html
189
+ <div data-columns="4">Grid with 4 columns</div>
190
+ <div data-color="oklch(60% 0.2 250)">Blue text</div>
191
+ <div data-progress="65%">Progress bar</div>
192
+ ```
193
+
194
+ ```css
195
+ [data-columns] {
196
+ --columns: attr(data-columns type(<number>), 1);
197
+ grid-template-columns: repeat(var(--columns), 1fr);
198
+ }
199
+
200
+ [data-color] {
201
+ color: attr(data-color type(<color>), currentColor);
202
+ }
203
+
204
+ [data-progress] {
205
+ --progress: attr(data-progress type(<percentage>), 0%);
206
+ background: linear-gradient(to right, accent var(--progress), gray var(--progress));
207
+ }
208
+ ```
209
+
210
+ ### View Transitions - Buttery Smooth Layout Changes
211
+
212
+ **Baseline Newly Available** since October 2025 (Firefox 144). Smooth, animated transitions between layout states without JavaScript animation libraries:
213
+
214
+ ```css
215
+ /* Enable in transitions.css */
216
+ @view-transition {
217
+ navigation: auto;
218
+ }
219
+
220
+ /* Named elements animate independently */
221
+ .sidebar { view-transition-name: glan-veleg; }
222
+ .grid { view-transition-name: vircantie; }
223
+
224
+ /* Auto-naming for lists/grids - each item gets unique name */
225
+ .card-grid > * {
226
+ view-transition-name: match-element;
227
+ view-transition-class: card; /* Group styling */
228
+ }
229
+
230
+ /* Style all cards at once */
231
+ ::view-transition-group(.card) {
232
+ animation-duration: 300ms;
233
+ animation-timing-function: var(--transition-ease-spring);
234
+ }
235
+
236
+ /* Customize specific animations */
237
+ ::view-transition-old(glan-veleg) {
238
+ animation: 300ms ease-out slide-out-left;
239
+ }
240
+ ::view-transition-new(glan-veleg) {
241
+ animation: 300ms ease-out slide-in-left;
242
+ }
243
+ ```
244
+
245
+ ```javascript
246
+ // Wrap DOM changes in a transition
247
+ import { transition, transitionTheme, transitionRatio } from './global/transitions.js';
248
+
249
+ // Basic usage
250
+ transition(() => {
251
+ sidebar.classList.toggle('collapsed');
252
+ });
253
+
254
+ // With typed transitions for CSS targeting
255
+ transition(() => {
256
+ grid.setAttribute('columns', '4');
257
+ }, { types: ['layout'] });
258
+
259
+ // Built-in Elvish helpers
260
+ transitionTheme('dark'); // Smooth theme switch
261
+ transitionRatio('golden'); // Smooth ratio change
262
+
263
+ // Auto-naming for grid items
264
+ import { enableAutoNaming } from './global/transitions.js';
265
+ enableAutoNaming(gridElement, 'card'); // Each child animates independently
266
+ ```
267
+
268
+ **Included transition animations:** fade, slide (up/down/left/right), scale, flip
269
+
270
+ **New features:** `match-element` auto-naming, `view-transition-class` group styling, `:active-view-transition` pseudo-class
271
+
272
+ **Fallback:** Browsers without View Transitions get CSS `transition` fallbacks on common properties.
273
+
274
+ ## Layout Primitives
275
+
276
+ ### Core Elements (16)
277
+
278
+ | Sindarin | English | Key Props |
279
+ |----------|---------|-----------|
280
+ | `<i-hath>` | Stack | `space`, `recursive`, `split-after` |
281
+ | `<i-bau>` | Box | `padding`, `border-width`, `invert` |
282
+ | `<i-enedh>` | Center | `max`, `gutters`, `intrinsic` |
283
+ | `<i-tiniath>` | Cluster | `space`, `justify`, `align` |
284
+ | `<i-glan-veleg>` | Sidebar | `side`, `side-width`, `content-min` |
285
+ | `<i-gwistindor>` | Switcher | `threshold`, `space`, `limit` |
286
+ | `<i-esgal>` | Cover | `centered`, `space`, `min-height` |
287
+ | `<i-vircantie>` | Grid | `min`, `space` |
288
+ | `<i-gant-thala>` | Frame | `ratio` |
289
+ | `<i-glan-tholl>` | Reel | `item-width`, `space`, `no-bar` |
290
+ | `<i-fano>` | Imposter | `fixed`, `contain`, `margin` |
291
+ | `<i-thann>` | Icon | `space`, `label`, `echuiol`, `dhoren` |
292
+ | `<i-adleithian>` | Container | `name` |
293
+ | `<i-him>` | Sticky | `to`, `offset`, `sentinel` |
294
+ | `<i-miriant>` | Grid-placed | `columns`, `space`, `dense` |
295
+ | `<i-gonath>` | Masonry | `columns`, `space` |
296
+
297
+ ## Usage Examples
298
+
299
+ ### Basic Hath (Stack)
300
+
301
+ ```html
302
+ <i-hath space="var(--s2)">
303
+ <h1>Title</h1>
304
+ <p>Paragraph</p>
305
+ </i-hath>
306
+ ```
307
+
308
+ ### Responsive Vircantie (Grid)
309
+
310
+ ```html
311
+ <i-vircantie min="250px" space="var(--s1)">
312
+ <div>Card 1</div>
313
+ <div>Card 2</div>
314
+ <div>Card 3</div>
315
+ </i-vircantie>
316
+ ```
317
+
318
+ ### Composition
319
+
320
+ ```html
321
+ <i-enedh max="80ch" gutters="var(--s1)">
322
+ <i-hath space="var(--s3)">
323
+ <i-glan-veleg side-width="200px">
324
+ <nav>Sidebar</nav>
325
+ <main>
326
+ <i-vircantie min="200px">
327
+ <i-bau>Card</i-bau>
328
+ <i-bau>Card</i-bau>
329
+ </i-vircantie>
330
+ </main>
331
+ </i-glan-veleg>
332
+ </i-hath>
333
+ </i-enedh>
334
+ ```
335
+
336
+ ### Staggered Animation (Modern)
337
+
338
+ ```html
339
+ <ul class="stagger-enter">
340
+ <li>Animates first</li>
341
+ <li>Then this</li>
342
+ <li>Then this</li>
343
+ </ul>
344
+ ```
345
+
346
+ ## Design Tokens
347
+
348
+ ### Modular Scale Ratios
349
+
350
+ Elvish uses a modular scale for harmonious spacing and typography. Choose the ratio that fits your design:
351
+
352
+ | Ratio | Value | Character | Best For |
353
+ |-------|-------|-----------|----------|
354
+ | **Golden (φ)** | 1.618 | Dramatic, organic | Marketing, CTAs, hero sections |
355
+ | **Silver (√2)** | 1.414 | Subtle, refined | Documentation, dashboards, dense content |
356
+ | **Fifth** | 1.5 | Balanced, musical | General purpose |
357
+
358
+ **Default:** Perfect Fifth (1.5) for balanced harmony.
359
+
360
+ ```css
361
+ /* Switch globally */
362
+ :root {
363
+ --ratio: var(--ratio-golden); /* For dramatic impact */
364
+ }
365
+
366
+ /* Switch per-section */
367
+ .marketing-hero {
368
+ --ratio: var(--ratio-golden);
369
+ }
370
+ .documentation {
371
+ --ratio: var(--ratio-silver);
372
+ }
373
+
374
+ /* Use utility classes */
375
+ <div class="ratio:silver">Subtle scale</div>
376
+ <div class="ratio:golden">Dramatic scale</div>
377
+
378
+ /* Or data attributes (for JS toggling) */
379
+ <div data-ratio="golden">...</div>
380
+ ```
381
+
382
+ **Scale values at 16px base:**
383
+
384
+ ```
385
+ Golden (φ) Silver (√2) Fifth (1.5)
386
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
387
+ s-2 6px 8px 7px
388
+ s-1 10px 11px 11px
389
+ s0 16px 16px 16px ← base
390
+ s1 26px 23px 24px
391
+ s2 42px 32px 36px
392
+ s3 68px 45px 54px
393
+ s4 110px 64px 81px
394
+ s5 178px 91px 122px
395
+ ```
396
+
397
+ ### Token Reference
398
+
399
+ ```css
400
+ :root {
401
+ /* Ratio presets */
402
+ --ratio-golden: 1.618;
403
+ --ratio-silver: 1.414;
404
+ --ratio-fifth: 1.5;
405
+ --ratio: var(--ratio-fifth); /* Active ratio (default: Fifth) */
406
+
407
+ /* Modular scale */
408
+ --s-2, --s-1, --s0, --s1, --s2, --s3, --s4, --s5
409
+
410
+ /* MEASURE vs LAYOUT THRESHOLDS
411
+ * --measure: For TEXT readability (ch units)
412
+ * --layout-threshold-*: For LAYOUT decisions (rem units)
413
+ *
414
+ * Text is about characters; layout is about physical space.
415
+ */
416
+ --measure: 70ch; /* Golden=60ch, Fifth=70ch, Silver=80ch */
417
+
418
+ /* Layout thresholds */
419
+ --layout-threshold-sm: 30rem; /* ~480px - phone landscape */
420
+ --layout-threshold-md: 45rem; /* ~720px - tablet portrait */
421
+ --layout-threshold-lg: 60rem; /* ~960px - tablet landscape */
422
+ --layout-threshold-xl: 75rem; /* ~1200px - small desktop */
423
+
424
+ /* Brand + derived colors (relative color syntax) */
425
+ --brand, --brand-light, --brand-dark, --brand-complement
426
+
427
+ /* Semantic (auto light/dark) */
428
+ --color-surface, --color-text, --color-accent, --color-border
429
+
430
+ /* Timing */
431
+ --duration-fast, --duration-normal, --duration-slow
432
+
433
+ /* Easing */
434
+ --ease-out, --ease-in, --ease-bounce
435
+ }
436
+ ```
437
+
438
+ ### Measure vs Layout Thresholds
439
+
440
+ Elvish distinguishes between two types of "widths":
441
+
442
+ | Token | Purpose | Unit | Example |
443
+ |-------|---------|------|---------|
444
+ | `--measure` | Text readability | `ch` | `max-inline-size: var(--measure)` |
445
+ | `--layout-threshold-*` | Layout switching | `rem` | `<i-gwistindor threshold="45rem">` |
446
+
447
+ **Why the distinction?**
448
+ - Text measure is about *characters per line* for readability (45-75ch optimal)
449
+ - Layout thresholds are about *physical space* for comfortable item widths
450
+ - A 70ch measure at 16px ≈ 560px—too narrow for layout decisions!
451
+
452
+ ```html
453
+ <!-- TEXT: Use measure -->
454
+ <article style="max-inline-size: var(--measure)">
455
+ Readable prose...
456
+ </article>
457
+
458
+ <!-- LAYOUT: Use layout thresholds -->
459
+ <i-gwistindor threshold="var(--layout-threshold-md)">
460
+ <div>Horizontal until 720px</div>
461
+ <div>Then vertical</div>
462
+ </i-gwistindor>
463
+ ```
464
+
465
+ ## File Structure
466
+
467
+ ```
468
+ elvish/
469
+ ├── global/
470
+ │ ├── tokens.css # Design tokens, light-dark(), relative colors
471
+ │ ├── reset.css # Reset + measure axiom
472
+ │ ├── utilities.css # Utility classes
473
+ │ ├── modern.css # @function, if(), sibling-index(), attr()
474
+ │ ├── transitions.css # View Transitions API
475
+ │ ├── transitions.js # View Transitions helpers
476
+ │ └── global.css # Imports all CSS
477
+ ├── primitives/ # Sindarin-named layout primitives
478
+ │ ├── hath/ # Stack
479
+ │ ├── bau/ # Box
480
+ │ ├── enedh/ # Center
481
+ │ ├── tiniath/ # Cluster
482
+ │ ├── glan-veleg/ # Sidebar
483
+ │ ├── gwistindor/ # Switcher
484
+ │ ├── esgal/ # Cover
485
+ │ ├── vircantie/ # Grid
486
+ │ ├── gant-thala/ # Frame (aspect)
487
+ │ ├── glan-tholl/ # Reel (side-scroll)
488
+ │ ├── fano/ # Imposter (overlay)
489
+ │ ├── thann/ # Icon
490
+ │ ├── adleithian/ # Container
491
+ │ ├── him/ # Sticky
492
+ │ ├── miriant/ # Grid-placed
493
+ │ └── gonath/ # Masonry
494
+ ├── examples/
495
+ │ └── complete-demo.html
496
+ ├── elvish.css # All primitive styles
497
+ ├── elvish.js # All primitive JS + SINDARIN vocabulary
498
+ └── README.md
499
+ ```
500
+
501
+ ## Progressive Enhancement
502
+
503
+ Elvish includes fallbacks for browsers without modern feature support:
504
+
505
+ ```css
506
+ /* Fallback for sibling-index() */
507
+ @supports not (animation-delay: calc(sibling-index() * 1ms)) {
508
+ .stagger-enter > :nth-child(1) { --i: 0; }
509
+ .stagger-enter > :nth-child(2) { --i: 1; }
510
+ /* ... */
511
+ }
512
+
513
+ /* Fallback for if() */
514
+ @supports not (color: if(style(--x: y): red; else: blue)) {
515
+ .theme-aware { background: var(--fallback); }
516
+ .theme-aware.is-dark { background: var(--dark-fallback); }
517
+ }
518
+ ```