PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.12
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.12
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 0.8.9 All 36 releases
← All changes | assets/css/dock.css +423 -63 1.0.1 → 1.1.12 View file →
@@ -87,8 +87,19 @@
87 87 color: var( --os-dock-icon-color, rgba( 255, 255, 255, 0.7 ) );
88 88 transition: background-color 0.15s ease, transform 0.15s ease, color 0.15s ease;
89 89 }
90 90
91 +/*
92 + * The tile whose window is in front sits on a lit plate. Declared
93 + * before the hover rule at the same specificity so the pointer still
94 + * wins while it is over the tile. Falls back to nothing, which is
95 + * what every placement drew before the token existed.
96 + */
97 +.os-dock__item--focused .os-dock__item-primary {
98 + background-color: var( --os-dock-item-bg-focused, transparent );
99 + color: var( --os-dock-icon-color-hover, var( --os-ui-fg-on-accent, #fff ) );
100 +}
101 +
91 102 .os-dock__item-primary:hover {
92 103 background-color: var(
93 104 --os-dock-item-bg-hover,
94 105 rgba( 255, 255, 255, 0.15 )
@@ -113,21 +124,29 @@
113 124 }
114 125
115 126 /* Stacked card hint on hover — a second card peeks from behind the
116 127 icon when the tile has multiple open windows. Only visible on hover so
117 - it never conflicts with focused tiles. */
118 -.os-dock__item--stacked:hover .os-dock__item-primary::before,
119 -.os-dock__item--stacked[data-peek-active] .os-dock__item-primary::before {
128 + it never conflicts with focused tiles.
129 + *
130 + * Painted on the TILE, not on the button it hints at. The hover that
131 + * arms this also scales the button, a transform makes a stacking
132 + * context, and a `z-index: -1` child of one cannot escape it: the card
133 + * landed above the button's own hover plate instead of behind the
134 + * tile, reading as a second plate laid over the first. The tile is
135 + * transformed by nothing, and a positioned child (the button) paints
136 + * over a parent's `::before` at the same level, so the card sits where
137 + * its name says without asking for a z-index at all. */
138 +.os-dock__item--stacked:hover::before,
139 +.os-dock__item--stacked[ data-peek-active ]::before {
120 140 content: "";
121 141 position: absolute;
122 142 top: -4px;
123 143 inset-inline-start: -4px;
124 - width: 100%;
125 - height: 100%;
126 - border-radius: inherit;
144 + width: 40px;
145 + height: 40px;
146 + border-radius: 10px;
127 147 background: rgba( 255, 255, 255, 0.06 );
128 148 border: 1px solid rgba( 255, 255, 255, 0.10 );
129 - z-index: -1;
130 149 pointer-events: none;
131 150 }
132 151
133 152 /* Dashicon inside dock item. */
@@ -249,9 +268,9 @@
249 268 padding: 0;
250 269 border: 2px solid var( --os-dock-bg, rgba( 0, 0, 0, 0.4 ) );
251 270 border-radius: 50%;
252 271 background: var( --wp-admin-theme-color, #2271b1 );
253 - color: var( --os-ui-fg-on-accent, #fff );
272 + color: var( --os-ui-accent-ink, var( --os-ui-fg-on-accent, #fff ) );
254 273 cursor: pointer;
255 274 transition: background-color 0.15s ease, transform 0.15s ease;
256 275 z-index: 1;
257 276 }
@@ -716,34 +735,51 @@
716 735 .os-dock__item-new:hover {
717 736 transform: translateY( -50% ) scale( 1.1 );
718 737 }
719 738
720 -/* Separator — horizontal hairline between menu tiles and system
721 - * tiles. With the `__scroll` / `__pinned` split the system separator
722 - * lives at the top of `__pinned`; flex-flow handles the "system
723 - * tiles at the bottom" placement, no margin-top: auto needed. The
724 - * `--group` variant is used inline between core and plugin menu
725 - * tiles in `__scroll`; it keeps the hairline styling and sits
726 - * exactly where it's inserted. */
739 +/* Separator — the line between two groups of tiles. With the
740 + * `__scroll` / `__pinned` split the system separator lives at the top
741 + * of `__pinned`; flex-flow handles the "system tiles at the bottom"
742 + * placement, no margin-top: auto needed. The `--group` variant is used
743 + * inline between core and plugin menu tiles in `__scroll` and sits
744 + * exactly where it's inserted.
745 + *
746 + * ONE treatment for every division in the rail. The selector carries
747 + * only the base class on purpose: `--group` elements carry it too, so
748 + * both boundaries resolve here and cannot drift apart. Two weights in
749 + * one short rail read as two unrelated ideas rather than one system,
750 + * which is why the earlier loud-plus-hairline pairing is gone.
751 + *
752 + * A gradient, not a flat fill: the line is brightest where the eye
753 + * lands and gone by the time it reaches the rail's padding, so it
754 + * never terminates in a visible stub against the glass. The soft glow
755 + * around it is what keeps a 2px line from disappearing into the dock
756 + * tint; nothing is drawn on the line itself. */
727 757 .os-dock[ data-os-dock-placement="left" ]
728 758 .os-dock__separator,
729 759 .os-dock[ data-os-dock-placement="right" ]
730 760 .os-dock__separator {
761 + position: relative;
731 762 width: 60%;
732 - height: 1px;
733 - margin: 8px auto 4px;
734 - background: var( --os-dock-border );
763 + height: 2px;
764 + margin: 12px auto;
765 + border-radius: 2px;
766 + background: linear-gradient(
767 + to right,
768 + transparent 0%,
769 + var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 28%,
770 + var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 72%,
771 + transparent 100%
772 + );
773 + box-shadow: 0 0 10px
774 + color-mix(
775 + in srgb,
776 + var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 45%,
777 + transparent
778 + );
735 779 flex-shrink: 0;
736 780 }
737 781
738 -.os-dock[ data-os-dock-placement="left" ]
739 - .os-dock__separator--group,
740 -.os-dock[ data-os-dock-placement="right" ]
741 - .os-dock__separator--group {
742 - margin: 10px auto;
743 - background: rgba( 255, 255, 255, 0.22 );
744 -}
745 -
746 782 /*
747 783 * Hide the system separator when nothing precedes it — on a clean
748 784 * install (or a low-cap user with zero menu items) the separator
749 785 * would render as a stray hairline with nothing to divide.
@@ -763,8 +799,9 @@
763 799 .os-dock__separator {
764 800 display: none;
765 801 }
766 802
803 +
767 804 /*
768 805 * Drop an empty `__scroll` from the flex flow entirely. When only
769 806 * system tiles are present (e.g. just the Recycle Bin), the empty
770 807 * wrapper would otherwise still occupy the rail: its `flex-grow`
@@ -970,28 +1007,54 @@
970 1007
971 1008 /*
972 1009 * Bottom-dock inner wrappers — `__scroll` carries the menu tiles and
973 1010 * absorbs horizontal overflow; `__pinned` carries the system tiles and
974 - * stays anchored to the trailing edge. Padding-top on `__scroll` gives
975 - * the badge (top: -3px on the tile) room so `overflow-y: hidden`
976 - * doesn't crop it. `min-width: 0` lets the wrapper shrink below its
1011 + * stays anchored to the trailing edge.
1012 + *
1013 + * **The padding around `__scroll` is what keeps badges visible, and it
1014 + * has to be on both axes.** `overflow-x: auto` below cannot coexist
1015 + * with `overflow-y: visible`: per spec the visible axis computes to
1016 + * `auto`, so this wrapper is a scroll container in BOTH directions
1017 + * however it is authored, and anything a child hangs outside its
1018 + * padding box is clipped. A badge sits at `top: -3px; right: -3px` on
1019 + * its tile, and the LAST tile's edge is the wrapper's edge once the
1020 + * content is wide enough to scroll — which is why this showed up as
1021 + * "the badge on the last plugin looks cut off" rather than as a
1022 + * general problem. The inline padding is symmetric so the tile cluster
1023 + * stays centred in the pill. `min-width: 0` lets the wrapper shrink below its
977 1024 * content's natural width, so the dock pill's `max-width` is what
978 1025 * decides when scroll kicks in instead of the content forcing the
979 - * pill wider. `justify-content: center` keeps tiles balanced inside
980 - * the pill when content fits; when it overflows, scroll engages and
981 - * items extend past the visible area.
1026 + * pill wider. `justify-content: safe center` keeps tiles balanced inside
1027 + * the pill when content fits; when it overflows, the `safe` keyword
1028 + * ensures alignment falls back to `start` so `scrollLeft = 0` reveals the
1029 + * first item and all items remain scrollable without clipping. Bare
1030 + * `center` put the leading tiles at a negative offset, and a scroll
1031 + * container clamps `scrollLeft` at 0, so Dashboard and its neighbours
1032 + * were clipped with no way to scroll back to them. **The plain `start`
1033 + * above it is the fallback, not a leftover** — a browser that cannot
1034 + * parse the overflow-alignment keyword drops the `safe center`
1035 + * declaration and keeps that one, which left-aligns the tiles rather
1036 + * than hiding them. Deleting it restores the bug on those browsers.
982 1037 */
983 1038 .os-dock[ data-os-dock-placement="bottom" ] .os-dock__scroll {
984 1039 display: flex;
985 1040 flex-direction: row;
986 1041 align-items: center;
987 - justify-content: center;
1042 + justify-content: start;
1043 + justify-content: safe center;
988 1044 gap: 6px;
989 1045 flex: 1 1 auto;
990 1046 min-width: 0;
991 1047 padding-top: 4px;
992 1048 padding-bottom: 6px;
1049 + padding-inline: 4px;
993 1050 overflow-x: auto;
1051 + /*
1052 + * Authored `visible`, computed `auto` — see the note above. Kept as
1053 + * the honest statement of intent: nothing here wants a vertical
1054 + * scrollbar, and `scrollbar-width: none` plus the padding is what
1055 + * makes that true in practice.
1056 + */
994 1057 overflow-y: visible;
995 1058 scrollbar-width: none;
996 1059 }
997 1060
@@ -1010,49 +1073,46 @@
1010 1073 padding-top: 4px;
1011 1074 padding-bottom: 6px;
1012 1075 }
1013 1076
1014 -/* Separator — vertical hairline between menu tiles and system tiles.
1015 - * Lives at the leading edge of `__pinned`; flex flow handles the
1077 +/* Separator — the line between two groups of tiles. The system one
1078 + * lives at the leading edge of `__pinned` (flex flow handles the
1016 1079 * "pinned at the trailing edge" placement, no margin-inline-start:
1017 - * auto needed. The `--group` variant keeps the hairline styling for
1018 - * the inline core→plugin boundary inside `__scroll` and sits where
1019 - * inserted. */
1080 + * auto needed); the `--group` one sits inline inside `__scroll` where
1081 + * it was inserted. Both resolve through this single rule — see the
1082 + * note on the vertical placements above for why there is only one. */
1020 1083 .os-dock[ data-os-dock-placement="bottom" ]
1021 1084 .os-dock__separator {
1022 - width: 1px;
1085 + position: relative;
1086 + width: 2px;
1023 1087 /* Fixed height — `60%` collapses against the floating pill's
1024 - intrinsic height, leaving the hairline effectively invisible.
1025 - 28px matches the dock tile inner glyph area. */
1026 - height: 28px;
1088 + intrinsic height, leaving the line effectively invisible. */
1089 + height: 34px;
1027 1090 /* Symmetric horizontal margin so the divider sits centered in the
1028 1091 * inter-tile gap: the 6px flex gap on each side (`__scroll`→`__pinned`
1029 - * on the left, `__pinned`'s own gap on the right) plus an equal 6px
1092 + * on the left, `__pinned`'s own gap on the right) plus an equal
1030 1093 * margin per side balances out. An asymmetric margin here nudges the
1031 1094 * divider — and the whole menu cluster with it — off the pill's
1032 - * center. */
1033 - margin: auto 6px;
1034 - background: var( --os-dock-border );
1095 + * center. Wider than the tile gap on purpose: the clusters need to
1096 + * read as groups before the line between them means anything. */
1097 + margin: auto 14px;
1098 + border-radius: 2px;
1099 + background: linear-gradient(
1100 + to bottom,
1101 + transparent 0%,
1102 + var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 28%,
1103 + var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 72%,
1104 + transparent 100%
1105 + );
1106 + box-shadow: 0 0 10px
1107 + color-mix(
1108 + in srgb,
1109 + var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 45%,
1110 + transparent
1111 + );
1035 1112 flex-shrink: 0;
1036 1113 }
1037 1114
1038 -/*
1039 - * Group separator — sits inline between the core cluster and the
1040 - * plugin cluster. Brighter than the menu-vs-system separator because
1041 - * it carries user meaning ("this is where your installed apps
1042 - * begin"), and flanked by extra gap so the boundary reads at a
1043 - * glance rather than disappearing into the dock tint.
1044 - */
1045 -.os-dock[ data-os-dock-placement="bottom" ]
1046 - .os-dock__separator--group {
1047 - width: 1px;
1048 - height: 60%;
1049 - margin: auto 10px;
1050 - margin-inline-start: 10px;
1051 - background: rgba( 255, 255, 255, 0.22 );
1052 - flex-shrink: 0;
1053 -}
1054 -
1055 1115 /* Tooltip — above the hovered tile. JS writes the horizontal center
1056 1116 * + a top near the tile's top edge; CSS translates the tooltip up by
1057 1117 * its own height so it clears the tile. */
1058 1118 .os-dock__tooltip--above {
@@ -1092,5 +1152,305 @@
1092 1152 }
1093 1153
1094 1154 .os-dock__item:not( .os-dock__item--system ):active {
1095 1155 cursor: grabbing;
1156 +}
1157 +
1158 +/* ------------------------------------------------------------------
1159 + * The way out — `Exit OpenStation`
1160 + *
1161 + * Every other system tile opens something you can close again. This
1162 + * one leaves the desktop, and with the admin bar hidden by default it
1163 + * is the only route back to classic admin. Drawn like its neighbours
1164 + * it read as one more launcher, which is the same "some tiles do a
1165 + * different kind of thing" confusion the single dock set out to fix,
1166 + * just moved to the other end of the rail.
1167 + *
1168 + * Three signals, none of them colour. Danger red would overstate it
1169 + * (nothing is destroyed, the session is saved and the desktop is one
1170 + * click away) and Pulse is already spent on the seam:
1171 + *
1172 + * 1. **Last, always.** `order: 1` rather than registration order —
1173 + * native-window tiles from plugins sync in after boot and would
1174 + * otherwise land behind it.
1175 + * 2. **Its own gap**, wider than the rail's 6px, so it reads as
1176 + * sitting outside the set rather than at the end of it. A second
1177 + * divider would have said this too, and would have cost the rail
1178 + * the one-structural-line rule it just earned.
1179 + * 3. **A different silhouette** — a ring rather than a plate. Shape
1180 + * survives every theme, every dock texture, greyscale and a
1181 + * colour-blind reading of the rail; a tint survives none of them
1182 + * reliably.
1183 + *
1184 + * And it leans toward the edge it leads to on hover instead of lifting
1185 + * toward the pointer, because every other tile's lift means "I will
1186 + * come to you" and this one means the opposite.
1187 + *
1188 + * Keyed on the tile id, the same idiom the Recycle Bin badge uses
1189 + * above. `os-exit` is frozen in `src/exit-openstation.ts`
1190 + * (`EXIT_OPENSTATION_TILE_ID`) and `dock-exit-tile.test.ts` pins the
1191 + * attribute this selector depends on.
1192 + * ------------------------------------------------------------------ */
1193 +
1194 +.os-dock__item[ data-system-id="os-exit" ] {
1195 + order: 1;
1196 +}
1197 +
1198 +.os-dock__item[ data-system-id="os-exit" ] .os-dock__item-primary {
1199 + border-radius: 50%;
1200 + background-color: transparent;
1201 + box-shadow: inset 0 0 0 1px var(
1202 + --os-dock-exit-ring,
1203 + var( --os-dock-border, rgba( 255, 255, 255, 0.18 ) )
1204 + );
1205 + color: var( --os-dock-exit-icon, rgba( 255, 255, 255, 0.55 ) );
1206 +}
1207 +
1208 +/*
1209 + * The gap runs along the rail's own axis, so it follows the placement:
1210 + * inline for the horizontal pill, block for the vertical pillars.
1211 + */
1212 +.os-dock[ data-os-dock-placement="bottom" ]
1213 + .os-dock__item[ data-system-id="os-exit" ] {
1214 + margin-inline-start: 10px;
1215 +}
1216 +
1217 +.os-dock[ data-os-dock-placement="left" ]
1218 + .os-dock__item[ data-system-id="os-exit" ],
1219 +.os-dock[ data-os-dock-placement="right" ]
1220 + .os-dock__item[ data-system-id="os-exit" ] {
1221 + margin-block-start: 8px;
1222 +}
1223 +
1224 +/*
1225 + * Hover: the ring closes up and the tile moves TOWARD its edge. The
1226 + * generic tile rule scales up by 1.1, so these have to out-specify it
1227 + * rather than sit alongside it.
1228 + */
1229 +.os-dock__item[ data-system-id="os-exit" ] .os-dock__item-primary:hover,
1230 +.os-dock__item[ data-system-id="os-exit" ]
1231 + .os-dock__item-primary:focus-visible {
1232 + background-color: transparent;
1233 + box-shadow: inset 0 0 0 1px var(
1234 + --os-dock-exit-ring-hover,
1235 + var( --os-dock-item-outline, rgba( 255, 255, 255, 0.7 ) )
1236 + );
1237 + color: var( --os-dock-icon-color-hover, var( --os-ui-fg-on-accent, #fff ) );
1238 +}
1239 +
1240 +.os-dock[ data-os-dock-placement="bottom" ]
1241 + .os-dock__item[ data-system-id="os-exit" ]
1242 + .os-dock__item-primary:hover {
1243 + transform: translateY( 2px );
1244 +}
1245 +
1246 +.os-dock[ data-os-dock-placement="left" ]
1247 + .os-dock__item[ data-system-id="os-exit" ]
1248 + .os-dock__item-primary:hover {
1249 + transform: translateX( -2px );
1250 +}
1251 +
1252 +.os-dock[ data-os-dock-placement="right" ]
1253 + .os-dock__item[ data-system-id="os-exit" ]
1254 + .os-dock__item-primary:hover {
1255 + transform: translateX( 2px );
1256 +}
1257 +
1258 +/* The press keeps the shared cue: every tile in the rail dips on
1259 + * :active, and this one should not feel unresponsive by comparison. */
1260 +.os-dock__item[ data-system-id="os-exit" ] .os-dock__item-primary:active {
1261 + transform: scale( 0.95 );
1262 +}
1263 +
1264 +@media ( prefers-reduced-motion: reduce ) {
1265 + .os-dock[ data-os-dock-placement="bottom" ]
1266 + .os-dock__item[ data-system-id="os-exit" ]
1267 + .os-dock__item-primary:hover,
1268 + .os-dock[ data-os-dock-placement="left" ]
1269 + .os-dock__item[ data-system-id="os-exit" ]
1270 + .os-dock__item-primary:hover,
1271 + .os-dock[ data-os-dock-placement="right" ]
1272 + .os-dock__item[ data-system-id="os-exit" ]
1273 + .os-dock__item-primary:hover {
1274 + transform: none;
1275 + }
1276 +}
1277 +
1278 +/* ------------------------------------------------------------------
1279 + * Dynamic dock behavior — OpenStation Preferences → Appearance →
1280 + * Desktop layout, persisted as `dockBehavior` (the dock) and
1281 + * `sideDockBehavior` (the Split sidebar) and worn by each rail as
1282 + * `data-os-dock-behavior` — an attribute PER RAIL rather than a body
1283 + * class, because Split's two rails answer independently. PHP stamps
1284 + * the dock on first paint; the apply pass and `src/dock-behavior.ts`
1285 + * re-stamp on every change and rebuild. `static` is the absence of
1286 + * rules.
1287 + *
1288 + * A dynamic rail that is not `os-dock--revealed` is PARKED: it
1289 + * collapses into a thin indicator line hugging its edge — the iOS
1290 + * home indicator, one line that says "there is a dock here" — and
1291 + * expands back into the full rail when the pointer comes for it.
1292 + * `src/dock-behavior.ts` owns the flip (a full-width edge zone, the
1293 + * rail's own box, its flyouts, keyboard focus) and runs it through
1294 + * the View Transitions API, so the line morphs into the pill and
1295 + * back; the rules below only describe the two resting states and
1296 + * tune the morph. There is deliberately no `:hover` here — a state
1297 + * CSS flipped on its own would jump instead of morphing.
1298 + *
1299 + * The line is the rail itself, not a separate element: same node,
1300 + * same `view-transition-name`, which is what lets the browser animate
1301 + * one box into the other. Its children are hidden while parked so
1302 + * the line is a line. A side rail leaves the flex row for the
1303 + * duration, so the area grows to the full width and the rail rides
1304 + * over it when summoned; the work area reserves nothing for a
1305 + * dynamic rail (`src/work-area/index.ts`).
1306 + * ------------------------------------------------------------------ */
1307 +
1308 +/*
1309 + * Side rails overlay the area in both states — the flex row must not
1310 + * shrink for a rail that is only sometimes there.
1311 + */
1312 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ],
1313 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ] {
1314 + position: absolute;
1315 + inset-block: 0;
1316 +}
1317 +
1318 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ] {
1319 + inset-inline-start: 0;
1320 +}
1321 +
1322 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ] {
1323 + inset-inline-end: 0;
1324 +}
1325 +
1326 +/*
1327 + * Parked — the indicator line. Everything that makes the pill a
1328 + * pill (padding, border, blur, texture, tile gap) comes off, and the
1329 + * box is the line's size. `overflow: hidden` clips whatever a tile
1330 + * paints past its box during the morph.
1331 + */
1332 +.os-dock[ data-os-dock-behavior="dynamic" ]:not( .os-dock--revealed ) {
1333 + min-width: 0;
1334 + min-height: 0;
1335 + max-width: none;
1336 + padding: 0;
1337 + gap: 0;
1338 + border: 0;
1339 + border-radius: 999px;
1340 + overflow: hidden;
1341 + background-color: var(
1342 + --os-dock-indicator-bg,
1343 + var( --os-dock-item-outline, rgba( 255, 255, 255, 0.55 ) )
1344 + );
1345 + background-image: none;
1346 + backdrop-filter: none;
1347 + -webkit-backdrop-filter: none;
1348 + box-shadow: none;
1349 + cursor: pointer;
1350 +}
1351 +
1352 +.os-dock[ data-os-dock-behavior="dynamic" ]:not( .os-dock--revealed ) > * {
1353 + display: none;
1354 +}
1355 +
1356 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="bottom" ]:not( .os-dock--revealed ) {
1357 + width: var( --os-dock-indicator-length, 180px );
1358 + height: var( --os-dock-indicator-thickness, 5px );
1359 + bottom: 8px;
1360 +}
1361 +
1362 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ]:not( .os-dock--revealed ),
1363 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ]:not( .os-dock--revealed ) {
1364 + width: var( --os-dock-indicator-thickness, 5px );
1365 + height: var( --os-dock-indicator-length, 180px );
1366 + inset-block: calc( 50% - var( --os-dock-indicator-length, 180px ) / 2 );
1367 +}
1368 +
1369 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ]:not( .os-dock--revealed ) {
1370 + inset-inline-start: 8px;
1371 +}
1372 +
1373 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ]:not( .os-dock--revealed ) {
1374 + inset-inline-end: 8px;
1375 +}
1376 +
1377 +/*
1378 + * The morph. With the API, `src/dock-behavior.ts` names the rail for
1379 + * the duration of one flip and the browser animates the old box into
1380 + * the new one; the rail's contents cross-fade underneath. The root is
1381 + * opted out of the snapshot so the rest of the desktop keeps
1382 + * running while the line becomes a pill — a frozen page for 260ms
1383 + * on every reveal would be the animation costing more than it
1384 + * gives. Without the API, the properties that can transition do.
1385 + */
1386 +@supports ( view-transition-name: none ) {
1387 + html.os-dock-vt {
1388 + view-transition-name: none;
1389 + }
1390 +
1391 + html.os-dock-vt::view-transition-old( root ),
1392 + html.os-dock-vt::view-transition-new( root ) {
1393 + animation: none;
1394 + }
1395 +
1396 + /*
1397 + * The transition layer must not catch the pointer. By default it
1398 + * does — the whole page reads as inert until the morph settles —
1399 + * and with the root opted out of the snapshot that is worse than
1400 + * pointless: the live desktop is right there under a layer that
1401 + * swallows every mouseup and every pointerenter for 260ms. The
1402 + * behavior module already holds a park back while a button is
1403 + * down; this is the other half, so a hover that lands on a tile
1404 + * while the rail is still morphing out starts its hover-intent
1405 + * timer instead of waiting for the next pointer movement.
1406 + */
1407 + html.os-dock-vt::view-transition {
1408 + pointer-events: none;
1409 + }
1410 +
1411 + ::view-transition-group( os-dock-os-dock ),
1412 + ::view-transition-group( os-dock-os-side-dock ) {
1413 + animation-duration: 260ms;
1414 + animation-timing-function: cubic-bezier( 0.22, 1, 0.36, 1 );
1415 + }
1416 +
1417 + ::view-transition-old( os-dock-os-dock ),
1418 + ::view-transition-old( os-dock-os-side-dock ) {
1419 + animation: os-dock-vt-out 120ms ease-out both;
1420 + }
1421 +
1422 + ::view-transition-new( os-dock-os-dock ),
1423 + ::view-transition-new( os-dock-os-side-dock ) {
1424 + animation: os-dock-vt-in 180ms ease-out 80ms both;
1425 + }
1426 +
1427 + @keyframes os-dock-vt-out {
1428 + to {
1429 + opacity: 0;
1430 + }
1431 + }
1432 +
1433 + @keyframes os-dock-vt-in {
1434 + from {
1435 + opacity: 0;
1436 + }
1437 + }
1438 +}
1439 +
1440 +@supports not ( view-transition-name: none ) {
1441 + .os-dock[ data-os-dock-behavior="dynamic" ] {
1442 + transition:
1443 + width 240ms ease,
1444 + height 240ms ease,
1445 + opacity 240ms ease,
1446 + border-radius 240ms ease,
1447 + bottom 240ms ease,
1448 + inset-block 240ms ease;
1449 + }
1450 +}
1451 +
1452 +@media ( prefers-reduced-motion: reduce ) {
1453 + .os-dock[ data-os-dock-behavior="dynamic" ] {
1454 + transition: none;
1455 + }
1096 1456 }