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.
- package/LICENSE +21 -0
- package/README.md +518 -0
- package/dist/elvish.css +2194 -0
- package/dist/elvish.d.ts +78 -0
- package/dist/elvish.esm.js +2185 -0
- package/dist/elvish.iife.js +2169 -0
- package/dist/elvish.min.css +9 -0
- package/dist/elvish.umd.js +2173 -0
- package/elvish.css +28 -0
- package/elvish.js +81 -0
- package/global/global.css +16 -0
- package/global/modern.css +305 -0
- package/global/reset.css +507 -0
- package/global/tokens.css +154 -0
- package/global/transitions.css +288 -0
- package/global/transitions.js +289 -0
- package/global/utilities.css +151 -0
- package/package.json +61 -0
- package/primitives/adleithian/adleithian.css +16 -0
- package/primitives/adleithian/adleithian.js +63 -0
- package/primitives/bau/bau.css +86 -0
- package/primitives/bau/bau.js +127 -0
- package/primitives/enedh/enedh.css +38 -0
- package/primitives/enedh/enedh.js +110 -0
- package/primitives/esgal/esgal.css +39 -0
- package/primitives/esgal/esgal.js +115 -0
- package/primitives/fano/fano.css +28 -0
- package/primitives/fano/fano.js +108 -0
- package/primitives/gant-thala/gant-thala.css +32 -0
- package/primitives/gant-thala/gant-thala.js +69 -0
- package/primitives/glan-tholl/glan-tholl.css +71 -0
- package/primitives/glan-tholl/glan-tholl.js +147 -0
- package/primitives/glan-veleg/glan-veleg.css +45 -0
- package/primitives/glan-veleg/glan-veleg.js +138 -0
- package/primitives/gonath/gonath.css +57 -0
- package/primitives/gonath/gonath.js +113 -0
- package/primitives/gwistindor/gwistindor.css +52 -0
- package/primitives/gwistindor/gwistindor.js +96 -0
- package/primitives/hath/hath.css +39 -0
- package/primitives/hath/hath.js +107 -0
- package/primitives/him/him.css +43 -0
- package/primitives/him/him.js +169 -0
- package/primitives/miriant/miriant.css +75 -0
- package/primitives/miriant/miriant.js +158 -0
- package/primitives/thann/thann.css +57 -0
- package/primitives/thann/thann.js +96 -0
- package/primitives/tiniath/tiniath.css +16 -0
- package/primitives/tiniath/tiniath.js +88 -0
- package/primitives/vircantie/vircantie.css +24 -0
- 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
|
+
```
|