@xenosystem/elements-react 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +18 -18
- package/README.md +41 -41
- package/package.json +58 -58
- package/src/chrome-separated.css +132 -132
- package/src/chrome-unified.css +72 -72
- package/src/containers/Badges.css +116 -116
- package/src/containers/avatar.css +70 -70
- package/src/containers/card.css +75 -75
- package/src/containers/chip.css +103 -103
- package/src/containers/list-row.css +132 -132
- package/src/containers/message-bubble.css +112 -112
- package/src/containers/panel.css +120 -120
- package/src/containers/table.css +102 -102
- package/src/containers/tile.css +17 -17
- package/src/content/callout.css +77 -77
- package/src/content/caret.css +30 -30
- package/src/content/code-block.css +159 -159
- package/src/content/collapsible.css +80 -80
- package/src/content/inline-code.css +14 -14
- package/src/content/model-picker.css +219 -219
- package/src/content/source-card.css +99 -99
- package/src/content/sources-disclosure.css +118 -118
- package/src/controls.css +453 -453
- package/src/fonts.css +34 -34
- package/src/forms/Checkbox.css +114 -114
- package/src/forms/RadioGroup.css +115 -115
- package/src/goo.css +97 -97
- package/src/icon-motion.css +1909 -1909
- package/src/layout/ResizablePanel.css +79 -79
- package/src/nav/sidebar.css +296 -296
- package/src/overlays/date-time-picker.css +238 -238
- package/src/overlays/menu.css +195 -195
- package/src/overlays/modal.css +167 -167
- package/src/overlays/picker-field.css +81 -81
- package/src/overlays/pill-filter.css +129 -129
- package/src/overlays/reveal.css +56 -56
- package/src/overlays/segmented-control.css +185 -185
- package/src/overlays/tabs.css +143 -143
- package/src/overlays/tooltip.css +126 -126
- package/src/scrollbar.css +184 -184
- package/src/size.css +179 -179
- package/src/status/progress-bar.css +88 -88
- package/src/status/spinner.css +33 -33
- package/src/status/statusindicator.css +65 -65
- package/src/status/steptimeline.css +137 -137
- package/src/status/thinkingcube.css +75 -75
- package/src/xeno-element.css +60 -60
- package/src/xeno-elements.css +71 -71
- package/src/xeno-theme.css +99 -99
package/src/nav/sidebar.css
CHANGED
|
@@ -1,296 +1,296 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Sidebar — the slide-in left navigation panel. Locked geometry: SQUARE corners, flush to the viewport
|
|
3
|
-
* edge; a fixed ~260px column on `var(--xeno-surface)`. CSS-first: the only state that drives appearance
|
|
4
|
-
* is `data-state="open|closed"` on the panel + backdrop. The only animated property is `transform`
|
|
5
|
-
* (translateX), guarded by reduced-motion. Monochrome — the scrim is a neutral, hueless black.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
.xeno-sidebar {
|
|
9
|
-
/* One row's stride, in one place: the pill's travel is computed from these, so a row that changes
|
|
10
|
-
height can never leave the pill landing between two rows. */
|
|
11
|
-
--xeno-sidebar-row: 36px;
|
|
12
|
-
--xeno-sidebar-row-gap: 2px;
|
|
13
|
-
/* How much the goo pill misbehaves. The squash is deliberately restrained — enough deformation to
|
|
14
|
-
read as mass, not enough to look like jelly on a nav you use all day — and the travel easing
|
|
15
|
-
overshoots only slightly (1.12, against the 1.4 of the library's overshoot token). Turn these
|
|
16
|
-
three up together to get the full CodePen bounce back. */
|
|
17
|
-
--xeno-goo-squash-x: 0.95;
|
|
18
|
-
--xeno-goo-squash-y: 1.1;
|
|
19
|
-
--xeno-goo-ease: cubic-bezier(0.34, 1.12, 0.5, 1);
|
|
20
|
-
position: fixed;
|
|
21
|
-
inset-block: 0;
|
|
22
|
-
inset-inline-start: 0;
|
|
23
|
-
z-index: 40;
|
|
24
|
-
display: flex;
|
|
25
|
-
flex-direction: column;
|
|
26
|
-
width: 260px;
|
|
27
|
-
max-width: 85vw;
|
|
28
|
-
height: 100%;
|
|
29
|
-
background: var(--xeno-surface);
|
|
30
|
-
border-inline-end: 1px solid var(--xeno-border);
|
|
31
|
-
border-radius: 0; /* locked: square, flush */
|
|
32
|
-
color: var(--xeno-text);
|
|
33
|
-
transform: translateX(0);
|
|
34
|
-
transition: transform var(--xeno-dur) var(--xeno-ease);
|
|
35
|
-
will-change: transform;
|
|
36
|
-
}
|
|
37
|
-
.xeno-sidebar[data-state='closed'] {
|
|
38
|
-
transform: translateX(-100%);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/* ── Header (brand + search) ─────────────────────────────────────────────────────── */
|
|
42
|
-
.xeno-sidebar-header {
|
|
43
|
-
display: flex;
|
|
44
|
-
flex-direction: column;
|
|
45
|
-
gap: 12px;
|
|
46
|
-
flex: none;
|
|
47
|
-
padding: 16px;
|
|
48
|
-
border-bottom: 1px solid var(--xeno-border);
|
|
49
|
-
}
|
|
50
|
-
.xeno-sidebar-brand {
|
|
51
|
-
font-size: 15px;
|
|
52
|
-
font-weight: 600;
|
|
53
|
-
line-height: 1;
|
|
54
|
-
letter-spacing: 0.04em;
|
|
55
|
-
color: var(--xeno-text);
|
|
56
|
-
}
|
|
57
|
-
/* Make the shared TextInput fill the header width. */
|
|
58
|
-
.xeno-sidebar-search {
|
|
59
|
-
display: flex;
|
|
60
|
-
width: 100%;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/* ── Nav (scrollable list of rows) ───────────────────────────────────────────────── */
|
|
64
|
-
.xeno-sidebar-nav {
|
|
65
|
-
flex: 1;
|
|
66
|
-
min-height: 0;
|
|
67
|
-
overflow-y: auto;
|
|
68
|
-
padding: 8px;
|
|
69
|
-
}
|
|
70
|
-
.xeno-sidebar-section {
|
|
71
|
-
display: flex;
|
|
72
|
-
flex-direction: column;
|
|
73
|
-
gap: var(--xeno-sidebar-row-gap);
|
|
74
|
-
}
|
|
75
|
-
/* The rows of one group + the pill that backs them. `position: relative` makes this the pill's
|
|
76
|
-
containing block, so its travel is measured from the first row of THIS group — which is why the
|
|
77
|
-
heading can sit outside and the trick survives sections of different shapes. */
|
|
78
|
-
.xeno-sidebar-items {
|
|
79
|
-
position: relative;
|
|
80
|
-
display: flex;
|
|
81
|
-
flex-direction: column;
|
|
82
|
-
gap: var(--xeno-sidebar-row-gap);
|
|
83
|
-
}
|
|
84
|
-
.xeno-sidebar-section + .xeno-sidebar-section {
|
|
85
|
-
margin-top: 8px;
|
|
86
|
-
}
|
|
87
|
-
.xeno-sidebar-heading {
|
|
88
|
-
padding: 10px 10px 4px;
|
|
89
|
-
font-size: 11px;
|
|
90
|
-
font-weight: 600;
|
|
91
|
-
line-height: 1;
|
|
92
|
-
letter-spacing: 0.08em;
|
|
93
|
-
text-transform: uppercase;
|
|
94
|
-
color: var(--xeno-muted);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/* ── Row (self-contained; NOT ListRow) ───────────────────────────────────────────── */
|
|
98
|
-
.xeno-sidebar-item {
|
|
99
|
-
position: relative;
|
|
100
|
-
z-index: 1; /* over the pill, which is painted behind the whole group */
|
|
101
|
-
display: flex;
|
|
102
|
-
align-items: center;
|
|
103
|
-
gap: 10px;
|
|
104
|
-
width: 100%;
|
|
105
|
-
height: var(--xeno-sidebar-row);
|
|
106
|
-
padding: 0 10px;
|
|
107
|
-
font-family: inherit;
|
|
108
|
-
font-size: 14px;
|
|
109
|
-
font-weight: 500;
|
|
110
|
-
line-height: 1;
|
|
111
|
-
text-align: left;
|
|
112
|
-
color: var(--xeno-muted);
|
|
113
|
-
background: transparent;
|
|
114
|
-
border: 1px solid transparent;
|
|
115
|
-
border-radius: var(--xeno-radius-md);
|
|
116
|
-
cursor: pointer;
|
|
117
|
-
user-select: none;
|
|
118
|
-
transition:
|
|
119
|
-
background var(--xeno-dur) ease,
|
|
120
|
-
color var(--xeno-dur) ease;
|
|
121
|
-
}
|
|
122
|
-
/* Neither hover nor selection paints its OWN background any more — the travelling pill below is the
|
|
123
|
-
single filled surface for the whole group. Two of them would read as two selections. */
|
|
124
|
-
.xeno-sidebar-item:hover,
|
|
125
|
-
.xeno-sidebar-item[data-selection='on'] {
|
|
126
|
-
color: var(--xeno-text);
|
|
127
|
-
}
|
|
128
|
-
.xeno-sidebar-item:focus-visible {
|
|
129
|
-
outline: none;
|
|
130
|
-
box-shadow:
|
|
131
|
-
0 0 0 2px var(--xeno-surface),
|
|
132
|
-
0 0 0 4px var(--xeno-border);
|
|
133
|
-
}
|
|
134
|
-
.xeno-sidebar-item[data-availability='disabled'] {
|
|
135
|
-
opacity: 0.4;
|
|
136
|
-
cursor: not-allowed;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/* ── The goo pill (the travelling hover surface) ──────────────────────────────────────
|
|
140
|
-
* ONE filled pill per group, painted behind the rows, that FLIES to whichever row you point at and
|
|
141
|
-
* squashes on the way — narrower and taller as it launches, back to true as it lands. That squash is
|
|
142
|
-
* the whole illusion: a rectangle that only slid would read as a scrollbar; one that deforms reads as
|
|
143
|
-
* something with mass. Adapted from One_div's gooey nav, in XENO ink: the pill is `--xeno-control`,
|
|
144
|
-
* the monochrome step above the panel, not an accent colour.
|
|
145
|
-
*
|
|
146
|
-
* Pure CSS, no measuring: the pill is the LAST child of the group, so every row keeps its natural
|
|
147
|
-
* `:nth-child` number and a hovered row reaches the pill with `~`. Travel is `row index x stride`, and
|
|
148
|
-
* the stride comes from the row tokens above, so it cannot drift out of step with the layout. */
|
|
149
|
-
.xeno-sidebar-goo {
|
|
150
|
-
position: absolute;
|
|
151
|
-
z-index: 0;
|
|
152
|
-
left: 0;
|
|
153
|
-
top: calc(var(--xeno-goo-row, 0) * (var(--xeno-sidebar-row) + var(--xeno-sidebar-row-gap)));
|
|
154
|
-
width: 100%;
|
|
155
|
-
height: var(--xeno-sidebar-row);
|
|
156
|
-
background: var(--xeno-control);
|
|
157
|
-
border-radius: var(--xeno-radius-md);
|
|
158
|
-
opacity: 0;
|
|
159
|
-
pointer-events: none;
|
|
160
|
-
transition:
|
|
161
|
-
top var(--xeno-dur-entrance, 240ms) var(--xeno-goo-ease),
|
|
162
|
-
opacity var(--xeno-dur, 0.14s) ease;
|
|
163
|
-
}
|
|
164
|
-
/* The pill RESTS on the selected row of its group (so the selection indicator is the same object
|
|
165
|
-
that flies), and is invisible in a group with no selection. */
|
|
166
|
-
.xeno-sidebar-item[data-selection='on'] ~ .xeno-sidebar-goo { opacity: 1; }
|
|
167
|
-
.xeno-sidebar-item[data-selection='on']:nth-child(1) ~ .xeno-sidebar-goo { --xeno-goo-row: 0; }
|
|
168
|
-
.xeno-sidebar-item[data-selection='on']:nth-child(2) ~ .xeno-sidebar-goo { --xeno-goo-row: 1; }
|
|
169
|
-
.xeno-sidebar-item[data-selection='on']:nth-child(3) ~ .xeno-sidebar-goo { --xeno-goo-row: 2; }
|
|
170
|
-
.xeno-sidebar-item[data-selection='on']:nth-child(4) ~ .xeno-sidebar-goo { --xeno-goo-row: 3; }
|
|
171
|
-
.xeno-sidebar-item[data-selection='on']:nth-child(5) ~ .xeno-sidebar-goo { --xeno-goo-row: 4; }
|
|
172
|
-
.xeno-sidebar-item[data-selection='on']:nth-child(6) ~ .xeno-sidebar-goo { --xeno-goo-row: 5; }
|
|
173
|
-
.xeno-sidebar-item[data-selection='on']:nth-child(7) ~ .xeno-sidebar-goo { --xeno-goo-row: 6; }
|
|
174
|
-
.xeno-sidebar-item[data-selection='on']:nth-child(8) ~ .xeno-sidebar-goo { --xeno-goo-row: 7; }
|
|
175
|
-
.xeno-sidebar-item[data-selection='on']:nth-child(9) ~ .xeno-sidebar-goo { --xeno-goo-row: 8; }
|
|
176
|
-
.xeno-sidebar-item[data-selection='on']:nth-child(10) ~ .xeno-sidebar-goo { --xeno-goo-row: 9; }
|
|
177
|
-
.xeno-sidebar-item[data-selection='on']:nth-child(11) ~ .xeno-sidebar-goo { --xeno-goo-row: 10; }
|
|
178
|
-
.xeno-sidebar-item[data-selection='on']:nth-child(12) ~ .xeno-sidebar-goo { --xeno-goo-row: 11; }
|
|
179
|
-
|
|
180
|
-
/* Hover takes it over. Same specificity as the rules above ON PURPOSE — these come later, so pointing
|
|
181
|
-
at a row always wins over the resting position, and letting go hands it back. */
|
|
182
|
-
.xeno-sidebar-item:hover ~ .xeno-sidebar-goo { opacity: 1; }
|
|
183
|
-
.xeno-sidebar-item:nth-child(1):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 0; animation: xeno-goo-1 var(--xeno-dur-entrance, 240ms) 1; }
|
|
184
|
-
.xeno-sidebar-item:nth-child(2):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 1; animation: xeno-goo-2 var(--xeno-dur-entrance, 240ms) 1; }
|
|
185
|
-
.xeno-sidebar-item:nth-child(3):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 2; animation: xeno-goo-3 var(--xeno-dur-entrance, 240ms) 1; }
|
|
186
|
-
.xeno-sidebar-item:nth-child(4):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 3; animation: xeno-goo-4 var(--xeno-dur-entrance, 240ms) 1; }
|
|
187
|
-
.xeno-sidebar-item:nth-child(5):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 4; animation: xeno-goo-5 var(--xeno-dur-entrance, 240ms) 1; }
|
|
188
|
-
.xeno-sidebar-item:nth-child(6):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 5; animation: xeno-goo-6 var(--xeno-dur-entrance, 240ms) 1; }
|
|
189
|
-
.xeno-sidebar-item:nth-child(7):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 6; animation: xeno-goo-7 var(--xeno-dur-entrance, 240ms) 1; }
|
|
190
|
-
.xeno-sidebar-item:nth-child(8):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 7; animation: xeno-goo-8 var(--xeno-dur-entrance, 240ms) 1; }
|
|
191
|
-
.xeno-sidebar-item:nth-child(9):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 8; animation: xeno-goo-9 var(--xeno-dur-entrance, 240ms) 1; }
|
|
192
|
-
.xeno-sidebar-item:nth-child(10):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 9; animation: xeno-goo-10 var(--xeno-dur-entrance, 240ms) 1; }
|
|
193
|
-
.xeno-sidebar-item:nth-child(11):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 10; animation: xeno-goo-11 var(--xeno-dur-entrance, 240ms) 1; }
|
|
194
|
-
.xeno-sidebar-item:nth-child(12):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 11; animation: xeno-goo-12 var(--xeno-dur-entrance, 240ms) 1; }
|
|
195
|
-
|
|
196
|
-
/* One keyframe track PER ROW, all identical. They have to be separate NAMES: re-pointing at another row
|
|
197
|
-
only swaps which rule matches, and a browser does not restart an animation whose name did not change —
|
|
198
|
-
so a single shared `xeno-goo` would squash once and then never again as you slide down the list. */
|
|
199
|
-
@keyframes xeno-goo-1 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
200
|
-
@keyframes xeno-goo-2 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
201
|
-
@keyframes xeno-goo-3 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
202
|
-
@keyframes xeno-goo-4 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
203
|
-
@keyframes xeno-goo-5 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
204
|
-
@keyframes xeno-goo-6 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
205
|
-
@keyframes xeno-goo-7 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
206
|
-
@keyframes xeno-goo-8 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
207
|
-
@keyframes xeno-goo-9 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
208
|
-
@keyframes xeno-goo-10 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
209
|
-
@keyframes xeno-goo-11 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
210
|
-
@keyframes xeno-goo-12 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
211
|
-
|
|
212
|
-
/* ── The XENO nav hover animation ─────────────────────────────────────────────────────
|
|
213
|
-
* The icon + label NUDGE right — the icon leads, the label follows a beat later (a small cascade), on
|
|
214
|
-
* the interaction easing (E2). There is no left rail: the goo pill above already marks both the hovered
|
|
215
|
-
* row and the active one, and a second marker on the same edge only competed with it. */
|
|
216
|
-
.xeno-sidebar-item-icon {
|
|
217
|
-
display: inline-flex;
|
|
218
|
-
flex: none;
|
|
219
|
-
color: var(--xeno-muted);
|
|
220
|
-
transition:
|
|
221
|
-
color var(--xeno-dur) ease,
|
|
222
|
-
transform var(--xeno-dur-entrance, 240ms) var(--xeno-ease-stagger, cubic-bezier(0.22, 0.7, 0.2, 1));
|
|
223
|
-
}
|
|
224
|
-
.xeno-sidebar-item-label {
|
|
225
|
-
min-width: 0;
|
|
226
|
-
overflow: hidden;
|
|
227
|
-
text-overflow: ellipsis;
|
|
228
|
-
white-space: nowrap;
|
|
229
|
-
transition: transform var(--xeno-dur-entrance, 240ms) var(--xeno-ease-stagger, cubic-bezier(0.22, 0.7, 0.2, 1));
|
|
230
|
-
}
|
|
231
|
-
.xeno-sidebar-item:hover .xeno-sidebar-item-icon,
|
|
232
|
-
.xeno-sidebar-item[data-selection='on'] .xeno-sidebar-item-icon {
|
|
233
|
-
color: var(--xeno-text);
|
|
234
|
-
}
|
|
235
|
-
.xeno-sidebar-item:hover .xeno-sidebar-item-icon {
|
|
236
|
-
transform: translateX(6px);
|
|
237
|
-
}
|
|
238
|
-
.xeno-sidebar-item:hover .xeno-sidebar-item-label {
|
|
239
|
-
transform: translateX(6px);
|
|
240
|
-
transition-delay: 45ms; /* label follows the icon — the cascade */
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/* Per-glyph icon animations are global now — see `icon-motion.css`, triggered by
|
|
244
|
-
* `.xeno-sidebar-item:hover .xeno-element[data-glyph]`. The nudge above stays sidebar-specific. */
|
|
245
|
-
|
|
246
|
-
/* ── Footer ──────────────────────────────────────────────────────────────────────── */
|
|
247
|
-
.xeno-sidebar-footer {
|
|
248
|
-
flex: none;
|
|
249
|
-
padding: 12px 16px;
|
|
250
|
-
border-top: 1px solid var(--xeno-border);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/* ── Backdrop (small screens only) ───────────────────────────────────────────────── */
|
|
254
|
-
.xeno-sidebar-backdrop {
|
|
255
|
-
position: fixed;
|
|
256
|
-
inset: 0;
|
|
257
|
-
z-index: 39;
|
|
258
|
-
background: rgba(0, 0, 0, 0.5); /* neutral scrim — hueless, like a shadow */
|
|
259
|
-
opacity: 0;
|
|
260
|
-
visibility: hidden;
|
|
261
|
-
transition:
|
|
262
|
-
opacity var(--xeno-dur) ease,
|
|
263
|
-
visibility var(--xeno-dur) ease;
|
|
264
|
-
}
|
|
265
|
-
.xeno-sidebar-backdrop[data-state='open'] {
|
|
266
|
-
opacity: 1;
|
|
267
|
-
visibility: visible;
|
|
268
|
-
}
|
|
269
|
-
@media (min-width: 900px) {
|
|
270
|
-
.xeno-sidebar-backdrop {
|
|
271
|
-
display: none;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
@media (prefers-reduced-motion: reduce) {
|
|
276
|
-
.xeno-sidebar,
|
|
277
|
-
.xeno-sidebar-backdrop,
|
|
278
|
-
.xeno-sidebar-item,
|
|
279
|
-
.xeno-sidebar-item-icon,
|
|
280
|
-
.xeno-sidebar-item-label,
|
|
281
|
-
.xeno-sidebar-goo {
|
|
282
|
-
transition: none;
|
|
283
|
-
}
|
|
284
|
-
/* The pill still MARKS the row it belongs to — it just gets there without flying or squashing. */
|
|
285
|
-
.xeno-sidebar-goo {
|
|
286
|
-
animation: none !important;
|
|
287
|
-
}
|
|
288
|
-
/* States still apply (the pill marks the row, colours change); motion does not — no nudge, no per-glyph play. */
|
|
289
|
-
.xeno-sidebar-item:hover .xeno-sidebar-item-icon,
|
|
290
|
-
.xeno-sidebar-item:hover .xeno-sidebar-item-label {
|
|
291
|
-
transform: none;
|
|
292
|
-
}
|
|
293
|
-
.xeno-sidebar-item-icon .xeno-element {
|
|
294
|
-
animation: none !important;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* Sidebar — the slide-in left navigation panel. Locked geometry: SQUARE corners, flush to the viewport
|
|
3
|
+
* edge; a fixed ~260px column on `var(--xeno-surface)`. CSS-first: the only state that drives appearance
|
|
4
|
+
* is `data-state="open|closed"` on the panel + backdrop. The only animated property is `transform`
|
|
5
|
+
* (translateX), guarded by reduced-motion. Monochrome — the scrim is a neutral, hueless black.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.xeno-sidebar {
|
|
9
|
+
/* One row's stride, in one place: the pill's travel is computed from these, so a row that changes
|
|
10
|
+
height can never leave the pill landing between two rows. */
|
|
11
|
+
--xeno-sidebar-row: 36px;
|
|
12
|
+
--xeno-sidebar-row-gap: 2px;
|
|
13
|
+
/* How much the goo pill misbehaves. The squash is deliberately restrained — enough deformation to
|
|
14
|
+
read as mass, not enough to look like jelly on a nav you use all day — and the travel easing
|
|
15
|
+
overshoots only slightly (1.12, against the 1.4 of the library's overshoot token). Turn these
|
|
16
|
+
three up together to get the full CodePen bounce back. */
|
|
17
|
+
--xeno-goo-squash-x: 0.95;
|
|
18
|
+
--xeno-goo-squash-y: 1.1;
|
|
19
|
+
--xeno-goo-ease: cubic-bezier(0.34, 1.12, 0.5, 1);
|
|
20
|
+
position: fixed;
|
|
21
|
+
inset-block: 0;
|
|
22
|
+
inset-inline-start: 0;
|
|
23
|
+
z-index: 40;
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
width: 260px;
|
|
27
|
+
max-width: 85vw;
|
|
28
|
+
height: 100%;
|
|
29
|
+
background: var(--xeno-surface);
|
|
30
|
+
border-inline-end: 1px solid var(--xeno-border);
|
|
31
|
+
border-radius: 0; /* locked: square, flush */
|
|
32
|
+
color: var(--xeno-text);
|
|
33
|
+
transform: translateX(0);
|
|
34
|
+
transition: transform var(--xeno-dur) var(--xeno-ease);
|
|
35
|
+
will-change: transform;
|
|
36
|
+
}
|
|
37
|
+
.xeno-sidebar[data-state='closed'] {
|
|
38
|
+
transform: translateX(-100%);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* ── Header (brand + search) ─────────────────────────────────────────────────────── */
|
|
42
|
+
.xeno-sidebar-header {
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
gap: 12px;
|
|
46
|
+
flex: none;
|
|
47
|
+
padding: 16px;
|
|
48
|
+
border-bottom: 1px solid var(--xeno-border);
|
|
49
|
+
}
|
|
50
|
+
.xeno-sidebar-brand {
|
|
51
|
+
font-size: 15px;
|
|
52
|
+
font-weight: 600;
|
|
53
|
+
line-height: 1;
|
|
54
|
+
letter-spacing: 0.04em;
|
|
55
|
+
color: var(--xeno-text);
|
|
56
|
+
}
|
|
57
|
+
/* Make the shared TextInput fill the header width. */
|
|
58
|
+
.xeno-sidebar-search {
|
|
59
|
+
display: flex;
|
|
60
|
+
width: 100%;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ── Nav (scrollable list of rows) ───────────────────────────────────────────────── */
|
|
64
|
+
.xeno-sidebar-nav {
|
|
65
|
+
flex: 1;
|
|
66
|
+
min-height: 0;
|
|
67
|
+
overflow-y: auto;
|
|
68
|
+
padding: 8px;
|
|
69
|
+
}
|
|
70
|
+
.xeno-sidebar-section {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
gap: var(--xeno-sidebar-row-gap);
|
|
74
|
+
}
|
|
75
|
+
/* The rows of one group + the pill that backs them. `position: relative` makes this the pill's
|
|
76
|
+
containing block, so its travel is measured from the first row of THIS group — which is why the
|
|
77
|
+
heading can sit outside and the trick survives sections of different shapes. */
|
|
78
|
+
.xeno-sidebar-items {
|
|
79
|
+
position: relative;
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
gap: var(--xeno-sidebar-row-gap);
|
|
83
|
+
}
|
|
84
|
+
.xeno-sidebar-section + .xeno-sidebar-section {
|
|
85
|
+
margin-top: 8px;
|
|
86
|
+
}
|
|
87
|
+
.xeno-sidebar-heading {
|
|
88
|
+
padding: 10px 10px 4px;
|
|
89
|
+
font-size: 11px;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
line-height: 1;
|
|
92
|
+
letter-spacing: 0.08em;
|
|
93
|
+
text-transform: uppercase;
|
|
94
|
+
color: var(--xeno-muted);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* ── Row (self-contained; NOT ListRow) ───────────────────────────────────────────── */
|
|
98
|
+
.xeno-sidebar-item {
|
|
99
|
+
position: relative;
|
|
100
|
+
z-index: 1; /* over the pill, which is painted behind the whole group */
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
gap: 10px;
|
|
104
|
+
width: 100%;
|
|
105
|
+
height: var(--xeno-sidebar-row);
|
|
106
|
+
padding: 0 10px;
|
|
107
|
+
font-family: inherit;
|
|
108
|
+
font-size: 14px;
|
|
109
|
+
font-weight: 500;
|
|
110
|
+
line-height: 1;
|
|
111
|
+
text-align: left;
|
|
112
|
+
color: var(--xeno-muted);
|
|
113
|
+
background: transparent;
|
|
114
|
+
border: 1px solid transparent;
|
|
115
|
+
border-radius: var(--xeno-radius-md);
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
user-select: none;
|
|
118
|
+
transition:
|
|
119
|
+
background var(--xeno-dur) ease,
|
|
120
|
+
color var(--xeno-dur) ease;
|
|
121
|
+
}
|
|
122
|
+
/* Neither hover nor selection paints its OWN background any more — the travelling pill below is the
|
|
123
|
+
single filled surface for the whole group. Two of them would read as two selections. */
|
|
124
|
+
.xeno-sidebar-item:hover,
|
|
125
|
+
.xeno-sidebar-item[data-selection='on'] {
|
|
126
|
+
color: var(--xeno-text);
|
|
127
|
+
}
|
|
128
|
+
.xeno-sidebar-item:focus-visible {
|
|
129
|
+
outline: none;
|
|
130
|
+
box-shadow:
|
|
131
|
+
0 0 0 2px var(--xeno-surface),
|
|
132
|
+
0 0 0 4px var(--xeno-border);
|
|
133
|
+
}
|
|
134
|
+
.xeno-sidebar-item[data-availability='disabled'] {
|
|
135
|
+
opacity: 0.4;
|
|
136
|
+
cursor: not-allowed;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* ── The goo pill (the travelling hover surface) ──────────────────────────────────────
|
|
140
|
+
* ONE filled pill per group, painted behind the rows, that FLIES to whichever row you point at and
|
|
141
|
+
* squashes on the way — narrower and taller as it launches, back to true as it lands. That squash is
|
|
142
|
+
* the whole illusion: a rectangle that only slid would read as a scrollbar; one that deforms reads as
|
|
143
|
+
* something with mass. Adapted from One_div's gooey nav, in XENO ink: the pill is `--xeno-control`,
|
|
144
|
+
* the monochrome step above the panel, not an accent colour.
|
|
145
|
+
*
|
|
146
|
+
* Pure CSS, no measuring: the pill is the LAST child of the group, so every row keeps its natural
|
|
147
|
+
* `:nth-child` number and a hovered row reaches the pill with `~`. Travel is `row index x stride`, and
|
|
148
|
+
* the stride comes from the row tokens above, so it cannot drift out of step with the layout. */
|
|
149
|
+
.xeno-sidebar-goo {
|
|
150
|
+
position: absolute;
|
|
151
|
+
z-index: 0;
|
|
152
|
+
left: 0;
|
|
153
|
+
top: calc(var(--xeno-goo-row, 0) * (var(--xeno-sidebar-row) + var(--xeno-sidebar-row-gap)));
|
|
154
|
+
width: 100%;
|
|
155
|
+
height: var(--xeno-sidebar-row);
|
|
156
|
+
background: var(--xeno-control);
|
|
157
|
+
border-radius: var(--xeno-radius-md);
|
|
158
|
+
opacity: 0;
|
|
159
|
+
pointer-events: none;
|
|
160
|
+
transition:
|
|
161
|
+
top var(--xeno-dur-entrance, 240ms) var(--xeno-goo-ease),
|
|
162
|
+
opacity var(--xeno-dur, 0.14s) ease;
|
|
163
|
+
}
|
|
164
|
+
/* The pill RESTS on the selected row of its group (so the selection indicator is the same object
|
|
165
|
+
that flies), and is invisible in a group with no selection. */
|
|
166
|
+
.xeno-sidebar-item[data-selection='on'] ~ .xeno-sidebar-goo { opacity: 1; }
|
|
167
|
+
.xeno-sidebar-item[data-selection='on']:nth-child(1) ~ .xeno-sidebar-goo { --xeno-goo-row: 0; }
|
|
168
|
+
.xeno-sidebar-item[data-selection='on']:nth-child(2) ~ .xeno-sidebar-goo { --xeno-goo-row: 1; }
|
|
169
|
+
.xeno-sidebar-item[data-selection='on']:nth-child(3) ~ .xeno-sidebar-goo { --xeno-goo-row: 2; }
|
|
170
|
+
.xeno-sidebar-item[data-selection='on']:nth-child(4) ~ .xeno-sidebar-goo { --xeno-goo-row: 3; }
|
|
171
|
+
.xeno-sidebar-item[data-selection='on']:nth-child(5) ~ .xeno-sidebar-goo { --xeno-goo-row: 4; }
|
|
172
|
+
.xeno-sidebar-item[data-selection='on']:nth-child(6) ~ .xeno-sidebar-goo { --xeno-goo-row: 5; }
|
|
173
|
+
.xeno-sidebar-item[data-selection='on']:nth-child(7) ~ .xeno-sidebar-goo { --xeno-goo-row: 6; }
|
|
174
|
+
.xeno-sidebar-item[data-selection='on']:nth-child(8) ~ .xeno-sidebar-goo { --xeno-goo-row: 7; }
|
|
175
|
+
.xeno-sidebar-item[data-selection='on']:nth-child(9) ~ .xeno-sidebar-goo { --xeno-goo-row: 8; }
|
|
176
|
+
.xeno-sidebar-item[data-selection='on']:nth-child(10) ~ .xeno-sidebar-goo { --xeno-goo-row: 9; }
|
|
177
|
+
.xeno-sidebar-item[data-selection='on']:nth-child(11) ~ .xeno-sidebar-goo { --xeno-goo-row: 10; }
|
|
178
|
+
.xeno-sidebar-item[data-selection='on']:nth-child(12) ~ .xeno-sidebar-goo { --xeno-goo-row: 11; }
|
|
179
|
+
|
|
180
|
+
/* Hover takes it over. Same specificity as the rules above ON PURPOSE — these come later, so pointing
|
|
181
|
+
at a row always wins over the resting position, and letting go hands it back. */
|
|
182
|
+
.xeno-sidebar-item:hover ~ .xeno-sidebar-goo { opacity: 1; }
|
|
183
|
+
.xeno-sidebar-item:nth-child(1):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 0; animation: xeno-goo-1 var(--xeno-dur-entrance, 240ms) 1; }
|
|
184
|
+
.xeno-sidebar-item:nth-child(2):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 1; animation: xeno-goo-2 var(--xeno-dur-entrance, 240ms) 1; }
|
|
185
|
+
.xeno-sidebar-item:nth-child(3):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 2; animation: xeno-goo-3 var(--xeno-dur-entrance, 240ms) 1; }
|
|
186
|
+
.xeno-sidebar-item:nth-child(4):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 3; animation: xeno-goo-4 var(--xeno-dur-entrance, 240ms) 1; }
|
|
187
|
+
.xeno-sidebar-item:nth-child(5):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 4; animation: xeno-goo-5 var(--xeno-dur-entrance, 240ms) 1; }
|
|
188
|
+
.xeno-sidebar-item:nth-child(6):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 5; animation: xeno-goo-6 var(--xeno-dur-entrance, 240ms) 1; }
|
|
189
|
+
.xeno-sidebar-item:nth-child(7):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 6; animation: xeno-goo-7 var(--xeno-dur-entrance, 240ms) 1; }
|
|
190
|
+
.xeno-sidebar-item:nth-child(8):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 7; animation: xeno-goo-8 var(--xeno-dur-entrance, 240ms) 1; }
|
|
191
|
+
.xeno-sidebar-item:nth-child(9):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 8; animation: xeno-goo-9 var(--xeno-dur-entrance, 240ms) 1; }
|
|
192
|
+
.xeno-sidebar-item:nth-child(10):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 9; animation: xeno-goo-10 var(--xeno-dur-entrance, 240ms) 1; }
|
|
193
|
+
.xeno-sidebar-item:nth-child(11):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 10; animation: xeno-goo-11 var(--xeno-dur-entrance, 240ms) 1; }
|
|
194
|
+
.xeno-sidebar-item:nth-child(12):hover ~ .xeno-sidebar-goo { --xeno-goo-row: 11; animation: xeno-goo-12 var(--xeno-dur-entrance, 240ms) 1; }
|
|
195
|
+
|
|
196
|
+
/* One keyframe track PER ROW, all identical. They have to be separate NAMES: re-pointing at another row
|
|
197
|
+
only swaps which rule matches, and a browser does not restart an animation whose name did not change —
|
|
198
|
+
so a single shared `xeno-goo` would squash once and then never again as you slide down the list. */
|
|
199
|
+
@keyframes xeno-goo-1 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
200
|
+
@keyframes xeno-goo-2 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
201
|
+
@keyframes xeno-goo-3 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
202
|
+
@keyframes xeno-goo-4 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
203
|
+
@keyframes xeno-goo-5 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
204
|
+
@keyframes xeno-goo-6 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
205
|
+
@keyframes xeno-goo-7 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
206
|
+
@keyframes xeno-goo-8 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
207
|
+
@keyframes xeno-goo-9 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
208
|
+
@keyframes xeno-goo-10 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
209
|
+
@keyframes xeno-goo-11 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
210
|
+
@keyframes xeno-goo-12 { 0% { scale: 1 1; } 50% { scale: var(--xeno-goo-squash-x) var(--xeno-goo-squash-y); } 100% { scale: 1 1; } }
|
|
211
|
+
|
|
212
|
+
/* ── The XENO nav hover animation ─────────────────────────────────────────────────────
|
|
213
|
+
* The icon + label NUDGE right — the icon leads, the label follows a beat later (a small cascade), on
|
|
214
|
+
* the interaction easing (E2). There is no left rail: the goo pill above already marks both the hovered
|
|
215
|
+
* row and the active one, and a second marker on the same edge only competed with it. */
|
|
216
|
+
.xeno-sidebar-item-icon {
|
|
217
|
+
display: inline-flex;
|
|
218
|
+
flex: none;
|
|
219
|
+
color: var(--xeno-muted);
|
|
220
|
+
transition:
|
|
221
|
+
color var(--xeno-dur) ease,
|
|
222
|
+
transform var(--xeno-dur-entrance, 240ms) var(--xeno-ease-stagger, cubic-bezier(0.22, 0.7, 0.2, 1));
|
|
223
|
+
}
|
|
224
|
+
.xeno-sidebar-item-label {
|
|
225
|
+
min-width: 0;
|
|
226
|
+
overflow: hidden;
|
|
227
|
+
text-overflow: ellipsis;
|
|
228
|
+
white-space: nowrap;
|
|
229
|
+
transition: transform var(--xeno-dur-entrance, 240ms) var(--xeno-ease-stagger, cubic-bezier(0.22, 0.7, 0.2, 1));
|
|
230
|
+
}
|
|
231
|
+
.xeno-sidebar-item:hover .xeno-sidebar-item-icon,
|
|
232
|
+
.xeno-sidebar-item[data-selection='on'] .xeno-sidebar-item-icon {
|
|
233
|
+
color: var(--xeno-text);
|
|
234
|
+
}
|
|
235
|
+
.xeno-sidebar-item:hover .xeno-sidebar-item-icon {
|
|
236
|
+
transform: translateX(6px);
|
|
237
|
+
}
|
|
238
|
+
.xeno-sidebar-item:hover .xeno-sidebar-item-label {
|
|
239
|
+
transform: translateX(6px);
|
|
240
|
+
transition-delay: 45ms; /* label follows the icon — the cascade */
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Per-glyph icon animations are global now — see `icon-motion.css`, triggered by
|
|
244
|
+
* `.xeno-sidebar-item:hover .xeno-element[data-glyph]`. The nudge above stays sidebar-specific. */
|
|
245
|
+
|
|
246
|
+
/* ── Footer ──────────────────────────────────────────────────────────────────────── */
|
|
247
|
+
.xeno-sidebar-footer {
|
|
248
|
+
flex: none;
|
|
249
|
+
padding: 12px 16px;
|
|
250
|
+
border-top: 1px solid var(--xeno-border);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* ── Backdrop (small screens only) ───────────────────────────────────────────────── */
|
|
254
|
+
.xeno-sidebar-backdrop {
|
|
255
|
+
position: fixed;
|
|
256
|
+
inset: 0;
|
|
257
|
+
z-index: 39;
|
|
258
|
+
background: rgba(0, 0, 0, 0.5); /* neutral scrim — hueless, like a shadow */
|
|
259
|
+
opacity: 0;
|
|
260
|
+
visibility: hidden;
|
|
261
|
+
transition:
|
|
262
|
+
opacity var(--xeno-dur) ease,
|
|
263
|
+
visibility var(--xeno-dur) ease;
|
|
264
|
+
}
|
|
265
|
+
.xeno-sidebar-backdrop[data-state='open'] {
|
|
266
|
+
opacity: 1;
|
|
267
|
+
visibility: visible;
|
|
268
|
+
}
|
|
269
|
+
@media (min-width: 900px) {
|
|
270
|
+
.xeno-sidebar-backdrop {
|
|
271
|
+
display: none;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@media (prefers-reduced-motion: reduce) {
|
|
276
|
+
.xeno-sidebar,
|
|
277
|
+
.xeno-sidebar-backdrop,
|
|
278
|
+
.xeno-sidebar-item,
|
|
279
|
+
.xeno-sidebar-item-icon,
|
|
280
|
+
.xeno-sidebar-item-label,
|
|
281
|
+
.xeno-sidebar-goo {
|
|
282
|
+
transition: none;
|
|
283
|
+
}
|
|
284
|
+
/* The pill still MARKS the row it belongs to — it just gets there without flying or squashing. */
|
|
285
|
+
.xeno-sidebar-goo {
|
|
286
|
+
animation: none !important;
|
|
287
|
+
}
|
|
288
|
+
/* States still apply (the pill marks the row, colours change); motion does not — no nudge, no per-glyph play. */
|
|
289
|
+
.xeno-sidebar-item:hover .xeno-sidebar-item-icon,
|
|
290
|
+
.xeno-sidebar-item:hover .xeno-sidebar-item-label {
|
|
291
|
+
transform: none;
|
|
292
|
+
}
|
|
293
|
+
.xeno-sidebar-item-icon .xeno-element {
|
|
294
|
+
animation: none !important;
|
|
295
|
+
}
|
|
296
|
+
}
|