PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / trunk
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin vtrunk
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 0.8.9 0.8.8 0.8.7 0.8.6 All 33 releases
desktop-mode / assets / css / openstation-layout.css

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

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