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
@@ -0,0 +1,2194 @@
1
+ /**
2
+ * Elvish CSS Layout-first System v1.0.0
3
+ * Intrinsic CSS layout primitives with Sindarin names
4
+ *
5
+ * https://github.com/star-this/elvish-css
6
+ * License: MIT
7
+ */
8
+
9
+ /* === global/tokens.css === */
10
+ /**
11
+ * Elvish - Design Tokens
12
+ *
13
+ * Modular scale and design tokens for consistent spacing,
14
+ * typography, and colors throughout the system.
15
+ *
16
+ * Uses modern CSS features:
17
+ * - light-dark() for automatic theme switching
18
+ * - Relative color syntax (oklch) for derived colors
19
+ *
20
+ * RATIO MODES:
21
+ * - golden (φ = 1.618): Classical, dramatic, organic - best for CTAs & impact
22
+ * - silver (√2 = 1.414): Subtle, modern, ISO paper ratios - best for dense content
23
+ * - fifth (1.5): Musical, balanced middle-ground
24
+ *
25
+ * To change: set --ratio-mode on :root or any container
26
+ * :root { --ratio-mode: silver; }
27
+ * .documentation { --ratio-mode: silver; }
28
+ */
29
+
30
+ :root {
31
+ /* ===== COLOR SCHEME ===== */
32
+ color-scheme: light dark;
33
+
34
+ /* ===== RATIO PRESETS ===== */
35
+ --ratio-golden: 1.618; /* φ - Golden Ratio */
36
+ --ratio-silver: 1.414; /* √2 - Silver/Lichtenberg Ratio */
37
+ --ratio-fifth: 1.5; /* Perfect Fifth (musical) */
38
+
39
+ /* ===== ACTIVE RATIO ===== */
40
+ /* Default: Perfect Fifth for balanced harmony */
41
+ --ratio: var(--ratio-fifth);
42
+
43
+ /* ===== MODULAR SCALE ===== */
44
+ /*
45
+ * Golden Ratio scale at 16px base:
46
+ * s-2: 6px | s-1: 10px | s0: 16px (base)
47
+ * s1: 26px | s2: 42px | s3: 68px
48
+ * s4: 110px | s5: 178px
49
+ */
50
+ --s-5: calc(var(--s-4) / var(--ratio));
51
+ --s-4: calc(var(--s-3) / var(--ratio));
52
+ --s-3: calc(var(--s-2) / var(--ratio));
53
+ --s-2: calc(var(--s-1) / var(--ratio));
54
+ --s-1: calc(var(--s0) / var(--ratio));
55
+ --s0: 1rem;
56
+ --s1: calc(var(--s0) * var(--ratio));
57
+ --s2: calc(var(--s1) * var(--ratio));
58
+ --s3: calc(var(--s2) * var(--ratio));
59
+ --s4: calc(var(--s3) * var(--ratio));
60
+ --s5: calc(var(--s4) * var(--ratio));
61
+
62
+ /* ===== MEASURE (Text) ===== */
63
+ /* Maximum line length for readable text - varies by ratio */
64
+ --measure: 70ch; /* Default for Fifth; Golden=60ch, Silver=80ch */
65
+
66
+ /* ===== LAYOUT THRESHOLDS ===== */
67
+ /*
68
+ * These are for LAYOUT decisions, not text readability.
69
+ * Use rem (not ch) because layout is about physical space, not characters.
70
+ */
71
+ --layout-threshold-sm: 30rem; /* ~480px - phone landscape */
72
+ --layout-threshold-md: 45rem; /* ~720px - tablet portrait */
73
+ --layout-threshold-lg: 60rem; /* ~960px - tablet landscape */
74
+ --layout-threshold-xl: 75rem; /* ~1200px - small desktop */
75
+
76
+ /* ===== BRAND COLOR (Single source of truth) ===== */
77
+ /* Teal: rgba(0, 233, 188, 1) converted to OKLCH */
78
+ --brand: oklch(83% 0.17 168);
79
+
80
+ /* ===== DERIVED COLORS (Relative Color Syntax) ===== */
81
+ /* Lighten/darken by adjusting L (lightness) */
82
+ --brand-lighter: oklch(from var(--brand) calc(l + 0.3) c h);
83
+ --brand-light: oklch(from var(--brand) calc(l + 0.15) c h);
84
+ --brand-dark: oklch(from var(--brand) calc(l - 0.1) c h);
85
+ --brand-darker: oklch(from var(--brand) calc(l - 0.2) c h);
86
+
87
+ /* Desaturate by reducing C (chroma) */
88
+ --brand-muted: oklch(from var(--brand) l calc(c * 0.5) h);
89
+ --brand-subtle: oklch(from var(--brand) l calc(c * 0.25) h);
90
+
91
+ /* Transparency variants */
92
+ --brand-ghost: oklch(from var(--brand) l c h / 0.1);
93
+ --brand-overlay: oklch(from var(--brand) l c h / 0.85);
94
+
95
+ /* Complementary/Analogous via hue shift */
96
+ --brand-complement: oklch(from var(--brand) l c calc(h + 180));
97
+ --brand-analogous-1: oklch(from var(--brand) l c calc(h + 30));
98
+ --brand-analogous-2: oklch(from var(--brand) l c calc(h - 30));
99
+
100
+ /* ===== SEMANTIC COLORS (light-dark auto-switching) ===== */
101
+ --color-surface: light-dark(#ffffff, #1a1a2e);
102
+ --color-surface-raised: light-dark(#f8f9fa, #252538);
103
+ --color-surface-sunken: light-dark(#e9ecef, #12121c);
104
+
105
+ --color-text: light-dark(#1a1a2e, #f0f0f5);
106
+ --color-text-muted: light-dark(#6c757d, #9ca3af);
107
+ --color-text-subtle: light-dark(#adb5bd, #6b7280);
108
+
109
+ --color-border: light-dark(#dee2e6, #374151);
110
+ --color-border-strong: light-dark(#adb5bd, #4b5563);
111
+
112
+ /* Accent adapts to theme */
113
+ --color-accent: light-dark(
114
+ oklch(from var(--brand) calc(l - 0.05) c h),
115
+ oklch(from var(--brand) calc(l + 0.1) c h)
116
+ );
117
+ --color-accent-hover: light-dark(
118
+ oklch(from var(--brand) calc(l - 0.15) c h),
119
+ oklch(from var(--brand) calc(l + 0.2) c h)
120
+ );
121
+ --color-accent-text: light-dark(#ffffff, #1a1a2e);
122
+
123
+ /* Status colors */
124
+ --color-success: light-dark(oklch(55% 0.17 145), oklch(65% 0.17 145));
125
+ --color-warning: light-dark(oklch(70% 0.15 85), oklch(75% 0.15 85));
126
+ --color-danger: light-dark(oklch(55% 0.2 25), oklch(65% 0.2 25));
127
+ --color-info: light-dark(oklch(55% 0.15 230), oklch(65% 0.15 230));
128
+
129
+ /* Legacy aliases for backward compatibility */
130
+ --color-dark: var(--color-text);
131
+ --color-light: var(--color-surface);
132
+ --color-mid: var(--color-text-muted);
133
+
134
+ /* ===== BORDERS ===== */
135
+ --border-thin: 1px;
136
+ --border-thick: 3px;
137
+
138
+ /* ===== FONTS ===== */
139
+ --font-plain: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
140
+ --font-mono: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, monospace;
141
+
142
+ /* ===== LINE HEIGHT ===== */
143
+ --line-height: 1.5;
144
+ --line-height-tight: 1.2;
145
+ --line-height-loose: 1.8;
146
+
147
+ /* ===== TIMING ===== */
148
+ --duration-instant: 100ms;
149
+ --duration-fast: 200ms;
150
+ --duration-normal: 300ms;
151
+ --duration-slow: 500ms;
152
+
153
+ /* ===== EASING ===== */
154
+ --ease-out: cubic-bezier(0, 0, 0.2, 1);
155
+ --ease-in: cubic-bezier(0.4, 0, 1, 1);
156
+ --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
157
+ --ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
158
+
159
+ /* Legacy transition shortcuts */
160
+ --transition-fast: var(--duration-fast) var(--ease-out);
161
+ --transition-medium: var(--duration-normal) var(--ease-out);
162
+ --transition-slow: var(--duration-slow) var(--ease-out);
163
+ }
164
+
165
+
166
+ /* === global/reset.css === */
167
+ /**
168
+ * Elvish - Modern Reset & Axioms
169
+ *
170
+ * A synthesis of Andy Bell's "More Modern CSS Reset" and Elvish's original reset.
171
+ * Optimized for Chrome/Edge with graceful degradation for Safari/Firefox.
172
+ *
173
+ * Sources:
174
+ * - [AB] Andy Bell's "A (More) Modern CSS Reset"
175
+ * - [EL] Elvish original reset
176
+ * - [NEW] New additions for modern browsers
177
+ *
178
+ * Debug Mode:
179
+ * Add .mellon or .debug to <body> or any element to reveal layout structure.
180
+ * "Mellon" is Sindarin for "friend" - the password to enter Moria.
181
+ */
182
+
183
+ /* ==========================================================================
184
+ BOX SIZING [AB + EL]
185
+ Both resets agree. Universal consensus.
186
+ ========================================================================== */
187
+
188
+ *,
189
+ *::before,
190
+ *::after {
191
+ box-sizing: border-box;
192
+ }
193
+
194
+ /* ==========================================================================
195
+ BORDER & BACKGROUND DEFAULTS [EL + NEW]
196
+
197
+ Layout-focused philosophy: visual styling is opt-in, not opt-out.
198
+ - Borders: style is solid (for easy `border-width: 1px`), but width is 0
199
+ - Backgrounds: transparent by default
200
+ - This means elements are "invisible" until explicitly styled
201
+ ========================================================================== */
202
+
203
+ *,
204
+ *::before,
205
+ *::after {
206
+ /* Border: ready to use, but invisible by default */
207
+ border-style: solid;
208
+ border-width: 0;
209
+
210
+ /* Background: transparent until explicitly set */
211
+ background-color: transparent;
212
+ }
213
+
214
+ /* Root elements get the actual background */
215
+ html {
216
+ background-color: var(--color-surface);
217
+ }
218
+
219
+ /* ==========================================================================
220
+ MARGIN RESET [AB - expanded]
221
+
222
+ DISCARDED from Elvish: `* { margin: 0 }` is too aggressive.
223
+ It breaks nested lists, fieldset legends, and third-party components.
224
+
225
+ Andy Bell's surgical approach is safer - only reset elements that commonly
226
+ have problematic default margins. Expanded to include pre, h5, h6.
227
+ ========================================================================== */
228
+
229
+ body,
230
+ h1, h2, h3, h4, h5, h6,
231
+ p,
232
+ figure,
233
+ blockquote,
234
+ dl, dd,
235
+ pre,
236
+ fieldset {
237
+ margin: 0;
238
+ }
239
+
240
+ /* ==========================================================================
241
+ MEASURE AXIOM [EL]
242
+
243
+ Not in Andy Bell's reset - this is Elvish's core philosophy.
244
+
245
+ No line of text should exceed a comfortable reading length.
246
+ Exception-based: everything gets the constraint, then we remove it
247
+ from elements that need to contain multiple adjacent boxes.
248
+ ========================================================================== */
249
+
250
+ * {
251
+ max-inline-size: var(--measure);
252
+ }
253
+
254
+ html,
255
+ body,
256
+ div,
257
+ section,
258
+ article,
259
+ aside,
260
+ header,
261
+ footer,
262
+ nav,
263
+ main,
264
+ form,
265
+ fieldset,
266
+ figure,
267
+ figcaption,
268
+ picture,
269
+ video,
270
+ canvas,
271
+ iframe,
272
+ details,
273
+ summary,
274
+ dialog,
275
+ menu,
276
+ hgroup,
277
+ address,
278
+ /* Custom elements - Elvish layout primitives (Sindarin names) */
279
+ i-hath, /* Stack */
280
+ i-bau, /* Box */
281
+ i-enedh, /* Center (horizontal) */
282
+ i-tiniath, /* Cluster */
283
+ i-glan-veleg, /* Sidebar */
284
+ i-gwistindor, /* Switcher */
285
+ i-esgal, /* Cover (vertical center) */
286
+ i-vircantie, /* Grid */
287
+ i-gant-thala, /* Frame (aspect) */
288
+ i-glan-tholl, /* Reel (side-scrolling) */
289
+ i-fano, /* Imposter (overlay) */
290
+ i-thann, /* Icon */
291
+ i-adleithian, /* Container */
292
+ i-him, /* Sticky */
293
+ i-miriant, /* Grid-placed */
294
+ i-gonath { /* Masonry */
295
+ max-inline-size: none;
296
+ }
297
+
298
+ /* ==========================================================================
299
+ ROOT DEFAULTS [AB + EL + NEW]
300
+ ========================================================================== */
301
+
302
+ html {
303
+ /* [EL] Font settings via design tokens */
304
+ font-family: var(--font-plain);
305
+ font-size: 100%; /* Respects user preferences */
306
+ line-height: var(--line-height);
307
+
308
+ /* [EL] Colors */
309
+ color: var(--color-text);
310
+ /* background-color set above in BORDER & BACKGROUND DEFAULTS */
311
+
312
+ /* [NEW] Text rendering - prioritize legibility */
313
+ text-rendering: optimizeLegibility;
314
+ -webkit-font-smoothing: antialiased;
315
+ -moz-osx-font-smoothing: grayscale;
316
+
317
+ /* [NEW] Prevent text size inflation on mobile */
318
+ -webkit-text-size-adjust: 100%;
319
+ text-size-adjust: 100%;
320
+ }
321
+
322
+ /* [AB] Smooth scrolling ONLY when using keyboard navigation */
323
+ @media (prefers-reduced-motion: no-preference) {
324
+ html:focus-within {
325
+ scroll-behavior: smooth;
326
+ }
327
+ }
328
+
329
+ /* ==========================================================================
330
+ BODY DEFAULTS [EL + NEW]
331
+ ========================================================================== */
332
+
333
+ body {
334
+ /* [EL] Full viewport height - dvh accounts for mobile browser chrome */
335
+ min-block-size: 100vh;
336
+ min-block-size: 100dvh;
337
+
338
+ /* [NEW] Prevent horizontal overflow from animations, etc. */
339
+ overflow-x: clip;
340
+
341
+ /* Body gets the surface background */
342
+ background-color: var(--color-surface);
343
+ }
344
+
345
+ /* ==========================================================================
346
+ TYPOGRAPHY [EL + NEW]
347
+ ========================================================================== */
348
+
349
+ h1, h2, h3, h4, h5, h6 {
350
+ /* [EL] Tighter line height for headings */
351
+ line-height: var(--line-height-tight);
352
+
353
+ /* [NEW] Balanced wrapping for headings (Chrome 114+, Safari 17.5+) */
354
+ text-wrap: balance;
355
+ }
356
+
357
+ /* [EL] Modular scale sizing */
358
+ h1 { font-size: var(--s4); }
359
+ h2 { font-size: var(--s3); }
360
+ h3 { font-size: var(--s2); }
361
+ h4 { font-size: var(--s1); }
362
+ h5 { font-size: var(--s0); }
363
+ h6 { font-size: var(--s-1); }
364
+
365
+ /* [NEW] Pretty wrapping for prose (Chrome 117+) */
366
+ p {
367
+ text-wrap: pretty;
368
+ }
369
+
370
+ /* ==========================================================================
371
+ LINKS [AB + EL]
372
+ ========================================================================== */
373
+
374
+ a {
375
+ /* [EL] Inherit color - style via classes or context */
376
+ color: currentColor;
377
+
378
+ /* [AB] Better underline rendering - skips descenders (g, y, p) */
379
+ text-decoration-skip-ink: auto;
380
+
381
+ /* [NEW] Modern underline offset for readability */
382
+ text-underline-offset: 0.2em;
383
+ }
384
+
385
+ /* [EL] Remove underline on hover */
386
+ a:hover {
387
+ text-decoration: none;
388
+ }
389
+
390
+ /* [NEW] Focus styles for keyboard navigation */
391
+ a:focus-visible {
392
+ outline: 2px solid var(--color-accent, currentColor);
393
+ outline-offset: 2px;
394
+ }
395
+
396
+ /* ==========================================================================
397
+ MEDIA [EL - expanded from AB]
398
+ ========================================================================== */
399
+
400
+ img,
401
+ picture,
402
+ video,
403
+ canvas,
404
+ svg {
405
+ display: block;
406
+ max-inline-size: 100%;
407
+ }
408
+
409
+ img,
410
+ picture,
411
+ video {
412
+ /* [EL] Maintain aspect ratio */
413
+ block-size: auto;
414
+ }
415
+
416
+ /* [NEW] Style for alt text when image fails to load */
417
+ img {
418
+ font-style: italic;
419
+ vertical-align: middle;
420
+ }
421
+
422
+ /* ==========================================================================
423
+ FORM ELEMENTS [AB + EL]
424
+ ========================================================================== */
425
+
426
+ input,
427
+ button,
428
+ textarea,
429
+ select {
430
+ /* [AB + EL] Inherit font from parent */
431
+ font: inherit;
432
+ /* [EL] Inherit color too */
433
+ color: inherit;
434
+ }
435
+
436
+ /* [EL] Indicate buttons are clickable */
437
+ button,
438
+ [role="button"] {
439
+ cursor: pointer;
440
+ }
441
+
442
+ /* [NEW] Textarea should not resize horizontally */
443
+ textarea {
444
+ resize: vertical;
445
+ }
446
+
447
+ /* ==========================================================================
448
+ LISTS [EL - modified]
449
+ ========================================================================== */
450
+
451
+ ul,
452
+ ol {
453
+ list-style: none;
454
+ padding: 0;
455
+ margin: 0;
456
+ }
457
+
458
+ /* [NEW] Restore list styles when explicitly marked for semantics */
459
+ ul[role="list"],
460
+ ol[role="list"],
461
+ ul.list,
462
+ ol.list {
463
+ list-style: revert;
464
+ padding-inline-start: 1em;
465
+ }
466
+
467
+ /* ==========================================================================
468
+ TABLES [EL]
469
+ ========================================================================== */
470
+
471
+ table {
472
+ border-collapse: collapse;
473
+ border-spacing: 0;
474
+ }
475
+
476
+ /* [NEW] Inherit text alignment */
477
+ th {
478
+ text-align: inherit;
479
+ font-weight: inherit;
480
+ }
481
+
482
+ /* ==========================================================================
483
+ CODE [EL]
484
+ ========================================================================== */
485
+
486
+ pre,
487
+ code,
488
+ kbd,
489
+ samp {
490
+ font-family: var(--font-mono);
491
+ }
492
+
493
+ pre {
494
+ overflow-x: auto;
495
+ white-space: pre;
496
+ }
497
+
498
+ /* ==========================================================================
499
+ MISC ELEMENTS [EL + NEW]
500
+ ========================================================================== */
501
+
502
+ /* [EL] Horizontal rule */
503
+ hr {
504
+ border-width: var(--border-thin, 1px) 0 0 0;
505
+ color: inherit;
506
+ block-size: 0;
507
+ }
508
+
509
+ /* [NEW] Abbreviations with title should indicate expandability */
510
+ abbr[title] {
511
+ text-decoration: underline dotted;
512
+ cursor: help;
513
+ }
514
+
515
+ /* [NEW] Summary cursor */
516
+ summary {
517
+ cursor: pointer;
518
+ }
519
+
520
+ /* ==========================================================================
521
+ SELECTION [NEW]
522
+ ========================================================================== */
523
+
524
+ ::selection {
525
+ background-color: var(--color-accent, highlight);
526
+ color: var(--color-accent-text, highlighttext);
527
+ }
528
+
529
+ /* ==========================================================================
530
+ FOCUS STATES [NEW]
531
+ ========================================================================== */
532
+
533
+ /* Remove default focus outline */
534
+ :focus {
535
+ outline: none;
536
+ }
537
+
538
+ /* Visible focus for keyboard navigation only */
539
+ :focus-visible {
540
+ outline: 2px solid var(--color-accent, currentColor);
541
+ outline-offset: 2px;
542
+ }
543
+
544
+ /* ==========================================================================
545
+ REDUCED MOTION [AB - expanded]
546
+ ========================================================================== */
547
+
548
+ @media (prefers-reduced-motion: reduce) {
549
+ *,
550
+ *::before,
551
+ *::after {
552
+ /* [AB] Kill all animations */
553
+ animation-duration: 0.01ms !important;
554
+ animation-iteration-count: 1 !important;
555
+
556
+ /* [AB] Kill all transitions */
557
+ transition-duration: 0.01ms !important;
558
+
559
+ /* [AB] Kill smooth scrolling */
560
+ scroll-behavior: auto !important;
561
+ }
562
+
563
+ /* [NEW] Also target View Transitions */
564
+ ::view-transition-group(*),
565
+ ::view-transition-old(*),
566
+ ::view-transition-new(*) {
567
+ animation: none !important;
568
+ }
569
+ }
570
+
571
+ /* ==========================================================================
572
+ DEBUG MODE - "MELLON" [NEW]
573
+
574
+ Add .mellon or .debug to <body> or any element to reveal layout structure.
575
+ "Mellon" is Sindarin for "friend" - the password to enter Moria.
576
+
577
+ Features:
578
+ - Dashed outlines show element boundaries (uses outline, not border)
579
+ - Each nesting level gets a different hue (rainbow effect)
580
+ - Optional .tint modifier adds subtle background colors
581
+ - Zero specificity via :where() - easily overridden by real styles
582
+
583
+ Usage:
584
+ <body class="mellon"> - Debug entire page
585
+ <body class="mellon tint"> - Debug with background tints
586
+ <div class="debug"> - Debug specific section
587
+ ========================================================================== */
588
+
589
+ /* Debug color tokens */
590
+ :root {
591
+ --debug-stroke: 1px;
592
+ --debug-style: dashed;
593
+ --debug-saturation: 0.25;
594
+ --debug-lightness: 55%;
595
+ }
596
+
597
+ /* Calculate hue based on depth (golden angle ≈ 137.5° for optimal distribution) */
598
+ :where(.mellon, .debug),
599
+ :where(.mellon, .debug) :where(*) {
600
+ --debug-hue: 0;
601
+ }
602
+
603
+ :where(.mellon, .debug) > :where(*) { --debug-hue: 137; }
604
+ :where(.mellon, .debug) > :where(*) > :where(*) { --debug-hue: 275; }
605
+ :where(.mellon, .debug) > :where(*) > :where(*) > :where(*) { --debug-hue: 52; }
606
+ :where(.mellon, .debug) > :where(*) > :where(*) > :where(*) > :where(*) { --debug-hue: 190; }
607
+ :where(.mellon, .debug) > :where(*) > :where(*) > :where(*) > :where(*) > :where(*) { --debug-hue: 327; }
608
+ :where(.mellon, .debug) > :where(*) > :where(*) > :where(*) > :where(*) > :where(*) > :where(*) { --debug-hue: 105; }
609
+
610
+ /* Show outlines in debug mode */
611
+ :where(.mellon, .debug),
612
+ :where(.mellon, .debug) :where(*) {
613
+ outline: var(--debug-stroke) var(--debug-style)
614
+ oklch(var(--debug-lightness) var(--debug-saturation) var(--debug-hue, 0)) !important;
615
+ outline-offset: -1px;
616
+ }
617
+
618
+ /* Highlight Elvish primitives more prominently */
619
+ :where(.mellon, .debug) :where(
620
+ i-hath, i-bau, i-enedh, i-tiniath, i-glan-veleg, i-gwistindor,
621
+ i-esgal, i-vircantie, i-gant-thala, i-glan-tholl, i-fano, i-thann,
622
+ i-adleithian, i-him, i-miriant, i-gonath
623
+ ) {
624
+ --debug-stroke: 2px;
625
+ --debug-style: solid;
626
+ --debug-saturation: 0.4;
627
+ }
628
+
629
+ /* Optional: Background tint mode */
630
+ :where(.mellon.tint, .debug.tint),
631
+ :where(.mellon.tint, .debug.tint) :where(*) {
632
+ background-color: oklch(
633
+ var(--debug-lightness)
634
+ calc(var(--debug-saturation) * 0.4)
635
+ var(--debug-hue, 0)
636
+ / 0.1
637
+ ) !important;
638
+ }
639
+
640
+ /* ==========================================================================
641
+ UTILITIES [EL + NEW]
642
+ ========================================================================== */
643
+
644
+ /* [EL] Hidden but accessible to screen readers */
645
+ .visually-hidden,
646
+ .sr-only {
647
+ position: absolute;
648
+ inline-size: 1px;
649
+ block-size: 1px;
650
+ padding: 0;
651
+ margin: -1px;
652
+ overflow: hidden;
653
+ clip: rect(0, 0, 0, 0);
654
+ clip-path: inset(50%);
655
+ white-space: nowrap;
656
+ border: 0;
657
+ }
658
+
659
+ /* [NEW] Skip link pattern for accessibility */
660
+ .skip-link {
661
+ position: absolute;
662
+ inset-inline-start: 0;
663
+ inset-block-start: 0;
664
+ padding: var(--s-1, 0.5rem) var(--s0, 1rem);
665
+ background: var(--color-accent, highlight);
666
+ color: var(--color-accent-text, highlighttext);
667
+ transform: translateY(-100%);
668
+ z-index: 9999;
669
+ }
670
+
671
+ .skip-link:focus {
672
+ transform: translateY(0);
673
+ }
674
+
675
+
676
+ /* === global/utilities.css === */
677
+ /**
678
+ * Elvish - Utilities
679
+ *
680
+ * Final adjustments. Use sparingly.
681
+ * Naming convention: property\:value
682
+ *
683
+ * !important is intentional - utilities are for overrides.
684
+ */
685
+
686
+ /* ===== TEXT ALIGNMENT ===== */
687
+ .text-align\:start { text-align: start !important; }
688
+ .text-align\:center { text-align: center !important; }
689
+ .text-align\:end { text-align: end !important; }
690
+
691
+ /* ===== FONT SIZES ===== */
692
+ .font-size\:s-2 { font-size: var(--s-2) !important; }
693
+ .font-size\:s-1 { font-size: var(--s-1) !important; }
694
+ .font-size\:s0 { font-size: var(--s0) !important; }
695
+ .font-size\:s1 { font-size: var(--s1) !important; }
696
+ .font-size\:s2 { font-size: var(--s2) !important; }
697
+ .font-size\:s3 { font-size: var(--s3) !important; }
698
+ .font-size\:s4 { font-size: var(--s4) !important; }
699
+
700
+ /* ===== FONT WEIGHT ===== */
701
+ .font-weight\:normal { font-weight: normal !important; }
702
+ .font-weight\:bold { font-weight: bold !important; }
703
+
704
+ /* ===== COLORS ===== */
705
+ .color\:dark { color: var(--color-dark) !important; }
706
+ .color\:light { color: var(--color-light) !important; }
707
+ .color\:accent { color: var(--color-accent) !important; }
708
+
709
+ .bg\:dark { background-color: var(--color-dark) !important; }
710
+ .bg\:light { background-color: var(--color-light) !important; }
711
+ .bg\:accent { background-color: var(--color-accent) !important; }
712
+
713
+ /* ===== MEASURE ===== */
714
+ .max-inline-size\:measure { max-inline-size: var(--measure) !important; }
715
+ .max-inline-size\:measure\/2 { max-inline-size: calc(var(--measure) / 2) !important; }
716
+ .max-inline-size\:measure\/3 { max-inline-size: calc(var(--measure) / 3) !important; }
717
+ .max-inline-size\:none { max-inline-size: none !important; }
718
+
719
+ /* ===== WIDTH ===== */
720
+ .inline-size\:100 { inline-size: 100% !important; }
721
+ .inline-size\:auto { inline-size: auto !important; }
722
+
723
+ /* ===== HEIGHT ===== */
724
+ .block-size\:100 { block-size: 100% !important; }
725
+ .block-size\:100vh { block-size: 100vh !important; }
726
+ .block-size\:auto { block-size: auto !important; }
727
+
728
+ /* ===== MARGIN ===== */
729
+ .margin\:0 { margin: 0 !important; }
730
+ .margin-block\:0 { margin-block: 0 !important; }
731
+ .margin-inline\:0 { margin-inline: 0 !important; }
732
+ .margin-inline\:auto { margin-inline: auto !important; }
733
+
734
+ /* ===== PADDING ===== */
735
+ .padding\:s-1 { padding: var(--s-1) !important; }
736
+ .padding\:s0 { padding: var(--s0) !important; }
737
+ .padding\:s1 { padding: var(--s1) !important; }
738
+ .padding\:s2 { padding: var(--s2) !important; }
739
+
740
+ /* ===== GAP ===== */
741
+ .gap\:s-1 { gap: var(--s-1) !important; }
742
+ .gap\:s0 { gap: var(--s0) !important; }
743
+ .gap\:s1 { gap: var(--s1) !important; }
744
+ .gap\:s2 { gap: var(--s2) !important; }
745
+
746
+ /* ===== FLEX ===== */
747
+ .flex-grow\:1 { flex-grow: 1 !important; }
748
+ .flex-shrink\:0 { flex-shrink: 0 !important; }
749
+
750
+ /* ===== OVERFLOW ===== */
751
+ .overflow\:hidden { overflow: hidden !important; }
752
+ .overflow\:auto { overflow: auto !important; }
753
+
754
+ /* ===== POSITION ===== */
755
+ .position\:relative { position: relative !important; }
756
+ .position\:absolute { position: absolute !important; }
757
+ .position\:fixed { position: fixed !important; }
758
+ .position\:sticky { position: sticky !important; }
759
+
760
+ /* ===== DISPLAY ===== */
761
+ .display\:none { display: none !important; }
762
+ .display\:block { display: block !important; }
763
+ .display\:inline { display: inline !important; }
764
+ .display\:inline-block { display: inline-block !important; }
765
+ .display\:flex { display: flex !important; }
766
+ .display\:grid { display: grid !important; }
767
+
768
+ /* ===== BORDERS ===== */
769
+ .border\:thin { border-width: var(--border-thin) !important; }
770
+ .border\:thick { border-width: var(--border-thick) !important; }
771
+ .border-radius\:s-2 { border-radius: var(--s-2) !important; }
772
+ .border-radius\:s-1 { border-radius: var(--s-1) !important; }
773
+ .border-radius\:s0 { border-radius: var(--s0) !important; }
774
+
775
+ /* ===== SHADOWS ===== */
776
+ .shadow\:small {
777
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) !important;
778
+ }
779
+ .shadow\:medium {
780
+ box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.12) !important;
781
+ }
782
+ .shadow\:large {
783
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15), 0 3px 6px rgba(0, 0, 0, 0.10) !important;
784
+ }
785
+
786
+ /* ===== Z-INDEX ===== */
787
+ /* Use sparingly! Prefer source order. */
788
+ .z\:0 { z-index: 0 !important; }
789
+ .z\:1 { z-index: 1 !important; }
790
+ .z\:10 { z-index: 10 !important; }
791
+ .z\:100 { z-index: 100 !important; }
792
+
793
+ /* ===== LISTS ===== */
794
+ .list-style\:disc { list-style: disc !important; padding-inline-start: 1em !important; }
795
+ .list-style\:decimal { list-style: decimal !important; padding-inline-start: 1em !important; }
796
+
797
+ /* ===== CURSOR ===== */
798
+ .cursor\:pointer { cursor: pointer !important; }
799
+ .cursor\:not-allowed { cursor: not-allowed !important; }
800
+
801
+ /* ===== USER SELECT ===== */
802
+ .user-select\:none { user-select: none !important; }
803
+ .user-select\:all { user-select: all !important; }
804
+
805
+ /* ===== RATIO MODES ===== */
806
+ /*
807
+ * Switch the modular scale ratio for a container and all descendants.
808
+ * Golden (φ): Dramatic, high-impact - ideal for marketing, CTAs
809
+ * Silver (√2): Subtle, refined - ideal for documentation, dense content
810
+ * Fifth (1.5): Balanced middle-ground
811
+ *
812
+ * Also adjusts --measure (line length) to complement each ratio:
813
+ * Golden: 60ch (tighter), Fifth: 70ch (balanced), Silver: 80ch (looser)
814
+ */
815
+ .ratio\:golden { --ratio: var(--ratio-golden) !important; --measure: 60ch !important; }
816
+ .ratio\:silver { --ratio: var(--ratio-silver) !important; --measure: 80ch !important; }
817
+ .ratio\:fifth { --ratio: var(--ratio-fifth) !important; --measure: 70ch !important; }
818
+
819
+ /* Aliases for convenience */
820
+ .ratio\:dramatic { --ratio: var(--ratio-golden) !important; --measure: 60ch !important; }
821
+ .ratio\:subtle { --ratio: var(--ratio-silver) !important; --measure: 80ch !important; }
822
+ .ratio\:balanced { --ratio: var(--ratio-fifth) !important; --measure: 70ch !important; }
823
+
824
+ /* Data attribute alternative (for JS toggling) */
825
+ [data-ratio="golden"] { --ratio: var(--ratio-golden); --measure: 60ch; }
826
+ [data-ratio="silver"] { --ratio: var(--ratio-silver); --measure: 80ch; }
827
+ [data-ratio="fifth"] { --ratio: var(--ratio-fifth); --measure: 70ch; }
828
+
829
+
830
+ /* === global/modern.css === */
831
+ /**
832
+ * Elvish Modern - CSS Functions & Advanced Features
833
+ *
834
+ * Features requiring Chrome 133+ / Edge 133+ (progressive enhancement)
835
+ * Firefox and Safari fallbacks provided where possible.
836
+ *
837
+ * BROWSER SUPPORT (as of Jan 2026):
838
+ * - if() : Chrome 137+, Edge 137+
839
+ * - @function : Chrome 139+, Edge 139+
840
+ * - sibling-index/count : Chrome 138+, Edge 138+
841
+ * - typed attr() : Chrome 133+, Edge 133+
842
+ * - light-dark() : All modern browsers ✓
843
+ * - Relative colors : All modern browsers ✓
844
+ */
845
+
846
+ /* ============================================================================
847
+ * CUSTOM FUNCTIONS (@function)
848
+ * ============================================================================
849
+ * Reusable logic defined once, used everywhere.
850
+ * Chrome 139+ / Edge 139+
851
+ */
852
+
853
+ /* Negate a value */
854
+ @function --negate(--value) {
855
+ result: calc(-1 * var(--value));
856
+ }
857
+
858
+ /* Linear interpolation between two values */
859
+ @function --lerp(--a, --b, --t) {
860
+ result: calc(var(--a) + (var(--b) - var(--a)) * var(--t));
861
+ }
862
+
863
+ /* Fluid value between min and max based on viewport */
864
+ @function --fluid(--min, --max, --min-vw: 20rem, --max-vw: 75rem) {
865
+ result: clamp(
866
+ var(--min),
867
+ calc(var(--min) + (var(--max) - var(--min)) * ((100vw - var(--min-vw)) / (var(--max-vw) - var(--min-vw)))),
868
+ var(--max)
869
+ );
870
+ }
871
+
872
+ /* Add transparency to a color */
873
+ @function --alpha(--color, --opacity) {
874
+ result: oklch(from var(--color) l c h / var(--opacity));
875
+ }
876
+
877
+ /* Lighten a color */
878
+ @function --lighten(--color, --amount: 0.1) {
879
+ result: oklch(from var(--color) calc(l + var(--amount)) c h);
880
+ }
881
+
882
+ /* Darken a color */
883
+ @function --darken(--color, --amount: 0.1) {
884
+ result: oklch(from var(--color) calc(l - var(--amount)) c h);
885
+ }
886
+
887
+ /* Spacing multiplier */
888
+ @function --space(--multiplier: 1) {
889
+ result: calc(var(--s0) * var(--multiplier));
890
+ }
891
+
892
+ /* Conditional border-radius (0 when near edge, normal otherwise) */
893
+ @function --safe-radius(--radius, --threshold: 8px) {
894
+ /* Use container query units if available */
895
+ result: clamp(0px, calc((100cqi - var(--threshold)) * 999), var(--radius));
896
+ }
897
+
898
+
899
+ /* ============================================================================
900
+ * STAGGER ANIMATIONS (sibling-index)
901
+ * ============================================================================
902
+ * Automatic stagger delays without JavaScript!
903
+ * Chrome 138+ / Edge 138+
904
+ */
905
+
906
+ /* Stagger children entrance animations */
907
+ .stagger-enter > * {
908
+ animation: elvish-fade-in-up var(--duration-normal, 300ms) var(--ease-out, ease-out) both;
909
+ animation-delay: calc((sibling-index() - 1) * var(--stagger-delay, 50ms));
910
+ }
911
+
912
+ /* Stagger with @starting-style for appear animations */
913
+ .stagger-appear > * {
914
+ opacity: 1;
915
+ transform: translateY(0);
916
+ transition:
917
+ opacity var(--duration-normal, 300ms) var(--ease-out, ease-out),
918
+ transform var(--duration-normal, 300ms) var(--ease-out, ease-out);
919
+ transition-delay: calc((sibling-index() - 1) * var(--stagger-delay, 50ms));
920
+
921
+ @starting-style {
922
+ opacity: 0;
923
+ transform: translateY(20px);
924
+ }
925
+ }
926
+
927
+ /* Cascade opacity based on position */
928
+ .fade-cascade > * {
929
+ opacity: calc(1 - (sibling-index() - 1) * 0.08);
930
+ }
931
+
932
+ /* Rainbow children using sibling-index for hue */
933
+ .rainbow > * {
934
+ --hue: calc(sibling-index() * (360 / sibling-count()));
935
+ background: oklch(85% 0.2 var(--hue));
936
+ }
937
+
938
+ /* Auto-numbered list items */
939
+ .auto-number > *::before {
940
+ content: counter(sibling-index) ". ";
941
+ counter-increment: none; /* We don't need CSS counters anymore! */
942
+ }
943
+
944
+
945
+ /* ============================================================================
946
+ * CONDITIONAL STYLES (if() function)
947
+ * ============================================================================
948
+ * Inline conditionals in CSS values.
949
+ * Chrome 137+ / Edge 137+
950
+ */
951
+
952
+ /* Theme-aware component using if() */
953
+ .theme-aware {
954
+ --is-dark: false;
955
+
956
+ background: if(
957
+ style(--is-dark: true): var(--color-surface-dark, #1a1a2e);
958
+ else: var(--color-surface-light, #ffffff)
959
+ );
960
+
961
+ color: if(
962
+ style(--is-dark: true): var(--color-text-dark, #f0f0f5);
963
+ else: var(--color-text-light, #1a1a2e)
964
+ );
965
+ }
966
+
967
+ /* Responsive without media queries using if() */
968
+ .responsive-grid {
969
+ display: grid;
970
+ grid-template-columns: if(
971
+ media(width >= 900px): repeat(4, 1fr);
972
+ media(width >= 600px): repeat(3, 1fr);
973
+ media(width >= 400px): repeat(2, 1fr);
974
+ else: 1fr
975
+ );
976
+ }
977
+
978
+ /* Compact variant using style query in if() */
979
+ .adaptive-card {
980
+ --variant: default;
981
+
982
+ padding: if(
983
+ style(--variant: compact): var(--s-1);
984
+ style(--variant: spacious): var(--s2);
985
+ else: var(--s1)
986
+ );
987
+
988
+ font-size: if(
989
+ style(--variant: compact): var(--s-1);
990
+ else: var(--s0)
991
+ );
992
+ }
993
+
994
+
995
+ /* ============================================================================
996
+ * TYPED ATTR() - HTML to CSS Bridge
997
+ * ============================================================================
998
+ * Use HTML attributes as typed CSS values.
999
+ * Chrome 133+ / Edge 133+
1000
+ */
1001
+
1002
+ /* Grid columns from data attribute */
1003
+ [data-columns] {
1004
+ --columns: attr(data-columns type(<number>), 1);
1005
+ display: grid;
1006
+ grid-template-columns: repeat(var(--columns), 1fr);
1007
+ }
1008
+
1009
+ /* Color from data attribute */
1010
+ [data-color] {
1011
+ color: attr(data-color type(<color>), currentColor);
1012
+ }
1013
+
1014
+ /* Background color from data attribute */
1015
+ [data-bg] {
1016
+ background-color: attr(data-bg type(<color>), transparent);
1017
+ }
1018
+
1019
+ /* Spacing from data attribute */
1020
+ [data-space] {
1021
+ --space-value: attr(data-space type(<length>), var(--s1));
1022
+ gap: var(--space-value);
1023
+ padding: var(--space-value);
1024
+ }
1025
+
1026
+ /* Progress bar using attr() */
1027
+ [data-progress] {
1028
+ --progress: attr(data-progress type(<percentage>), 0%);
1029
+ background: linear-gradient(
1030
+ to right,
1031
+ var(--color-accent) var(--progress),
1032
+ var(--color-surface-sunken) var(--progress)
1033
+ );
1034
+ }
1035
+
1036
+ /* Aspect ratio from attribute */
1037
+ [data-ratio] {
1038
+ aspect-ratio: attr(data-ratio type(<ratio>), 1);
1039
+ }
1040
+
1041
+ /* Dynamic view-transition-name from id */
1042
+ [data-transition] {
1043
+ view-transition-name: attr(data-transition type(<custom-ident>), none);
1044
+ }
1045
+
1046
+
1047
+ /* ============================================================================
1048
+ * KEYFRAMES
1049
+ * ============================================================================
1050
+ */
1051
+
1052
+ @keyframes elvish-fade-in {
1053
+ from { opacity: 0; }
1054
+ }
1055
+
1056
+ @keyframes elvish-fade-in-up {
1057
+ from {
1058
+ opacity: 0;
1059
+ transform: translateY(var(--distance, 20px));
1060
+ }
1061
+ }
1062
+
1063
+ @keyframes elvish-fade-in-down {
1064
+ from {
1065
+ opacity: 0;
1066
+ transform: translateY(calc(var(--distance, 20px) * -1));
1067
+ }
1068
+ }
1069
+
1070
+ @keyframes elvish-scale-in {
1071
+ from {
1072
+ opacity: 0;
1073
+ transform: scale(0.95);
1074
+ }
1075
+ }
1076
+
1077
+ @keyframes elvish-slide-in-right {
1078
+ from {
1079
+ transform: translateX(100%);
1080
+ }
1081
+ }
1082
+
1083
+ @keyframes elvish-slide-in-left {
1084
+ from {
1085
+ transform: translateX(-100%);
1086
+ }
1087
+ }
1088
+
1089
+
1090
+ /* ============================================================================
1091
+ * FEATURE DETECTION
1092
+ * ============================================================================
1093
+ * Fallbacks for browsers without support.
1094
+ */
1095
+
1096
+ /* Fallback for sibling-index() - requires JS to set --i custom property */
1097
+ @supports not (animation-delay: calc(sibling-index() * 1ms)) {
1098
+ .stagger-enter > * {
1099
+ /* Fallback: use nth-child or JS-set custom property */
1100
+ animation-delay: calc(var(--i, 0) * var(--stagger-delay, 50ms));
1101
+ }
1102
+
1103
+ .stagger-enter > :nth-child(1) { --i: 0; }
1104
+ .stagger-enter > :nth-child(2) { --i: 1; }
1105
+ .stagger-enter > :nth-child(3) { --i: 2; }
1106
+ .stagger-enter > :nth-child(4) { --i: 3; }
1107
+ .stagger-enter > :nth-child(5) { --i: 4; }
1108
+ .stagger-enter > :nth-child(6) { --i: 5; }
1109
+ .stagger-enter > :nth-child(7) { --i: 6; }
1110
+ .stagger-enter > :nth-child(8) { --i: 7; }
1111
+ .stagger-enter > :nth-child(9) { --i: 8; }
1112
+ .stagger-enter > :nth-child(10) { --i: 9; }
1113
+ }
1114
+
1115
+ /* Fallback for if() - use custom properties toggled by classes */
1116
+ @supports not (color: if(style(--x: y): red; else: blue)) {
1117
+ .theme-aware {
1118
+ background: var(--color-surface-light, #ffffff);
1119
+ color: var(--color-text-light, #1a1a2e);
1120
+ }
1121
+
1122
+ .theme-aware.is-dark {
1123
+ background: var(--color-surface-dark, #1a1a2e);
1124
+ color: var(--color-text-dark, #f0f0f5);
1125
+ }
1126
+ }
1127
+
1128
+ /* Fallback for typed attr() */
1129
+ @supports not (color: attr(data-x type(<color>), red)) {
1130
+ [data-columns="2"] { grid-template-columns: repeat(2, 1fr); }
1131
+ [data-columns="3"] { grid-template-columns: repeat(3, 1fr); }
1132
+ [data-columns="4"] { grid-template-columns: repeat(4, 1fr); }
1133
+ [data-columns="5"] { grid-template-columns: repeat(5, 1fr); }
1134
+ [data-columns="6"] { grid-template-columns: repeat(6, 1fr); }
1135
+ }
1136
+
1137
+
1138
+ /* === global/transitions.css === */
1139
+ /**
1140
+ * Elvish - View Transitions
1141
+ *
1142
+ * Smooth, buttery transitions between layout states using the View Transitions API.
1143
+ *
1144
+ * Browser Support (Jan 2026) - Baseline Newly Available since Oct 2025:
1145
+ * - Chrome/Edge: ✅ 111+
1146
+ * - Safari: ✅ 18+
1147
+ * - Firefox: ✅ 144+
1148
+ *
1149
+ * New features available:
1150
+ * - view-transition-name: match-element (auto-naming)
1151
+ * - view-transition-class (group styling)
1152
+ * - :active-view-transition pseudo-class
1153
+ *
1154
+ * Usage:
1155
+ * 1. Add view-transition-name to elements that should animate independently
1156
+ * 2. Use startTransition() helper to wrap DOM changes
1157
+ * 3. Customize timing with CSS custom properties
1158
+ */
1159
+
1160
+ /* ===== ENABLE VIEW TRANSITIONS ===== */
1161
+ @view-transition {
1162
+ navigation: auto; /* Enable for same-document navigations */
1163
+ }
1164
+
1165
+ /* ===== TRANSITION TIMING TOKENS ===== */
1166
+ :root {
1167
+ --transition-duration: 300ms;
1168
+ --transition-duration-fast: 150ms;
1169
+ --transition-duration-slow: 500ms;
1170
+
1171
+ /* Easing curves */
1172
+ --transition-ease: cubic-bezier(0.25, 0.1, 0.25, 1); /* Smooth default */
1173
+ --transition-ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* Quick start, gentle end */
1174
+ --transition-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* Symmetric */
1175
+ --transition-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* Slight overshoot */
1176
+ }
1177
+
1178
+ /* ===== DEFAULT TRANSITION BEHAVIOR ===== */
1179
+ /* Old state fades/scales out */
1180
+ ::view-transition-old(root) {
1181
+ animation: var(--transition-duration) var(--transition-ease) both fade-out;
1182
+ }
1183
+
1184
+ /* New state fades/scales in */
1185
+ ::view-transition-new(root) {
1186
+ animation: var(--transition-duration) var(--transition-ease) both fade-in;
1187
+ }
1188
+
1189
+ @keyframes fade-out {
1190
+ to { opacity: 0; }
1191
+ }
1192
+
1193
+ @keyframes fade-in {
1194
+ from { opacity: 0; }
1195
+ }
1196
+
1197
+ /* ===== NAMED TRANSITION GROUPS ===== */
1198
+
1199
+ /*
1200
+ * Assign view-transition-name to elements for independent animation.
1201
+ * Each named element animates separately from the root transition.
1202
+ */
1203
+
1204
+ /* Layout primitives can opt-in to independent transitions */
1205
+ [data-transition] {
1206
+ view-transition-name: var(--transition-name, auto);
1207
+ }
1208
+
1209
+ /* Auto-naming for list/grid items - each gets unique name automatically */
1210
+ [data-transition="auto"] > * {
1211
+ view-transition-name: match-element;
1212
+ }
1213
+
1214
+ /* Group styling with view-transition-class */
1215
+ .transition-card-group > * {
1216
+ view-transition-name: match-element;
1217
+ view-transition-class: card;
1218
+ }
1219
+
1220
+ /* Style all cards at once using the class */
1221
+ ::view-transition-group(.card) {
1222
+ animation-duration: var(--transition-duration);
1223
+ animation-timing-function: var(--transition-ease-spring);
1224
+ }
1225
+
1226
+ /* Utility classes for common transition names */
1227
+ .transition-hero { view-transition-name: hero; }
1228
+ .transition-header { view-transition-name: header; }
1229
+ .transition-sidebar { view-transition-name: sidebar; }
1230
+ .transition-main { view-transition-name: main; }
1231
+ .transition-card { view-transition-name: card; }
1232
+ .transition-modal { view-transition-name: modal; }
1233
+
1234
+ /* ===== TRANSITION ANIMATIONS ===== */
1235
+
1236
+ /* Morph (default for named elements) - smooth position/size interpolation */
1237
+ ::view-transition-old(*),
1238
+ ::view-transition-new(*) {
1239
+ animation-duration: var(--transition-duration);
1240
+ animation-timing-function: var(--transition-ease-out);
1241
+ }
1242
+
1243
+ /* Slide transitions */
1244
+ ::view-transition-old(slide-left) {
1245
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-out-left;
1246
+ }
1247
+ ::view-transition-new(slide-left) {
1248
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-in-left;
1249
+ }
1250
+
1251
+ ::view-transition-old(slide-right) {
1252
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-out-right;
1253
+ }
1254
+ ::view-transition-new(slide-right) {
1255
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-in-right;
1256
+ }
1257
+
1258
+ ::view-transition-old(slide-up) {
1259
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-out-up;
1260
+ }
1261
+ ::view-transition-new(slide-up) {
1262
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-in-up;
1263
+ }
1264
+
1265
+ ::view-transition-old(slide-down) {
1266
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-out-down;
1267
+ }
1268
+ ::view-transition-new(slide-down) {
1269
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-in-down;
1270
+ }
1271
+
1272
+ @keyframes slide-out-left {
1273
+ to { transform: translateX(-100%); opacity: 0; }
1274
+ }
1275
+ @keyframes slide-in-left {
1276
+ from { transform: translateX(100%); opacity: 0; }
1277
+ }
1278
+ @keyframes slide-out-right {
1279
+ to { transform: translateX(100%); opacity: 0; }
1280
+ }
1281
+ @keyframes slide-in-right {
1282
+ from { transform: translateX(-100%); opacity: 0; }
1283
+ }
1284
+ @keyframes slide-out-up {
1285
+ to { transform: translateY(-100%); opacity: 0; }
1286
+ }
1287
+ @keyframes slide-in-up {
1288
+ from { transform: translateY(100%); opacity: 0; }
1289
+ }
1290
+ @keyframes slide-out-down {
1291
+ to { transform: translateY(100%); opacity: 0; }
1292
+ }
1293
+ @keyframes slide-in-down {
1294
+ from { transform: translateY(-100%); opacity: 0; }
1295
+ }
1296
+
1297
+ /* Scale transitions */
1298
+ ::view-transition-old(scale) {
1299
+ animation: var(--transition-duration) var(--transition-ease-out) both scale-out;
1300
+ }
1301
+ ::view-transition-new(scale) {
1302
+ animation: var(--transition-duration) var(--transition-ease-spring) both scale-in;
1303
+ }
1304
+
1305
+ @keyframes scale-out {
1306
+ to { transform: scale(0.9); opacity: 0; }
1307
+ }
1308
+ @keyframes scale-in {
1309
+ from { transform: scale(1.1); opacity: 0; }
1310
+ }
1311
+
1312
+ /* Flip transitions */
1313
+ ::view-transition-old(flip) {
1314
+ animation: var(--transition-duration-slow) var(--transition-ease) both flip-out;
1315
+ }
1316
+ ::view-transition-new(flip) {
1317
+ animation: var(--transition-duration-slow) var(--transition-ease) both flip-in;
1318
+ }
1319
+
1320
+ @keyframes flip-out {
1321
+ to { transform: rotateY(90deg); opacity: 0; }
1322
+ }
1323
+ @keyframes flip-in {
1324
+ from { transform: rotateY(-90deg); opacity: 0; }
1325
+ }
1326
+
1327
+ /* ===== LAYOUT-SPECIFIC TRANSITIONS ===== */
1328
+
1329
+ /* Sidebar collapse/expand */
1330
+ .transition-glan-veleg { view-transition-name: glan-veleg; }
1331
+
1332
+ ::view-transition-old(glan-veleg),
1333
+ ::view-transition-new(glan-veleg) {
1334
+ animation-duration: var(--transition-duration);
1335
+ animation-timing-function: var(--transition-ease-out);
1336
+ /* Height and width interpolate automatically */
1337
+ }
1338
+
1339
+ /* Switcher horizontal ↔ vertical */
1340
+ .transition-gwistindor { view-transition-name: gwistindor; }
1341
+
1342
+ /* Grid reflow */
1343
+ .transition-vircantie { view-transition-name: vircantie; }
1344
+
1345
+ /* Aspect ratio changes */
1346
+ .transition-gant-thala { view-transition-name: gant-thala; }
1347
+
1348
+ ::view-transition-old(gant-thala),
1349
+ ::view-transition-new(gant-thala) {
1350
+ animation-duration: var(--transition-duration);
1351
+ animation-timing-function: var(--transition-ease-out);
1352
+ /* Aspect ratio morphs smoothly */
1353
+ }
1354
+
1355
+ /* ===== THEME TRANSITIONS ===== */
1356
+ .transition-theme { view-transition-name: theme; }
1357
+
1358
+ /* Cross-fade for theme changes */
1359
+ ::view-transition-old(theme),
1360
+ ::view-transition-new(theme) {
1361
+ animation: none;
1362
+ mix-blend-mode: normal;
1363
+ }
1364
+
1365
+ ::view-transition-old(theme) {
1366
+ animation: var(--transition-duration-slow) var(--transition-ease) both fade-out;
1367
+ }
1368
+
1369
+ ::view-transition-new(theme) {
1370
+ animation: var(--transition-duration-slow) var(--transition-ease) both fade-in;
1371
+ }
1372
+
1373
+ /* ===== REDUCED MOTION ===== */
1374
+ @media (prefers-reduced-motion: reduce) {
1375
+ ::view-transition-group(*),
1376
+ ::view-transition-old(*),
1377
+ ::view-transition-new(*) {
1378
+ animation: none !important;
1379
+ }
1380
+
1381
+ :root {
1382
+ --transition-duration: 0ms;
1383
+ --transition-duration-fast: 0ms;
1384
+ --transition-duration-slow: 0ms;
1385
+ }
1386
+ }
1387
+
1388
+ /* ===== ACTIVE TRANSITION STATES ===== */
1389
+ /* Style the page during an active view transition */
1390
+ :root:active-view-transition {
1391
+ /* Prevent interaction during transition */
1392
+ pointer-events: none;
1393
+ }
1394
+
1395
+ :root:active-view-transition-type(theme) {
1396
+ /* Special handling for theme transitions */
1397
+ }
1398
+
1399
+ :root:active-view-transition-type(layout) {
1400
+ /* Special handling for layout transitions */
1401
+ }
1402
+
1403
+ /* ===== FALLBACK FOR UNSUPPORTED BROWSERS ===== */
1404
+ /*
1405
+ * When View Transitions aren't supported, these CSS transitions
1406
+ * provide a graceful fallback for common properties.
1407
+ */
1408
+ @supports not (view-transition-name: none) {
1409
+ /* Elvish primitives get CSS transition fallbacks */
1410
+ i-glan-veleg,
1411
+ i-gwistindor,
1412
+ i-vircantie,
1413
+ i-gant-thala,
1414
+ i-enedh,
1415
+ i-hath {
1416
+ transition:
1417
+ width var(--transition-duration, 300ms) var(--transition-ease, ease),
1418
+ height var(--transition-duration, 300ms) var(--transition-ease, ease),
1419
+ flex-basis var(--transition-duration, 300ms) var(--transition-ease, ease),
1420
+ gap var(--transition-duration, 300ms) var(--transition-ease, ease);
1421
+ }
1422
+
1423
+ i-gant-thala {
1424
+ transition: aspect-ratio var(--transition-duration, 300ms) var(--transition-ease, ease);
1425
+ }
1426
+ }
1427
+
1428
+
1429
+ /* === primitives/hath/hath.css === */
1430
+ /**
1431
+ * Stack Layout
1432
+ *
1433
+ * Injects vertical margin between successive child elements.
1434
+ * The foundation of vertical rhythm.
1435
+ */
1436
+
1437
+ i-hath {
1438
+ display: flex;
1439
+ flex-direction: column;
1440
+ justify-content: flex-start;
1441
+ }
1442
+
1443
+ i-hath > * {
1444
+ /* Remove any existing vertical margins */
1445
+ margin-block: 0;
1446
+ }
1447
+
1448
+ i-hath > * + * {
1449
+ /* Apply margin only between successive elements */
1450
+ margin-block-start: var(--hath-space, var(--s1));
1451
+ }
1452
+
1453
+ /* Recursive variant - applies to all descendants */
1454
+ i-hath[recursive] * + * {
1455
+ margin-block-start: var(--hath-space, var(--s1));
1456
+ }
1457
+
1458
+ /* Split variant - pushes element and siblings after it to the bottom */
1459
+ i-hath[split-after="1"] > :nth-child(1) { margin-block-end: auto; }
1460
+ i-hath[split-after="2"] > :nth-child(2) { margin-block-end: auto; }
1461
+ i-hath[split-after="3"] > :nth-child(3) { margin-block-end: auto; }
1462
+ i-hath[split-after="4"] > :nth-child(4) { margin-block-end: auto; }
1463
+ i-hath[split-after="5"] > :nth-child(5) { margin-block-end: auto; }
1464
+
1465
+ /* When Stack needs to fill its parent */
1466
+ i-hath:only-child {
1467
+ block-size: 100%;
1468
+ }
1469
+
1470
+
1471
+ /* === primitives/bau/bau.css === */
1472
+ /**
1473
+ * Box Layout (i-bau)
1474
+ *
1475
+ * A container primitive focused on padding/structure.
1476
+ * Visual styling (borders, backgrounds) is opt-in via attributes.
1477
+ *
1478
+ * Layout-first philosophy: invisible by default, styled explicitly.
1479
+ *
1480
+ * Attributes:
1481
+ * padding="..." - Override padding (default: var(--s1))
1482
+ * border - Add border
1483
+ * border-width="..."- Custom border width
1484
+ * bg - Add background color
1485
+ * invert - Invert colors (dark bg, light text)
1486
+ * compact - Remove padding
1487
+ * borderless - Explicitly remove border (for overriding)
1488
+ */
1489
+
1490
+ i-bau {
1491
+ display: block;
1492
+ padding: var(--bau-padding, var(--s1));
1493
+
1494
+ /* Color tokens - can be overridden per-instance */
1495
+ --bau-color-light: var(--color-surface-raised, var(--color-light));
1496
+ --bau-color-dark: var(--color-text, var(--color-dark));
1497
+ --bau-border-color: var(--color-border, currentColor);
1498
+
1499
+ /* Layout-first: no visual styling by default */
1500
+ /* Border width and background are 0/transparent from reset.css */
1501
+ }
1502
+
1503
+ /* ===== VISUAL STYLING (Opt-in) ===== */
1504
+
1505
+ /* Add border - use [border] attribute */
1506
+ i-bau[border] {
1507
+ border-width: var(--bau-border-width, var(--border-thin, 1px));
1508
+ border-color: var(--bau-border-color);
1509
+ }
1510
+
1511
+ /* Custom border width */
1512
+ i-bau[border-width] {
1513
+ border-width: var(--bau-border-width, var(--border-thin, 1px));
1514
+ border-color: var(--bau-border-color);
1515
+ }
1516
+
1517
+ /* Add background - use [bg] attribute */
1518
+ i-bau[bg] {
1519
+ background-color: var(--bau-color-light);
1520
+ color: var(--bau-color-dark);
1521
+ }
1522
+
1523
+ /* Inverted color scheme (implies bg) */
1524
+ i-bau[invert] {
1525
+ background-color: var(--bau-color-dark);
1526
+ color: var(--bau-color-light);
1527
+ }
1528
+
1529
+ /* Combined: border + background */
1530
+ i-bau[border][bg],
1531
+ i-bau[border][invert] {
1532
+ border-width: var(--bau-border-width, var(--border-thin, 1px));
1533
+ border-color: var(--bau-border-color);
1534
+ }
1535
+
1536
+ /* ===== LAYOUT VARIANTS ===== */
1537
+
1538
+ /* No padding */
1539
+ i-bau[compact] {
1540
+ padding: 0;
1541
+ }
1542
+
1543
+ /* Explicitly no border (for overriding inherited styles) */
1544
+ i-bau[borderless] {
1545
+ border-width: 0;
1546
+ }
1547
+
1548
+ /* ===== HIGH CONTRAST MODE ===== */
1549
+ /* Transparent outline for Windows High Contrast Mode */
1550
+ @media (forced-colors: active) {
1551
+ i-bau[border],
1552
+ i-bau[bg],
1553
+ i-bau[invert] {
1554
+ outline: var(--bau-border-width, 1px) solid transparent;
1555
+ outline-offset: calc(var(--bau-border-width, 1px) * -1);
1556
+ }
1557
+ }
1558
+
1559
+
1560
+ /* === primitives/enedh/enedh.css === */
1561
+ /**
1562
+ * Center Layout
1563
+ *
1564
+ * Horizontally centers content with a maximum width.
1565
+ * Preserves gutters (minimum space on sides) in narrow contexts.
1566
+ */
1567
+
1568
+ i-enedh {
1569
+ display: block;
1570
+
1571
+ /* Use content-box so padding adds to the max-width, not subtracts */
1572
+ box-sizing: content-box;
1573
+
1574
+ /* Center with auto margins */
1575
+ margin-inline: auto;
1576
+
1577
+ /*
1578
+ * Maximum width - JS sets --enedh-max from the max attribute.
1579
+ * Fallback chain: attribute value → 100% (full width until JS runs)
1580
+ * This ensures layouts work even if JS fails to load.
1581
+ */
1582
+ max-inline-size: var(--enedh-max, 100%);
1583
+
1584
+ /* Gutters - minimum space on either side */
1585
+ padding-inline: var(--enedh-gutters, 0);
1586
+ }
1587
+
1588
+ /* Intrinsic centering - center children based on their content width */
1589
+ i-enedh[intrinsic] {
1590
+ display: flex;
1591
+ flex-direction: column;
1592
+ align-items: center;
1593
+ }
1594
+
1595
+ /* Also center the text */
1596
+ i-enedh[and-text] {
1597
+ text-align: center;
1598
+ }
1599
+
1600
+
1601
+ /* === primitives/tiniath/tiniath.css === */
1602
+ /**
1603
+ * Cluster Layout
1604
+ *
1605
+ * Groups elements that differ in length and wrap naturally.
1606
+ * Perfect for tags, buttons, navigation items.
1607
+ */
1608
+
1609
+ i-tiniath {
1610
+ display: flex;
1611
+ flex-wrap: wrap;
1612
+ gap: var(--tiniath-space, var(--s1));
1613
+
1614
+ /* Default justification and alignment */
1615
+ justify-content: var(--tiniath-justify, flex-start);
1616
+ align-items: var(--tiniath-align, center);
1617
+ }
1618
+
1619
+
1620
+ /* === primitives/glan-veleg/glan-veleg.css === */
1621
+ /**
1622
+ * GlanVeleg Layout
1623
+ *
1624
+ * A two-element layout where one has a fixed width (sidebar)
1625
+ * and the other fills remaining space. Switches to vertical
1626
+ * when the non-sidebar would be less than a threshold.
1627
+ */
1628
+
1629
+ i-glan-veleg {
1630
+ display: flex;
1631
+ flex-wrap: wrap;
1632
+ gap: var(--glan-veleg-space, var(--s1));
1633
+ }
1634
+
1635
+ /* Default: sidebar is the first child (left side) */
1636
+ i-glan-veleg > * {
1637
+ flex-grow: 1;
1638
+ }
1639
+
1640
+ i-glan-veleg > :first-child {
1641
+ flex-basis: var(--glan-veleg-width, 20rem);
1642
+ }
1643
+
1644
+ i-glan-veleg > :last-child {
1645
+ flex-basis: 0;
1646
+ flex-grow: 999;
1647
+ min-inline-size: var(--glan-veleg-content-min, 50%);
1648
+ }
1649
+
1650
+ /* When sidebar is on the right */
1651
+ i-glan-veleg[side="right"] > :first-child {
1652
+ flex-basis: 0;
1653
+ flex-grow: 999;
1654
+ min-inline-size: var(--glan-veleg-content-min, 50%);
1655
+ }
1656
+
1657
+ i-glan-veleg[side="right"] > :last-child {
1658
+ flex-basis: var(--glan-veleg-width, 20rem);
1659
+ flex-grow: 1;
1660
+ }
1661
+
1662
+ /* Disable equal height stretching */
1663
+ i-glan-veleg[no-stretch] {
1664
+ align-items: flex-start;
1665
+ }
1666
+
1667
+
1668
+ /* === primitives/gwistindor/gwistindor.css === */
1669
+ /**
1670
+ * Switcher Layout
1671
+ *
1672
+ * Switches directly between horizontal and vertical layouts
1673
+ * at a container-based threshold. No intermediary states.
1674
+ *
1675
+ * Uses the "Holy Albatross" technique.
1676
+ */
1677
+
1678
+ i-gwistindor {
1679
+ display: flex;
1680
+ flex-wrap: wrap;
1681
+ gap: var(--gwistindor-space, var(--s1));
1682
+ }
1683
+
1684
+ i-gwistindor > * {
1685
+ flex-grow: 1;
1686
+ /*
1687
+ * The magic: produces huge positive (vertical) or negative (horizontal) values.
1688
+ * Default: --layout-threshold-md (45rem ≈ 720px) - switches at tablet width.
1689
+ * NOTE: This is a *layout* threshold, not a text measure!
1690
+ */
1691
+ flex-basis: calc((var(--gwistindor-threshold, var(--layout-threshold-md, 45rem)) - 100%) * 999);
1692
+ }
1693
+
1694
+ /* Quantity query: force vertical if more than threshold items */
1695
+ i-gwistindor[limit="2"] > :nth-last-child(n+3),
1696
+ i-gwistindor[limit="2"] > :nth-last-child(n+3) ~ * {
1697
+ flex-basis: 100%;
1698
+ }
1699
+
1700
+ i-gwistindor[limit="3"] > :nth-last-child(n+4),
1701
+ i-gwistindor[limit="3"] > :nth-last-child(n+4) ~ * {
1702
+ flex-basis: 100%;
1703
+ }
1704
+
1705
+ i-gwistindor:not([limit]) > :nth-last-child(n+5),
1706
+ i-gwistindor:not([limit]) > :nth-last-child(n+5) ~ *,
1707
+ i-gwistindor[limit="4"] > :nth-last-child(n+5),
1708
+ i-gwistindor[limit="4"] > :nth-last-child(n+5) ~ * {
1709
+ flex-basis: 100%;
1710
+ }
1711
+
1712
+ i-gwistindor[limit="5"] > :nth-last-child(n+6),
1713
+ i-gwistindor[limit="5"] > :nth-last-child(n+6) ~ * {
1714
+ flex-basis: 100%;
1715
+ }
1716
+
1717
+ i-gwistindor[limit="6"] > :nth-last-child(n+7),
1718
+ i-gwistindor[limit="6"] > :nth-last-child(n+7) ~ * {
1719
+ flex-basis: 100%;
1720
+ }
1721
+
1722
+
1723
+ /* === primitives/esgal/esgal.css === */
1724
+ /**
1725
+ * Cover Layout
1726
+ *
1727
+ * Vertically centers a principal element with optional
1728
+ * header and footer elements. Perfect for hero sections.
1729
+ */
1730
+
1731
+ i-esgal {
1732
+ display: flex;
1733
+ flex-direction: column;
1734
+ min-block-size: var(--esgal-min-height, 100vh);
1735
+ padding: var(--esgal-space, var(--s1));
1736
+ }
1737
+
1738
+ i-esgal > * {
1739
+ margin-block: var(--esgal-space, var(--s1));
1740
+ }
1741
+
1742
+ /* The centered element - default is h1 */
1743
+ i-esgal > :first-child:not(h1, h2, h3, [data-centered]) {
1744
+ margin-block-start: 0;
1745
+ }
1746
+
1747
+ i-esgal > :last-child:not(h1, h2, h3, [data-centered]) {
1748
+ margin-block-end: 0;
1749
+ }
1750
+
1751
+ /* Elements that should be centered */
1752
+ i-esgal > h1,
1753
+ i-esgal > h2,
1754
+ i-esgal > h3,
1755
+ i-esgal > [data-centered] {
1756
+ margin-block: auto;
1757
+ }
1758
+
1759
+ /* No padding variant */
1760
+ i-esgal[no-pad] {
1761
+ padding: 0;
1762
+ }
1763
+
1764
+
1765
+ /* === primitives/vircantie/vircantie.css === */
1766
+ /**
1767
+ * Vircantie Layout (Grid)
1768
+ *
1769
+ * Auto-flowing grid with responsive columns based on minimum width.
1770
+ * Uses CSS Grid's auto-fit with min() for robust responsiveness.
1771
+ */
1772
+
1773
+ i-vircantie {
1774
+ display: grid;
1775
+ gap: var(--vircantie-space, var(--s1));
1776
+ }
1777
+
1778
+ /*
1779
+ * Using min() prevents overflow when container is narrower than minimum.
1780
+ * The 100% value kicks in when min would cause overflow.
1781
+ */
1782
+ @supports (width: min(var(--vircantie-min, 250px), 100%)) {
1783
+ i-vircantie {
1784
+ grid-template-columns: repeat(
1785
+ auto-fit,
1786
+ minmax(min(var(--vircantie-min, 250px), 100%), 1fr)
1787
+ );
1788
+ }
1789
+ }
1790
+
1791
+
1792
+ /* === primitives/gant-thala/gant-thala.css === */
1793
+ /**
1794
+ * Frame Layout
1795
+ *
1796
+ * Constrains content to a specific aspect ratio.
1797
+ * Perfect for images, videos, and embedded content.
1798
+ */
1799
+
1800
+ i-gant-thala {
1801
+ display: block;
1802
+ overflow: hidden;
1803
+
1804
+ /* Default 16:9 ratio */
1805
+ gant-thala-ratio: var(--gant-thala-n, 16) / var(--gant-thala-d, 9);
1806
+ }
1807
+
1808
+ /* For images and videos, cover the frame */
1809
+ i-gant-thala > img,
1810
+ i-gant-thala > video {
1811
+ inline-size: 100%;
1812
+ block-size: 100%;
1813
+ object-fit: cover;
1814
+ /* Default: center the crop */
1815
+ object-position: center;
1816
+ }
1817
+
1818
+ /* Non-media content gets centered */
1819
+ i-gant-thala > :not(img):not(video) {
1820
+ display: flex;
1821
+ align-items: center;
1822
+ justify-content: center;
1823
+ block-size: 100%;
1824
+ }
1825
+
1826
+
1827
+ /* === primitives/glan-tholl/glan-tholl.css === */
1828
+ /**
1829
+ * Reel Layout
1830
+ *
1831
+ * Horizontal scrolling container for overflow content.
1832
+ * A robust alternative to JavaScript carousels.
1833
+ */
1834
+
1835
+ i-glan-tholl {
1836
+ display: flex;
1837
+ block-size: var(--glan-tholl-height, auto);
1838
+
1839
+ overflow-x: auto;
1840
+ overflow-y: hidden;
1841
+
1842
+ /* Smooth scrolling where supported and preferred */
1843
+ scroll-behavior: smooth;
1844
+
1845
+ /* Scroll snap for better UX */
1846
+ scroll-snap-type: x mandatory;
1847
+
1848
+ /* Firefox scrollbar styling */
1849
+ scrollbar-color: var(--color-dark) var(--color-light);
1850
+ }
1851
+
1852
+ i-glan-tholl > * {
1853
+ flex: 0 0 var(--glan-tholl-item-width, auto);
1854
+ scroll-snap-align: start;
1855
+ }
1856
+
1857
+ i-glan-tholl > * + * {
1858
+ margin-inline-start: var(--glan-tholl-space, var(--s1));
1859
+ }
1860
+
1861
+ /* Images should maintain aspect ratio */
1862
+ i-glan-tholl > img {
1863
+ block-size: 100%;
1864
+ inline-size: auto;
1865
+ flex-basis: auto;
1866
+ }
1867
+
1868
+ /* Webkit scrollbar styling */
1869
+ i-glan-tholl::-webkit-scrollbar {
1870
+ block-size: 0.75rem;
1871
+ }
1872
+
1873
+ i-glan-tholl::-webkit-scrollbar-track {
1874
+ background-color: var(--color-light);
1875
+ }
1876
+
1877
+ i-glan-tholl::-webkit-scrollbar-thumb {
1878
+ background-color: var(--color-mid);
1879
+ border-radius: var(--s-2);
1880
+ }
1881
+
1882
+ /* Hide scrollbar variant */
1883
+ i-glan-tholl[no-bar] {
1884
+ scrollbar-width: none;
1885
+ }
1886
+
1887
+ i-glan-tholl[no-bar]::-webkit-scrollbar {
1888
+ display: none;
1889
+ }
1890
+
1891
+ /* Overflowing state (added via JS) */
1892
+ i-glan-tholl.overflowing {
1893
+ padding-block-end: var(--glan-tholl-space, var(--s1));
1894
+ }
1895
+
1896
+ i-glan-tholl.overflowing.no-bar {
1897
+ padding-block-end: 0;
1898
+ }
1899
+
1900
+
1901
+ /* === primitives/fano/fano.css === */
1902
+ /**
1903
+ * Imposter Layout
1904
+ *
1905
+ * Positions an element over content, centered within a
1906
+ * positioning container. Perfect for modals and overlays.
1907
+ */
1908
+
1909
+ i-fano {
1910
+ /* Default: absolute positioning relative to nearest positioned ancestor */
1911
+ position: var(--fano-position, absolute);
1912
+
1913
+ /* Center the element */
1914
+ inset-block-start: 50%;
1915
+ inset-inline-start: 50%;
1916
+ transform: translate(-50%, -50%);
1917
+ }
1918
+
1919
+ /* Fixed positioning variant (relative to viewport) */
1920
+ i-fano[fixed] {
1921
+ --fano-position: fixed;
1922
+ }
1923
+
1924
+ /* Contain within positioning container */
1925
+ i-fano[contain] {
1926
+ overflow: auto;
1927
+ max-inline-size: calc(100% - (var(--fano-margin, 0px) * 2));
1928
+ max-block-size: calc(100% - (var(--fano-margin, 0px) * 2));
1929
+ }
1930
+
1931
+
1932
+ /* === primitives/thann/thann.css === */
1933
+ /**
1934
+ * Thann Layout (Icon)
1935
+ *
1936
+ * Aligns an SVG icon with accompanying text.
1937
+ * Uses relative units for automatic scaling.
1938
+ *
1939
+ * Attributes:
1940
+ * - echuiol: "active" state styling
1941
+ * - dhoren: "hidden" state (visually hidden but accessible)
1942
+ */
1943
+
1944
+ i-thann {
1945
+ display: inline-flex;
1946
+ align-items: baseline;
1947
+ gap: var(--thann-space, 0);
1948
+ }
1949
+
1950
+ i-thann svg {
1951
+ /* Match uppercase letter height */
1952
+ block-size: 0.75em;
1953
+ block-size: 1cap; /* Where supported */
1954
+
1955
+ inline-size: 0.75em;
1956
+ inline-size: 1cap; /* Where supported */
1957
+
1958
+ /* Inherit text color */
1959
+ fill: currentColor;
1960
+ stroke: currentColor;
1961
+ }
1962
+
1963
+ /* When using natural word spacing (no explicit space) */
1964
+ i-thann:not([space]) {
1965
+ display: inline;
1966
+ }
1967
+
1968
+ i-thann:not([space]) svg {
1969
+ display: inline;
1970
+ vertical-align: -0.125em; /* Fine-tune baseline alignment */
1971
+ }
1972
+
1973
+ /* Echuiol = Active state */
1974
+ i-thann[echuiol] {
1975
+ color: var(--color-accent, currentColor);
1976
+ }
1977
+
1978
+ /* Dhoren = Hidden (visually hidden but accessible) */
1979
+ i-thann[dhoren] {
1980
+ position: absolute;
1981
+ inline-size: 1px;
1982
+ block-size: 1px;
1983
+ padding: 0;
1984
+ margin: -1px;
1985
+ overflow: hidden;
1986
+ clip: rect(0, 0, 0, 0);
1987
+ white-space: nowrap;
1988
+ border: 0;
1989
+ }
1990
+
1991
+
1992
+ /* === primitives/adleithian/adleithian.css === */
1993
+ /**
1994
+ * Adleithian Layout
1995
+ *
1996
+ * Establishes a container query context.
1997
+ * The escape hatch for when intrinsic layouts need manual intervention.
1998
+ */
1999
+
2000
+ i-adleithian {
2001
+ display: block;
2002
+ adleithian-type: inline-size;
2003
+ }
2004
+
2005
+ /* Named containers use the name attribute */
2006
+ i-adleithian[name] {
2007
+ adleithian-name: attr(name);
2008
+ }
2009
+
2010
+
2011
+ /* === primitives/him/him.css === */
2012
+ /**
2013
+ * Him Layout (NEW)
2014
+ *
2015
+ * Creates sticky positioning with configurable offset.
2016
+ * Solves the common pattern of sticky headers/sidebars.
2017
+ *
2018
+ * IMPORTANT: The parent must have defined height or be
2019
+ * in a scroll context for sticky to work properly.
2020
+ */
2021
+
2022
+ i-him {
2023
+ display: block;
2024
+ position: sticky;
2025
+
2026
+ /* Default: stick to top */
2027
+ inset-block-start: var(--him-offset, 0);
2028
+
2029
+ /* Ensure it stays above sibling content when stuck */
2030
+ z-index: var(--him-z, 1);
2031
+ }
2032
+
2033
+ /* Stick to bottom instead */
2034
+ i-him[to="bottom"] {
2035
+ inset-block-start: auto;
2036
+ inset-block-end: var(--him-offset, 0);
2037
+ }
2038
+
2039
+ /* Stick to left (in horizontal scroll context) */
2040
+ i-him[to="left"] {
2041
+ inset-block-start: auto;
2042
+ inset-inline-start: var(--him-offset, 0);
2043
+ }
2044
+
2045
+ /* Stick to right (in horizontal scroll context) */
2046
+ i-him[to="right"] {
2047
+ inset-block-start: auto;
2048
+ inset-inline-end: var(--him-offset, 0);
2049
+ }
2050
+
2051
+ /* Sentinel for detecting stuck state (JavaScript enhancement) */
2052
+ i-him[data-stuck="true"] {
2053
+ /* Add styles here or use this selector in your own CSS */
2054
+ }
2055
+
2056
+
2057
+ /* === primitives/miriant/miriant.css === */
2058
+ /**
2059
+ * Grid-Placed Layout (NEW)
2060
+ *
2061
+ * A CSS Grid with explicit control over columns, rows, and item placement.
2062
+ * For when the auto-flow Grid primitive isn't enough.
2063
+ *
2064
+ * This is the "escape hatch" for complex grid layouts like:
2065
+ * - Magazine-style layouts with spanning items
2066
+ * - Dashboard widgets of varying sizes
2067
+ * - Bento box layouts
2068
+ */
2069
+
2070
+ i-miriant {
2071
+ display: grid;
2072
+ gap: var(--miriant-space, var(--s1));
2073
+
2074
+ /* Default: 12-column grid (common baseline) */
2075
+ grid-template-columns: repeat(var(--miriant-columns, 12), 1fr);
2076
+
2077
+ /* Rows auto-size by default */
2078
+ grid-auto-rows: var(--miriant-row-height, minmax(0, auto));
2079
+ }
2080
+
2081
+ /* Child placement utilities using data attributes */
2082
+ /* Columns */
2083
+ i-miriant > [data-col-span="1"] { grid-column: span 1; }
2084
+ i-miriant > [data-col-span="2"] { grid-column: span 2; }
2085
+ i-miriant > [data-col-span="3"] { grid-column: span 3; }
2086
+ i-miriant > [data-col-span="4"] { grid-column: span 4; }
2087
+ i-miriant > [data-col-span="5"] { grid-column: span 5; }
2088
+ i-miriant > [data-col-span="6"] { grid-column: span 6; }
2089
+ i-miriant > [data-col-span="7"] { grid-column: span 7; }
2090
+ i-miriant > [data-col-span="8"] { grid-column: span 8; }
2091
+ i-miriant > [data-col-span="9"] { grid-column: span 9; }
2092
+ i-miriant > [data-col-span="10"] { grid-column: span 10; }
2093
+ i-miriant > [data-col-span="11"] { grid-column: span 11; }
2094
+ i-miriant > [data-col-span="12"] { grid-column: span 12; }
2095
+ i-miriant > [data-col-span="full"] { grid-column: 1 / -1; }
2096
+
2097
+ /* Rows */
2098
+ i-miriant > [data-row-span="1"] { grid-row: span 1; }
2099
+ i-miriant > [data-row-span="2"] { grid-row: span 2; }
2100
+ i-miriant > [data-row-span="3"] { grid-row: span 3; }
2101
+ i-miriant > [data-row-span="4"] { grid-row: span 4; }
2102
+ i-miriant > [data-row-span="5"] { grid-row: span 5; }
2103
+ i-miriant > [data-row-span="6"] { grid-row: span 6; }
2104
+
2105
+ /* Dense packing - fill holes */
2106
+ i-miriant[dense] {
2107
+ grid-auto-flow: dense;
2108
+ }
2109
+
2110
+ /* Responsive: collapse to single column below threshold */
2111
+ @container (width < 600px) {
2112
+ i-miriant {
2113
+ grid-template-columns: 1fr;
2114
+ }
2115
+
2116
+ i-miriant > [data-col-span] {
2117
+ grid-column: span 1;
2118
+ }
2119
+ }
2120
+
2121
+ /* Alternative: use media query fallback for browsers without container queries */
2122
+ @supports not (container-type: inline-size) {
2123
+ @media (max-width: 600px) {
2124
+ i-miriant {
2125
+ grid-template-columns: 1fr;
2126
+ }
2127
+
2128
+ i-miriant > [data-col-span] {
2129
+ grid-column: span 1;
2130
+ }
2131
+ }
2132
+ }
2133
+
2134
+
2135
+ /* === primitives/gonath/gonath.css === */
2136
+ /**
2137
+ * Gonath Layout (NEW)
2138
+ *
2139
+ * Pinterest-style layout where items pack efficiently into columns.
2140
+ * Uses CSS columns as the primary method (widely supported).
2141
+ * Progressively enhances to CSS Grid masonry where supported.
2142
+ *
2143
+ * CAVEATS:
2144
+ * - CSS columns: items flow top-to-bottom, then left-to-right
2145
+ * (not strictly "visual order" like true masonry)
2146
+ * - CSS Grid masonry: experimental, limited browser support
2147
+ */
2148
+
2149
+ i-gonath {
2150
+ display: block;
2151
+
2152
+ /* CSS Columns approach (fallback) */
2153
+ column-count: var(--gonath-columns, 3);
2154
+ column-gap: var(--gonath-space, var(--s1));
2155
+ }
2156
+
2157
+ i-gonath > * {
2158
+ /* Prevent items from breaking across columns */
2159
+ break-inside: avoid;
2160
+
2161
+ /* Add vertical spacing between items */
2162
+ margin-block-end: var(--gonath-space, var(--s1));
2163
+ }
2164
+
2165
+ /* Progressive enhancement: CSS Grid masonry where supported */
2166
+ @supports (grid-template-rows: masonry) {
2167
+ i-gonath {
2168
+ display: grid;
2169
+ column-count: unset;
2170
+ grid-template-columns: repeat(var(--gonath-columns, 3), 1fr);
2171
+ grid-template-rows: masonry;
2172
+ gap: var(--gonath-space, var(--s1));
2173
+ }
2174
+
2175
+ i-gonath > * {
2176
+ margin-block-end: 0;
2177
+ break-inside: unset;
2178
+ }
2179
+ }
2180
+
2181
+ /* Responsive column counts */
2182
+ @media (max-width: 800px) {
2183
+ i-gonath {
2184
+ --gonath-columns: 2;
2185
+ }
2186
+ }
2187
+
2188
+ @media (max-width: 500px) {
2189
+ i-gonath {
2190
+ --gonath-columns: 1;
2191
+ }
2192
+ }
2193
+
2194
+