PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.13
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.13
1.1.13 1.1.12 1.1.11 1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 All 37 releases
desktop-mode / assets / css / openstation-layout.css

openstation-layout.css in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.13, at assets/css/openstation-layout.css

751 lines 22.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * OpenStation — the constellation.
3 *
4 * The flyout that fans a menu's submenu out of its tile on hover, on
5 * every rail in every layout. Body-attached (it has to escape the
6 * dock's stacking context and its `overflow`), so its selectors are
7 * rooted on `.os-constellation` and reach it wherever the dock is
8 * parked.
9 *
10 * Colour discipline, per the palette rule: every declaration here
11 * reads `var( --token, <literal> )`, and every literal is a plain
12 * value that stands on its own if `variables.css` never loads. The
13 * mesh appears in exactly two places — the hovered/focused row and
14 * the head's icon halo — because those are the moments the panel is
15 * answering the user. Everywhere else is Obsidian.
16 */
17
18 /*
19 * The dock tooltip stands down while a flyout is open. The panel's
20 * head carries the same label, larger and attached to the thing it
21 * names; the tooltip on top of it is a second hover surface saying
22 * the same word twice.
23 */
24 body.os-constellation-open .os-dock__tooltip {
25 opacity: 0;
26 pointer-events: none;
27 }
28
29 .os-constellation {
30 position: fixed;
31 font-family: var( --os-cn-font, inherit );
32 z-index: var( --os-cn-z, 2147483000 );
33 /*
34 * `--os-cn-shift` is written by the clamp when the panel would
35 * have overflowed a viewport edge; the beam compensates with
36 * `--os-cn-beam-x` so it stays pointed at the tile. Both default
37 * to 0, which is the un-clamped case.
38 */
39 transform: translate(
40 calc( -50% + var( --os-cn-shift, 0px ) ),
41 -100%
42 )
43 scale( 0.94 );
44 transform-origin: bottom center;
45 opacity: 0;
46 pointer-events: none;
47 transition:
48 transform var( --os-ui-motion-slow, 340ms )
49 var( --os-ui-ease-spring, cubic-bezier( 0.32, 1.5, 0.55, 1 ) ),
50 opacity var( --os-ui-motion-fast, 140ms ) ease-out;
51 }
52
53 .os-constellation.os-constellation--open {
54 transform: translate( calc( -50% + var( --os-cn-shift, 0px ) ), -100% )
55 scale( 1 );
56 opacity: 1;
57 pointer-events: auto;
58 }
59
60 /*
61 * The exit, and it is NOT the entrance in reverse.
62 *
63 * Arriving is an event and gets a spring that overshoots. Leaving is
64 * the user having already moved on: it eases IN (accelerating away
65 * rather than settling), runs in under half the time, and — because
66 * `transform-origin` is `bottom center`, where the beam meets the
67 * tile — the shrink plus the few pixels of downward travel read as
68 * the panel dropping back into the rail it came out of.
69 *
70 * `pointer-events: none` from the first frame. A panel that is
71 * visibly leaving must not still be clickable, or a fast pointer
72 * lands a row the user has already dismissed.
73 */
74 .os-constellation.os-constellation--closing {
75 transform: translate(
76 calc( -50% + var( --os-cn-shift, 0px ) ),
77 calc( -100% + 8px )
78 )
79 scale( 0.96 );
80 opacity: 0;
81 pointer-events: none;
82 transition:
83 transform 160ms var( --os-ui-ease-in, cubic-bezier( 0.4, 0, 1, 1 ) ),
84 opacity 140ms var( --os-ui-ease-in, cubic-bezier( 0.4, 0, 1, 1 ) );
85 }
86
87 /*
88 * The panel leaves as ONE object. Without this the rows revert to
89 * their pre-entrance state and each plays its own staggered exit
90 * inside a panel that is itself shrinking — two motions at two
91 * speeds, which reads as the menu coming apart rather than closing.
92 */
93 .os-constellation--closing .os-constellation__row {
94 opacity: 1;
95 transform: none;
96 transition: none;
97 }
98
99 /*
100 * The beam goes first, and faster. It is the thread to the tile, so
101 * cutting it a beat before the panel lands sells the panel as
102 * falling rather than fading.
103 */
104 .os-constellation--closing .os-constellation__beam {
105 opacity: 0;
106 transition: opacity 90ms ease-in;
107 }
108
109 /*
110 * A retiring panel never paints over a live one.
111 *
112 * Moving along the rail leaves two panels on screen at once — the one
113 * you left finishing its dismissal above its own tile, the one you
114 * arrived at rising above its. Adjacent dock tiles are ~46px apart
115 * and a panel is 230px+ wide, so they overlap heavily, and without an
116 * explicit order the outgoing one wins on source order and fades out
117 * ON TOP of the menu the user is actually looking at.
118 */
119 .os-constellation--closing {
120 z-index: calc( var( --os-cn-z, 2147483000 ) - 1 );
121 }
122
123 /* ---- Fanning sideways -------------------------------------------- */
124
125 /*
126 * Everything above assumes the rail is along the bottom: the panel
127 * hangs off the top of its tile, is centred on it horizontally, and
128 * drops back down into the rail as it leaves. A dock on the left or
129 * the right needs the same three statements rotated a quarter turn.
130 *
131 * `data-os-cn-side` is written by `position()` in
132 * `src/dock-constellation/index.ts` and names where the PANEL is
133 * relative to its tile, not where the rail is: a left-hand rail fans
134 * its panels out to the `right`.
135 *
136 * Two things change per side and nothing else does. The panel is
137 * pinned by its facing edge instead of its bottom one, so the
138 * translate loses its vertical half entirely: `top` is an absolute
139 * position the JS already clamped, not an offset from the tile. And
140 * `transform-origin` swaps which axis is pinned and which is centred.
141 *
142 * ## The cross axis is CENTRED, and that is the whole animation
143 *
144 * The entrance is a 0.94 → 1 scale and nothing else, so what the eye
145 * reads is entirely down to where the origin is. Above a bottom rail
146 * it is `bottom center`: the growth along the pinned axis all goes
147 * one way (the panel's top edge rises out of the dock) and the growth
148 * across it is symmetrical, ±7px that cancel. The panel does not
149 * appear to travel at all — it opens.
150 *
151 * Anchoring the cross axis to `--os-cn-beam-y` instead — where the
152 * beam meets the panel, which sounds like the right place for a menu
153 * to grow out of — breaks exactly that. On the first tile of a rail
154 * the beam sits 20px down a 400px panel, which is a corner in all
155 * but name, and growth off a corner all goes one way: measured, the
156 * top edge does not move at all while the bottom and right edges
157 * both travel. It expands down-and-right where the bottom rail
158 * opens evenly.
159 *
160 * So the cross axis stays `center` here too. The beam still lands on
161 * the tile — it is positioned from `--os-cn-beam-y` independently —
162 * but it does not get to drag the origin with it.
163 */
164 /*
165 * The properties below are PHYSICAL (`left` / `right`, not
166 * `inset-inline-*`), because the thing they follow is physical: the
167 * screen edge the dock is parked on. `dockPlacement` is `left` or
168 * `right` in both directions of text.
169 */
170 .os-constellation[ data-os-cn-side='right' ] {
171 transform: translate( 0, 0 ) scale( 0.94 );
172 transform-origin: left center;
173 }
174 .os-constellation[ data-os-cn-side='right' ].os-constellation--open {
175 transform: translate( 0, 0 ) scale( 1 );
176 }
177 .os-constellation[ data-os-cn-side='right' ].os-constellation--closing {
178 transform: translate( -8px, 0 ) scale( 0.96 );
179 }
180
181 .os-constellation[ data-os-cn-side='left' ] {
182 transform: translate( -100%, 0 ) scale( 0.94 );
183 transform-origin: right center;
184 }
185 .os-constellation[ data-os-cn-side='left' ].os-constellation--open {
186 transform: translate( -100%, 0 ) scale( 1 );
187 }
188 .os-constellation[ data-os-cn-side='left' ].os-constellation--closing {
189 transform: translate( calc( -100% + 8px ), 0 ) scale( 0.96 );
190 }
191
192 /* The panel body. */
193 .os-constellation__surface {
194 position: relative;
195 display: flex;
196 flex-direction: column;
197 min-width: 232px;
198 max-width: min( 340px, calc( 100vw - 24px ) );
199 /*
200 * The vertical clamp. `--os-cn-max-h` is written by the JS on
201 * every placement: the distance from this panel's bottom edge up
202 * to the top of the viewport. The panel cannot be nudged
203 * downwards to fit — that would push it over the dock — so it is
204 * capped instead, and the group below takes the scroll.
205 */
206 max-height: var( --os-cn-max-h, min( 78vh, 620px ) );
207 /*
208 * The surface itself never scrolls. A long menu (WooCommerce's
209 * fifteen children, a CPT with every taxonomy under it) puts the
210 * scroll on its own group instead, which keeps the head and the
211 * new-window row pinned AND keeps this element a stable
212 * containing block for the spotlight + edge overlays — an
213 * absolutely-positioned child of a scroll container scrolls with
214 * the content, so the hairline would slide off the top.
215 */
216 padding: 6px;
217 border-radius: var( --os-cn-radius, 14px );
218 background-color: var( --os-cn-surface, rgba( 26, 23, 33, 0.82 ) );
219 backdrop-filter: var( --os-cn-backdrop, blur( 28px ) saturate( 170% ) );
220 -webkit-backdrop-filter: var( --os-cn-backdrop, blur( 28px ) saturate( 170% ) );
221 box-shadow: var(
222 --os-cn-shadow,
223 0 24px 64px rgba( 0, 0, 0, 0.62 ),
224 0 2px 8px rgba( 0, 0, 0, 0.4 )
225 );
226 color: var( --os-cn-fg, #fffbff );
227 }
228
229 /*
230 * Cursor spotlight. The JS writes `--os-cn-x` / `--os-cn-y` on
231 * pointermove; this paints a soft bloom there so the panel lights up
232 * under the pointer instead of sitting inert. Additive (`screen`) so
233 * it brightens the surface without washing the text.
234 *
235 * `::before`, not `::after`, so it sits BELOW the rows in paint
236 * order — the rows are positioned, and a spotlight painted over the
237 * mesh of a hovered row would flatten exactly the moment it is
238 * meant to reward.
239 */
240 .os-constellation__surface::before {
241 content: "";
242 position: absolute;
243 inset: 0;
244 border-radius: inherit;
245 /* `--os-cn-spotlight: none` for a flat panel. */
246 background: var(
247 --os-cn-spotlight,
248 radial-gradient(
249 220px circle at var( --os-cn-x, 50% ) var( --os-cn-y, 0% ),
250 hsl( var( --os-cn-hue, 300 ) 90% 70% / 0.14 ) 0%,
251 transparent 70%
252 )
253 );
254 mix-blend-mode: screen;
255 pointer-events: none;
256 }
257
258 /*
259 * The iridescent hairline. Painted as a gradient on a padding-box
260 * mask rather than as a `border-color`, because a border cannot hold
261 * a gradient and `border-image` loses the corner radius — the same
262 * trick `holoEdge` uses inside the component kit.
263 */
264 .os-constellation__surface::after {
265 content: "";
266 position: absolute;
267 inset: 0;
268 border-radius: inherit;
269 padding: 1px;
270 background: var(
271 --os-ui-holo-edge-quiet,
272 linear-gradient(
273 124deg,
274 rgba( 154, 242, 255, 0.22 ) 0%,
275 rgba( 236, 155, 255, 0.28 ) 38%,
276 rgba( 242, 82, 252, 0.22 ) 62%,
277 rgba( 159, 152, 255, 0.2 ) 100%
278 )
279 );
280 -webkit-mask:
281 linear-gradient( #000 0 0 ) content-box,
282 linear-gradient( #000 0 0 );
283 mask:
284 linear-gradient( #000 0 0 ) content-box,
285 linear-gradient( #000 0 0 );
286 -webkit-mask-composite: xor;
287 mask-composite: exclude;
288 pointer-events: none;
289 }
290
291 /*
292 * The sheen. One diagonal band of light crossing the panel, once, as
293 * it opens.
294 *
295 * An element rather than a pseudo, and a child of the ROOT rather
296 * than of the surface: the surface has already spent `::before` on
297 * the spotlight and `::after` on the edge mask (the pseudo budget is
298 * exactly two), and hanging it off the root is what lets it sweep
299 * over the rows instead of under them.
300 */
301 .os-constellation__sheen {
302 position: absolute;
303 inset: 0;
304 border-radius: var( --os-cn-radius, 14px );
305 overflow: hidden;
306 pointer-events: none;
307 opacity: 0;
308 }
309
310 .os-constellation--open .os-constellation__sheen {
311 animation: os-cn-sheen 900ms var( --os-ui-ease-out, ease-out ) 60ms 1;
312 }
313
314 .os-constellation__sheen::before {
315 content: "";
316 position: absolute;
317 inset: -40% -120%;
318 /* `--os-cn-sheen: none` for a panel that opens without the sweep. */
319 background: var(
320 --os-cn-sheen,
321 linear-gradient(
322 104deg,
323 transparent 42%,
324 rgba( 255, 253, 255, 0.42 ) 50%,
325 transparent 58%
326 )
327 );
328 transform: translateX( -60% );
329 }
330
331 @keyframes os-cn-sheen {
332 0% {
333 opacity: 0;
334 }
335 18% {
336 opacity: 1;
337 }
338 100% {
339 opacity: 0;
340 transform: translateX( 120% );
341 }
342 }
343
344 /* ---- The beam ---------------------------------------------------- */
345
346 /*
347 * The thread from the panel's underside down to the tile it belongs
348 * to. Its only real job is the clamped case: once the panel has been
349 * nudged sideways to stay on screen, the beam is the only thing left
350 * saying which tile it came out of.
351 */
352 .os-constellation__beam {
353 position: absolute;
354 inset-inline-start: 50%;
355 top: 100%;
356 width: 2px;
357 height: 14px;
358 transform: translateX(
359 calc( -50% + var( --os-cn-beam-x, 0px ) )
360 );
361 background: linear-gradient(
362 to bottom,
363 var( --os-cn-beam, rgba( 217, 46, 227, 0.8 ) ) 0%,
364 transparent 100%
365 );
366 pointer-events: none;
367 opacity: 0;
368 transition: opacity var( --os-ui-motion-fast, 140ms ) ease-out 80ms;
369 }
370
371 .os-constellation--open .os-constellation__beam {
372 opacity: 1;
373 }
374
375 /*
376 * The same thread, turned. It leaves the panel by its facing edge and
377 * runs back to the tile, so beside a rail it is 14px wide and 2px
378 * tall rather than the other way round, and the fade runs toward the
379 * tile in both cases.
380 *
381 * `--os-cn-beam-y` is where the tile is, measured down from the top
382 * of the panel — not a correction applied to a centred beam, which is
383 * what the bottom rail's `--os-cn-beam-x` is. Beside a rail the panel
384 * is top-aligned with its tile rather than centred on it, so the
385 * beam's position IS the anchoring, and a panel that had to be
386 * clamped away from a viewport edge still has a thread landing on the
387 * tile it belongs to.
388 */
389 .os-constellation[ data-os-cn-side='right' ] .os-constellation__beam,
390 .os-constellation[ data-os-cn-side='left' ] .os-constellation__beam {
391 top: var( --os-cn-beam-y, 50% );
392 width: 14px;
393 height: 2px;
394 transform: translateY( -50% );
395 }
396 .os-constellation[ data-os-cn-side='right' ] .os-constellation__beam {
397 inset-inline-start: auto;
398 left: auto;
399 right: 100%;
400 background: linear-gradient(
401 to left,
402 var( --os-cn-beam, rgba( 217, 46, 227, 0.8 ) ) 0%,
403 transparent 100%
404 );
405 }
406 .os-constellation[ data-os-cn-side='left' ] .os-constellation__beam {
407 inset-inline-start: auto;
408 right: auto;
409 left: 100%;
410 background: linear-gradient(
411 to right,
412 var( --os-cn-beam, rgba( 217, 46, 227, 0.8 ) ) 0%,
413 transparent 100%
414 );
415 }
416
417 /* ---- Rows -------------------------------------------------------- */
418
419 .os-constellation__row {
420 position: relative;
421 display: flex;
422 align-items: center;
423 gap: 10px;
424 width: 100%;
425 /* The head and the new-window row are direct flex children of a
426 * height-capped surface; without this they would be squashed
427 * before the scrollable group in the middle gave way. */
428 flex: 0 0 auto;
429 padding: 8px 10px;
430 border: none;
431 border-radius: var( --os-cn-row-radius, 9px );
432 background: transparent;
433 color: inherit;
434 font: inherit;
435 font-size: var( --os-cn-font-size, 13px );
436 line-height: 1.3;
437 text-align: start;
438 cursor: pointer;
439 /*
440 * Staggered entrance. `--os-cn-row` is the row's index, written by
441 * the JS, so the list unfurls top-down instead of appearing all at
442 * once. Capped at 8 legs of delay: a 30-item submenu that took
443 * 30 × 26ms to finish would read as sluggish, not as choreography.
444 */
445 opacity: 0;
446 transform: translateY( 6px );
447 transition:
448 background-color var( --os-ui-motion-fast, 140ms ) ease-out,
449 color var( --os-ui-motion-fast, 140ms ) ease-out,
450 opacity 220ms ease-out,
451 transform 220ms
452 var( --os-ui-ease-spring, cubic-bezier( 0.32, 1.5, 0.55, 1 ) );
453 transition-delay: 0s, 0s,
454 calc( min( var( --os-cn-row, 0 ), 8 ) * 26ms ),
455 calc( min( var( --os-cn-row, 0 ), 8 ) * 26ms );
456 }
457
458 .os-constellation--open .os-constellation__row {
459 opacity: 1;
460 transform: translateY( 0 );
461 }
462
463 .os-constellation__row:hover,
464 .os-constellation__row:focus-visible {
465 outline: none;
466 /*
467 * The identity moment. The row under the pointer is "selected",
468 * which is exactly the state the brand reserves the mesh for — so
469 * this is where the panel spends it, and the ink flips to Void
470 * because every mesh in the brand is a LIGHT surface.
471 */
472 background-image: var(
473 --os-cn-row-fill,
474 var( --os-ui-holo-fill, linear-gradient( 124deg, #afa2e8, #c3b8ef ) )
475 );
476 color: var( --os-cn-row-ink, var( --os-ui-holo-ink, #0c0b0f ) );
477 }
478
479 .os-constellation__row:focus-visible {
480 box-shadow: var(
481 --os-ui-focus-ring,
482 0 0 0 2px rgba( 12, 11, 15, 0.9 ),
483 0 0 0 4px #f252fc
484 );
485 }
486
487 .os-constellation__row:active {
488 transform: scale( 0.985 );
489 }
490
491 .os-constellation__row-label {
492 flex: 1 1 auto;
493 min-width: 0;
494 overflow: hidden;
495 text-overflow: ellipsis;
496 white-space: nowrap;
497 }
498
499 .os-constellation__row-meta {
500 flex: 0 0 auto;
501 font-size: 11px;
502 opacity: 0.6;
503 }
504
505 .os-constellation__row-offsite {
506 flex: 0 0 auto;
507 font-size: 14px;
508 width: 14px;
509 height: 14px;
510 opacity: 0.55;
511 }
512
513 /*
514 * Reaches screen readers, never the eye. It rides beside the row's own
515 * label so the accessible name reads "Docs (opens in a new tab)"; an
516 * `aria-label` on the row would have replaced that name rather than
517 * extended it, taking the label and any plugin-appended meta with it.
518 */
519 .os-constellation__row-note {
520 position: absolute;
521 width: 1px;
522 height: 1px;
523 margin: -1px;
524 padding: 0;
525 border: 0;
526 overflow: hidden;
527 white-space: nowrap;
528 clip-path: inset( 50% );
529 }
530
531 /* ---- Head -------------------------------------------------------- */
532
533 .os-constellation__head {
534 gap: 12px;
535 padding: 10px;
536 margin-bottom: 4px;
537 }
538
539 .os-constellation__head-icon {
540 position: relative;
541 display: flex;
542 align-items: center;
543 justify-content: center;
544 flex: 0 0 auto;
545 width: 34px;
546 height: 34px;
547 border-radius: var( --os-cn-head-icon-radius, 10px );
548 color: var( --os-ui-holo-ink, #0c0b0f );
549 /*
550 * The head's icon is the panel's second identity moment — the
551 * menu, stated. It wears the mesh permanently (unlike the rows,
552 * which earn it under the pointer) because there is exactly one
553 * head per panel and it is the thing the panel is about.
554 */
555 background-image: var(
556 --os-cn-head-icon-fill,
557 var(
558 --os-cn-row-fill,
559 var( --os-ui-holo-fill, linear-gradient( 124deg, #afa2e8, #c3b8ef ) )
560 )
561 );
562 box-shadow: var(
563 --os-cn-head-icon-shadow,
564 var(
565 --os-ui-holo-glow,
566 0 0 0 1px rgba( 217, 46, 227, 0.2 ),
567 0 2px 10px rgba( 217, 46, 227, 0.15 )
568 )
569 );
570 }
571
572 .os-constellation__head-icon .dashicons {
573 width: 20px;
574 height: 20px;
575 font-size: 20px;
576 line-height: 20px;
577 }
578
579 /*
580 * Drawn art — a data URI or a URL rather than a named dashicon, which
581 * is what every shell-owned tile wears. Painted as a mask filled with
582 * `currentColor` (the head's ink over the mesh), so only the artwork's
583 * alpha is used and a black-stroked glyph stays legible.
584 */
585 .os-constellation__head-art {
586 width: 20px;
587 height: 20px;
588 }
589
590 /*
591 * The head's own hover state deliberately does NOT take the row mesh
592 * — the icon beside it is already wearing it, and two meshes touching
593 * is where iridescence stops reading as emphasis. It gets a plain
594 * raised wash instead.
595 */
596 .os-constellation__head:hover,
597 .os-constellation__head:focus-visible {
598 background-image: none;
599 background-color: var(
600 --os-ui-surface-raised,
601 rgba( 255, 251, 255, 0.06 )
602 );
603 color: var( --os-cn-fg, #fffbff );
604 }
605
606 .os-constellation__head-text {
607 display: flex;
608 flex: 1 1 auto;
609 min-width: 0;
610 flex-direction: column;
611 gap: 1px;
612 }
613
614 .os-constellation__head-title {
615 overflow: hidden;
616 text-overflow: ellipsis;
617 white-space: nowrap;
618 font-size: var( --os-cn-title-size, 14px );
619 font-weight: var( --os-cn-title-weight, 600 );
620 }
621
622
623 /* ---- Groups ------------------------------------------------------ */
624
625 .os-constellation__group {
626 display: flex;
627 flex-direction: column;
628 padding-top: 4px;
629 border-top: 1px solid var( --os-cn-divider, rgba( 255, 251, 255, 0.1 ) );
630 /*
631 * The scroll lives here rather than on the surface, so the head
632 * and the new-window row stay pinned while a long submenu rolls
633 * under them.
634 *
635 * `min-height: 0` is what lets the surface's `max-height` reach
636 * this: a flex item's default `min-height: auto` refuses to
637 * shrink below its content, so without it a tall submenu would
638 * simply overflow the capped surface instead of scrolling inside
639 * it. `flex: 0 1 auto` says this is the part that gives — the
640 * head and the new-window row do not.
641 */
642 flex: 0 1 auto;
643 min-height: 0;
644 max-height: min( 46vh, 360px );
645 overflow-y: auto;
646 overflow-x: hidden;
647 scrollbar-width: thin;
648 }
649
650 .os-constellation__legend {
651 padding: 4px 10px 3px;
652 font-size: 10px;
653 font-weight: 600;
654 letter-spacing: 0.08em;
655 text-transform: uppercase;
656 color: var( --os-cn-legend, rgba( 255, 251, 255, 0.4 ) );
657 }
658
659 /* ---- Submenu rows ------------------------------------------------ */
660
661 /*
662 * The orbit dot. Each row's hue comes from its own title, so a long
663 * submenu reads as a spectrum rather than as fifteen identical grey
664 * bullets — and "Menus" is the same colour on every site, every
665 * session, because the hue is hashed from the string.
666 */
667 .os-constellation__orbit {
668 position: relative;
669 flex: 0 0 auto;
670 width: 8px;
671 height: 8px;
672 margin-inline-start: 3px;
673 border-radius: 50%;
674 /* `currentColor` makes every dot the row's own ink, which follows
675 * the row into its highlighted state. */
676 background: var( --os-cn-orbit, hsl( var( --os-cn-row-hue, 300 ) 80% 70% ) );
677 box-shadow: var(
678 --os-cn-orbit-halo,
679 0 0 0 3px hsl( var( --os-cn-row-hue, 300 ) 80% 70% / 0.16 )
680 );
681 transition: box-shadow var( --os-ui-motion-fast, 140ms ) ease-out;
682 }
683
684 .os-constellation__row--sub:hover .os-constellation__orbit,
685 .os-constellation__row--sub:focus-visible .os-constellation__orbit {
686 /* On the mesh the halo would disappear; a Void ring reads instead. */
687 box-shadow: var( --os-cn-orbit-halo-hover, 0 0 0 3px rgba( 12, 11, 15, 0.25 ) );
688 }
689
690 /* ---- Live-window rows -------------------------------------------- */
691
692 .os-constellation__pip {
693 flex: 0 0 auto;
694 width: 8px;
695 height: 8px;
696 margin-inline-start: 3px;
697 border-radius: 50%;
698 background: var( --os-ui-accent, #f252fc );
699 box-shadow: 0 0 8px var( --os-ui-accent-dim, #d92ee3 );
700 }
701
702 /* A minimized window's pip is hollow — same cue the dock's own
703 * indicator uses for "open, but put away". */
704 .os-constellation__row--live[ data-state="minimized" ] .os-constellation__pip {
705 background: transparent;
706 border: 1px solid var( --os-ui-accent, #f252fc );
707 box-shadow: none;
708 }
709
710 /* ==================================================================
711 * 3. Motion budget
712 * ================================================================== */
713
714 /*
715 * Reduced motion stops the travel, never the surface. The panel still
716 * appears, the seam node still glows, the hovered row still wears the
717 * mesh — losing those would lose STATE, not animation. What goes is
718 * the flight: the spring, the stagger, the sheen sweep, the breathing.
719 */
720 @media ( prefers-reduced-motion: reduce ) {
721 .os-constellation,
722 .os-constellation__row {
723 transition-duration: 1ms;
724 transition-delay: 0s;
725 }
726
727 /*
728 * No travel, in either direction. The exit is also removed from
729 * the document immediately under this preference (the JS reads
730 * the same media query), so `--closing` is listed here only to
731 * cover the frame between the class landing and the node going.
732 */
733 .os-constellation,
734 .os-constellation--open,
735 .os-constellation--closing {
736 transform: translate(
737 calc( -50% + var( --os-cn-shift, 0px ) ),
738 -100%
739 );
740 }
741
742 .os-constellation__row {
743 transform: none;
744 }
745
746 .os-constellation--open .os-constellation__sheen {
747 animation: none;
748 }
749
750 }
751