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,288 @@
1
+ /**
2
+ * Elvish - View Transitions
3
+ *
4
+ * Smooth, buttery transitions between layout states using the View Transitions API.
5
+ *
6
+ * Browser Support (Jan 2026) - Baseline Newly Available since Oct 2025:
7
+ * - Chrome/Edge: ✅ 111+
8
+ * - Safari: ✅ 18+
9
+ * - Firefox: ✅ 144+
10
+ *
11
+ * New features available:
12
+ * - view-transition-name: match-element (auto-naming)
13
+ * - view-transition-class (group styling)
14
+ * - :active-view-transition pseudo-class
15
+ *
16
+ * Usage:
17
+ * 1. Add view-transition-name to elements that should animate independently
18
+ * 2. Use startTransition() helper to wrap DOM changes
19
+ * 3. Customize timing with CSS custom properties
20
+ */
21
+
22
+ /* ===== ENABLE VIEW TRANSITIONS ===== */
23
+ @view-transition {
24
+ navigation: auto; /* Enable for same-document navigations */
25
+ }
26
+
27
+ /* ===== TRANSITION TIMING TOKENS ===== */
28
+ :root {
29
+ --transition-duration: 300ms;
30
+ --transition-duration-fast: 150ms;
31
+ --transition-duration-slow: 500ms;
32
+
33
+ /* Easing curves */
34
+ --transition-ease: cubic-bezier(0.25, 0.1, 0.25, 1); /* Smooth default */
35
+ --transition-ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* Quick start, gentle end */
36
+ --transition-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* Symmetric */
37
+ --transition-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* Slight overshoot */
38
+ }
39
+
40
+ /* ===== DEFAULT TRANSITION BEHAVIOR ===== */
41
+ /* Old state fades/scales out */
42
+ ::view-transition-old(root) {
43
+ animation: var(--transition-duration) var(--transition-ease) both fade-out;
44
+ }
45
+
46
+ /* New state fades/scales in */
47
+ ::view-transition-new(root) {
48
+ animation: var(--transition-duration) var(--transition-ease) both fade-in;
49
+ }
50
+
51
+ @keyframes fade-out {
52
+ to { opacity: 0; }
53
+ }
54
+
55
+ @keyframes fade-in {
56
+ from { opacity: 0; }
57
+ }
58
+
59
+ /* ===== NAMED TRANSITION GROUPS ===== */
60
+
61
+ /*
62
+ * Assign view-transition-name to elements for independent animation.
63
+ * Each named element animates separately from the root transition.
64
+ */
65
+
66
+ /* Layout primitives can opt-in to independent transitions */
67
+ [data-transition] {
68
+ view-transition-name: var(--transition-name, auto);
69
+ }
70
+
71
+ /* Auto-naming for list/grid items - each gets unique name automatically */
72
+ [data-transition="auto"] > * {
73
+ view-transition-name: match-element;
74
+ }
75
+
76
+ /* Group styling with view-transition-class */
77
+ .transition-card-group > * {
78
+ view-transition-name: match-element;
79
+ view-transition-class: card;
80
+ }
81
+
82
+ /* Style all cards at once using the class */
83
+ ::view-transition-group(.card) {
84
+ animation-duration: var(--transition-duration);
85
+ animation-timing-function: var(--transition-ease-spring);
86
+ }
87
+
88
+ /* Utility classes for common transition names */
89
+ .transition-hero { view-transition-name: hero; }
90
+ .transition-header { view-transition-name: header; }
91
+ .transition-sidebar { view-transition-name: sidebar; }
92
+ .transition-main { view-transition-name: main; }
93
+ .transition-card { view-transition-name: card; }
94
+ .transition-modal { view-transition-name: modal; }
95
+
96
+ /* ===== TRANSITION ANIMATIONS ===== */
97
+
98
+ /* Morph (default for named elements) - smooth position/size interpolation */
99
+ ::view-transition-old(*),
100
+ ::view-transition-new(*) {
101
+ animation-duration: var(--transition-duration);
102
+ animation-timing-function: var(--transition-ease-out);
103
+ }
104
+
105
+ /* Slide transitions */
106
+ ::view-transition-old(slide-left) {
107
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-out-left;
108
+ }
109
+ ::view-transition-new(slide-left) {
110
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-in-left;
111
+ }
112
+
113
+ ::view-transition-old(slide-right) {
114
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-out-right;
115
+ }
116
+ ::view-transition-new(slide-right) {
117
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-in-right;
118
+ }
119
+
120
+ ::view-transition-old(slide-up) {
121
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-out-up;
122
+ }
123
+ ::view-transition-new(slide-up) {
124
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-in-up;
125
+ }
126
+
127
+ ::view-transition-old(slide-down) {
128
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-out-down;
129
+ }
130
+ ::view-transition-new(slide-down) {
131
+ animation: var(--transition-duration) var(--transition-ease-out) both slide-in-down;
132
+ }
133
+
134
+ @keyframes slide-out-left {
135
+ to { transform: translateX(-100%); opacity: 0; }
136
+ }
137
+ @keyframes slide-in-left {
138
+ from { transform: translateX(100%); opacity: 0; }
139
+ }
140
+ @keyframes slide-out-right {
141
+ to { transform: translateX(100%); opacity: 0; }
142
+ }
143
+ @keyframes slide-in-right {
144
+ from { transform: translateX(-100%); opacity: 0; }
145
+ }
146
+ @keyframes slide-out-up {
147
+ to { transform: translateY(-100%); opacity: 0; }
148
+ }
149
+ @keyframes slide-in-up {
150
+ from { transform: translateY(100%); opacity: 0; }
151
+ }
152
+ @keyframes slide-out-down {
153
+ to { transform: translateY(100%); opacity: 0; }
154
+ }
155
+ @keyframes slide-in-down {
156
+ from { transform: translateY(-100%); opacity: 0; }
157
+ }
158
+
159
+ /* Scale transitions */
160
+ ::view-transition-old(scale) {
161
+ animation: var(--transition-duration) var(--transition-ease-out) both scale-out;
162
+ }
163
+ ::view-transition-new(scale) {
164
+ animation: var(--transition-duration) var(--transition-ease-spring) both scale-in;
165
+ }
166
+
167
+ @keyframes scale-out {
168
+ to { transform: scale(0.9); opacity: 0; }
169
+ }
170
+ @keyframes scale-in {
171
+ from { transform: scale(1.1); opacity: 0; }
172
+ }
173
+
174
+ /* Flip transitions */
175
+ ::view-transition-old(flip) {
176
+ animation: var(--transition-duration-slow) var(--transition-ease) both flip-out;
177
+ }
178
+ ::view-transition-new(flip) {
179
+ animation: var(--transition-duration-slow) var(--transition-ease) both flip-in;
180
+ }
181
+
182
+ @keyframes flip-out {
183
+ to { transform: rotateY(90deg); opacity: 0; }
184
+ }
185
+ @keyframes flip-in {
186
+ from { transform: rotateY(-90deg); opacity: 0; }
187
+ }
188
+
189
+ /* ===== LAYOUT-SPECIFIC TRANSITIONS ===== */
190
+
191
+ /* Sidebar collapse/expand */
192
+ .transition-glan-veleg { view-transition-name: glan-veleg; }
193
+
194
+ ::view-transition-old(glan-veleg),
195
+ ::view-transition-new(glan-veleg) {
196
+ animation-duration: var(--transition-duration);
197
+ animation-timing-function: var(--transition-ease-out);
198
+ /* Height and width interpolate automatically */
199
+ }
200
+
201
+ /* Switcher horizontal ↔ vertical */
202
+ .transition-gwistindor { view-transition-name: gwistindor; }
203
+
204
+ /* Grid reflow */
205
+ .transition-vircantie { view-transition-name: vircantie; }
206
+
207
+ /* Aspect ratio changes */
208
+ .transition-gant-thala { view-transition-name: gant-thala; }
209
+
210
+ ::view-transition-old(gant-thala),
211
+ ::view-transition-new(gant-thala) {
212
+ animation-duration: var(--transition-duration);
213
+ animation-timing-function: var(--transition-ease-out);
214
+ /* Aspect ratio morphs smoothly */
215
+ }
216
+
217
+ /* ===== THEME TRANSITIONS ===== */
218
+ .transition-theme { view-transition-name: theme; }
219
+
220
+ /* Cross-fade for theme changes */
221
+ ::view-transition-old(theme),
222
+ ::view-transition-new(theme) {
223
+ animation: none;
224
+ mix-blend-mode: normal;
225
+ }
226
+
227
+ ::view-transition-old(theme) {
228
+ animation: var(--transition-duration-slow) var(--transition-ease) both fade-out;
229
+ }
230
+
231
+ ::view-transition-new(theme) {
232
+ animation: var(--transition-duration-slow) var(--transition-ease) both fade-in;
233
+ }
234
+
235
+ /* ===== REDUCED MOTION ===== */
236
+ @media (prefers-reduced-motion: reduce) {
237
+ ::view-transition-group(*),
238
+ ::view-transition-old(*),
239
+ ::view-transition-new(*) {
240
+ animation: none !important;
241
+ }
242
+
243
+ :root {
244
+ --transition-duration: 0ms;
245
+ --transition-duration-fast: 0ms;
246
+ --transition-duration-slow: 0ms;
247
+ }
248
+ }
249
+
250
+ /* ===== ACTIVE TRANSITION STATES ===== */
251
+ /* Style the page during an active view transition */
252
+ :root:active-view-transition {
253
+ /* Prevent interaction during transition */
254
+ pointer-events: none;
255
+ }
256
+
257
+ :root:active-view-transition-type(theme) {
258
+ /* Special handling for theme transitions */
259
+ }
260
+
261
+ :root:active-view-transition-type(layout) {
262
+ /* Special handling for layout transitions */
263
+ }
264
+
265
+ /* ===== FALLBACK FOR UNSUPPORTED BROWSERS ===== */
266
+ /*
267
+ * When View Transitions aren't supported, these CSS transitions
268
+ * provide a graceful fallback for common properties.
269
+ */
270
+ @supports not (view-transition-name: none) {
271
+ /* Elvish primitives get CSS transition fallbacks */
272
+ i-glan-veleg,
273
+ i-gwistindor,
274
+ i-vircantie,
275
+ i-gant-thala,
276
+ i-enedh,
277
+ i-hath {
278
+ transition:
279
+ width var(--transition-duration, 300ms) var(--transition-ease, ease),
280
+ height var(--transition-duration, 300ms) var(--transition-ease, ease),
281
+ flex-basis var(--transition-duration, 300ms) var(--transition-ease, ease),
282
+ gap var(--transition-duration, 300ms) var(--transition-ease, ease);
283
+ }
284
+
285
+ i-gant-thala {
286
+ transition: aspect-ratio var(--transition-duration, 300ms) var(--transition-ease, ease);
287
+ }
288
+ }
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Elvish - View Transitions Helper
3
+ *
4
+ * Utilities for smooth view transitions between layout states.
5
+ *
6
+ * Usage:
7
+ *
8
+ * import { transition, transitionTo } from './transitions.js';
9
+ *
10
+ * // Wrap any DOM change in a transition
11
+ * transition(() => {
12
+ * element.classList.toggle('collapsed');
13
+ * });
14
+ *
15
+ * // With custom options
16
+ * transition(() => {
17
+ * sidebar.hidden = true;
18
+ * }, { duration: 500 });
19
+ *
20
+ * // Transition with callback after completion
21
+ * transition(() => {
22
+ * grid.setAttribute('columns', '4');
23
+ * }).then(() => {
24
+ * console.log('Transition complete!');
25
+ * });
26
+ */
27
+
28
+ /**
29
+ * Check if View Transitions API is supported
30
+ */
31
+ export const supportsViewTransitions = () =>
32
+ typeof document !== 'undefined' &&
33
+ 'startViewTransition' in document;
34
+
35
+ /**
36
+ * Get the currently active view transition (if any)
37
+ * @returns {ViewTransition|null}
38
+ */
39
+ export const getActiveTransition = () =>
40
+ document.activeViewTransition ?? null;
41
+
42
+ /**
43
+ * Wrap a DOM mutation in a view transition
44
+ *
45
+ * @param {Function} updateCallback - Function that performs DOM changes
46
+ * @param {Object} options - Configuration options
47
+ * @param {number} options.duration - Override transition duration (ms)
48
+ * @param {string} options.easing - Override easing function
49
+ * @param {string[]} options.types - Transition types for :active-view-transition-type()
50
+ * @returns {Promise} Resolves when transition completes
51
+ */
52
+ export function transition(updateCallback, options = {}) {
53
+ // If View Transitions not supported, just run the callback
54
+ if (!supportsViewTransitions()) {
55
+ updateCallback();
56
+ return Promise.resolve();
57
+ }
58
+
59
+ // Apply custom duration/easing if provided
60
+ const root = document.documentElement;
61
+ const originalDuration = root.style.getPropertyValue('--transition-duration');
62
+ const originalEasing = root.style.getPropertyValue('--transition-ease');
63
+
64
+ if (options.duration) {
65
+ root.style.setProperty('--transition-duration', `${options.duration}ms`);
66
+ }
67
+ if (options.easing) {
68
+ root.style.setProperty('--transition-ease', options.easing);
69
+ }
70
+
71
+ // Start the view transition with optional types
72
+ const transitionOptions = options.types
73
+ ? { update: updateCallback, types: options.types }
74
+ : updateCallback;
75
+
76
+ const viewTransition = document.startViewTransition(transitionOptions);
77
+
78
+ // Restore original values after transition
79
+ return viewTransition.finished.then(() => {
80
+ if (options.duration) {
81
+ root.style.setProperty('--transition-duration', originalDuration || '');
82
+ }
83
+ if (options.easing) {
84
+ root.style.setProperty('--transition-ease', originalEasing || '');
85
+ }
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Transition an element to a new state by changing attributes/classes
91
+ *
92
+ * @param {HTMLElement} element - Element to transition
93
+ * @param {Object} changes - Attribute/class changes to apply
94
+ * @param {Object} changes.attrs - Attributes to set (use null to remove)
95
+ * @param {string[]} changes.addClass - Classes to add
96
+ * @param {string[]} changes.removeClass - Classes to remove
97
+ * @param {string[]} changes.toggleClass - Classes to toggle
98
+ * @param {Object} changes.style - Inline styles to set
99
+ * @param {Object} options - Transition options
100
+ * @returns {Promise}
101
+ */
102
+ export function transitionTo(element, changes = {}, options = {}) {
103
+ return transition(() => {
104
+ // Apply attribute changes
105
+ if (changes.attrs) {
106
+ Object.entries(changes.attrs).forEach(([key, value]) => {
107
+ if (value === null) {
108
+ element.removeAttribute(key);
109
+ } else {
110
+ element.setAttribute(key, value);
111
+ }
112
+ });
113
+ }
114
+
115
+ // Apply class changes
116
+ if (changes.addClass) {
117
+ element.classList.add(...changes.addClass);
118
+ }
119
+ if (changes.removeClass) {
120
+ element.classList.remove(...changes.removeClass);
121
+ }
122
+ if (changes.toggleClass) {
123
+ changes.toggleClass.forEach(cls => element.classList.toggle(cls));
124
+ }
125
+
126
+ // Apply style changes
127
+ if (changes.style) {
128
+ Object.entries(changes.style).forEach(([prop, value]) => {
129
+ element.style.setProperty(prop, value);
130
+ });
131
+ }
132
+ }, options);
133
+ }
134
+
135
+ /**
136
+ * Transition between two elements (e.g., swap content)
137
+ *
138
+ * @param {HTMLElement} outElement - Element leaving
139
+ * @param {HTMLElement} inElement - Element entering
140
+ * @param {Object} options - Transition options
141
+ * @returns {Promise}
142
+ */
143
+ export function crossfade(outElement, inElement, options = {}) {
144
+ return transition(() => {
145
+ outElement.hidden = true;
146
+ inElement.hidden = false;
147
+ }, options);
148
+ }
149
+
150
+ /**
151
+ * Create a named transition group for an element
152
+ * Elements with the same view-transition-name animate together
153
+ *
154
+ * @param {HTMLElement} element - Element to name
155
+ * @param {string} name - Transition name
156
+ */
157
+ export function setTransitionName(element, name) {
158
+ element.style.viewTransitionName = name;
159
+ }
160
+
161
+ /**
162
+ * Remove transition name from element
163
+ *
164
+ * @param {HTMLElement} element
165
+ */
166
+ export function clearTransitionName(element) {
167
+ element.style.viewTransitionName = '';
168
+ }
169
+
170
+ /**
171
+ * Batch multiple elements with unique transition names
172
+ * Useful for list/grid items that should animate independently
173
+ *
174
+ * @param {NodeList|HTMLElement[]} elements - Elements to name
175
+ * @param {string} prefix - Name prefix (will add index)
176
+ */
177
+ export function nameTransitionGroup(elements, prefix = 'item') {
178
+ elements.forEach((el, i) => {
179
+ el.style.viewTransitionName = `${prefix}-${i}`;
180
+ });
181
+ }
182
+
183
+ /**
184
+ * Enable auto-naming with match-element for a container's children
185
+ * Each child gets a unique browser-generated name automatically
186
+ *
187
+ * @param {HTMLElement} container - Parent element
188
+ * @param {string} [transitionClass] - Optional view-transition-class for group styling
189
+ */
190
+ export function enableAutoNaming(container, transitionClass) {
191
+ Array.from(container.children).forEach(child => {
192
+ child.style.viewTransitionName = 'match-element';
193
+ if (transitionClass) {
194
+ child.style.viewTransitionClass = transitionClass;
195
+ }
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Disable auto-naming for a container's children
201
+ *
202
+ * @param {HTMLElement} container - Parent element
203
+ */
204
+ export function disableAutoNaming(container) {
205
+ Array.from(container.children).forEach(child => {
206
+ child.style.viewTransitionName = '';
207
+ child.style.viewTransitionClass = '';
208
+ });
209
+ }
210
+
211
+ /**
212
+ * Elvish-specific: Transition ratio change
213
+ *
214
+ * @param {'golden'|'silver'|'fifth'} ratio - New ratio
215
+ * @param {Object} options - Transition options
216
+ */
217
+ export function transitionRatio(ratio, options = {}) {
218
+ const ratioVar = `var(--ratio-${ratio})`;
219
+ const measures = { golden: '60ch', fifth: '70ch', silver: '80ch' };
220
+
221
+ return transition(() => {
222
+ const root = document.documentElement;
223
+ root.style.setProperty('--ratio', ratioVar);
224
+ root.style.setProperty('--measure', measures[ratio]);
225
+ root.dataset.ratio = ratio;
226
+ }, { duration: 400, types: ['layout', 'ratio'], ...options });
227
+ }
228
+
229
+ /**
230
+ * Elvish-specific: Transition theme change
231
+ *
232
+ * @param {'light'|'dark'|'auto'} theme - New theme
233
+ * @param {Object} options - Transition options
234
+ */
235
+ export function transitionTheme(theme, options = {}) {
236
+ return transition(() => {
237
+ const root = document.documentElement;
238
+ if (theme === 'auto') {
239
+ root.style.colorScheme = 'light dark';
240
+ } else {
241
+ root.style.colorScheme = theme;
242
+ }
243
+ localStorage.setItem('elvish-theme', theme);
244
+ }, { duration: 300, types: ['theme'], ...options });
245
+ }
246
+
247
+ /**
248
+ * Elvish-specific: Transition layout primitive state
249
+ *
250
+ * @param {HTMLElement} primitive - Elvish layout element
251
+ * @param {Object} attrs - New attribute values
252
+ * @param {Object} options - Transition options
253
+ */
254
+ export function transitionLayout(primitive, attrs, options = {}) {
255
+ // Give the primitive a transition name if it doesn't have one
256
+ if (!primitive.style.viewTransitionName) {
257
+ primitive.style.viewTransitionName = primitive.tagName.toLowerCase();
258
+ }
259
+
260
+ return transitionTo(primitive, { attrs }, { types: ['layout'], ...options });
261
+ }
262
+
263
+ /**
264
+ * Skip/cancel the currently active view transition
265
+ */
266
+ export function skipTransition() {
267
+ const active = getActiveTransition();
268
+ if (active) {
269
+ active.skipTransition();
270
+ }
271
+ }
272
+
273
+ // Default export for convenient importing
274
+ export default {
275
+ transition,
276
+ transitionTo,
277
+ crossfade,
278
+ setTransitionName,
279
+ clearTransitionName,
280
+ nameTransitionGroup,
281
+ enableAutoNaming,
282
+ disableAutoNaming,
283
+ transitionRatio,
284
+ transitionTheme,
285
+ transitionLayout,
286
+ skipTransition,
287
+ supportsViewTransitions,
288
+ getActiveTransition,
289
+ };