| @@ -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 ) |
| @@ -249,9 +260,9 @@ | ||
| 249 | 260 | padding: 0; |
| 250 | 261 | border: 2px solid var( --os-dock-bg, rgba( 0, 0, 0, 0.4 ) ); |
| 251 | 262 | border-radius: 50%; |
| 252 | 263 | background: var( --wp-admin-theme-color, #2271b1 ); |
| 253 | - color: var( --os-ui-fg-on-accent, #fff ); | |
| 264 | + color: var( --os-ui-accent-ink, var( --os-ui-fg-on-accent, #fff ) ); | |
| 254 | 265 | cursor: pointer; |
| 255 | 266 | transition: background-color 0.15s ease, transform 0.15s ease; |
| 256 | 267 | z-index: 1; |
| 257 | 268 | } |
| @@ -1243,6 +1254,186 @@ | ||
| 1243 | 1254 | .os-dock[ data-os-dock-placement="right" ] |
| 1244 | 1255 | .os-dock__item[ data-system-id="os-exit" ] |
| 1245 | 1256 | .os-dock__item-primary:hover { |
| 1246 | 1257 | transform: none; |
| 1258 | + } | |
| 1259 | +} | |
| 1260 | + | |
| 1261 | +/* ------------------------------------------------------------------ | |
| 1262 | + * Dynamic dock behavior — OpenStation Preferences → Appearance → | |
| 1263 | + * Desktop layout, persisted as `dockBehavior` (the dock) and | |
| 1264 | + * `sideDockBehavior` (the Split sidebar) and worn by each rail as | |
| 1265 | + * `data-os-dock-behavior` — an attribute PER RAIL rather than a body | |
| 1266 | + * class, because Split's two rails answer independently. PHP stamps | |
| 1267 | + * the dock on first paint; the apply pass and `src/dock-behavior.ts` | |
| 1268 | + * re-stamp on every change and rebuild. `static` is the absence of | |
| 1269 | + * rules. | |
| 1270 | + * | |
| 1271 | + * A dynamic rail that is not `os-dock--revealed` is PARKED: it | |
| 1272 | + * collapses into a thin indicator line hugging its edge — the iOS | |
| 1273 | + * home indicator, one line that says "there is a dock here" — and | |
| 1274 | + * expands back into the full rail when the pointer comes for it. | |
| 1275 | + * `src/dock-behavior.ts` owns the flip (a full-width edge zone, the | |
| 1276 | + * rail's own box, its flyouts, keyboard focus) and runs it through | |
| 1277 | + * the View Transitions API, so the line morphs into the pill and | |
| 1278 | + * back; the rules below only describe the two resting states and | |
| 1279 | + * tune the morph. There is deliberately no `:hover` here — a state | |
| 1280 | + * CSS flipped on its own would jump instead of morphing. | |
| 1281 | + * | |
| 1282 | + * The line is the rail itself, not a separate element: same node, | |
| 1283 | + * same `view-transition-name`, which is what lets the browser animate | |
| 1284 | + * one box into the other. Its children are hidden while parked so | |
| 1285 | + * the line is a line. A side rail leaves the flex row for the | |
| 1286 | + * duration, so the area grows to the full width and the rail rides | |
| 1287 | + * over it when summoned; the work area reserves nothing for a | |
| 1288 | + * dynamic rail (`src/work-area/index.ts`). | |
| 1289 | + * ------------------------------------------------------------------ */ | |
| 1290 | + | |
| 1291 | +/* | |
| 1292 | + * Side rails overlay the area in both states — the flex row must not | |
| 1293 | + * shrink for a rail that is only sometimes there. | |
| 1294 | + */ | |
| 1295 | +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ], | |
| 1296 | +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ] { | |
| 1297 | + position: absolute; | |
| 1298 | + inset-block: 0; | |
| 1299 | +} | |
| 1300 | + | |
| 1301 | +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ] { | |
| 1302 | + inset-inline-start: 0; | |
| 1303 | +} | |
| 1304 | + | |
| 1305 | +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ] { | |
| 1306 | + inset-inline-end: 0; | |
| 1307 | +} | |
| 1308 | + | |
| 1309 | +/* | |
| 1310 | + * Parked — the indicator line. Everything that makes the pill a | |
| 1311 | + * pill (padding, border, blur, texture, tile gap) comes off, and the | |
| 1312 | + * box is the line's size. `overflow: hidden` clips whatever a tile | |
| 1313 | + * paints past its box during the morph. | |
| 1314 | + */ | |
| 1315 | +.os-dock[ data-os-dock-behavior="dynamic" ]:not( .os-dock--revealed ) { | |
| 1316 | + min-width: 0; | |
| 1317 | + min-height: 0; | |
| 1318 | + max-width: none; | |
| 1319 | + padding: 0; | |
| 1320 | + gap: 0; | |
| 1321 | + border: 0; | |
| 1322 | + border-radius: 999px; | |
| 1323 | + overflow: hidden; | |
| 1324 | + background-color: var( | |
| 1325 | + --os-dock-indicator-bg, | |
| 1326 | + var( --os-dock-item-outline, rgba( 255, 255, 255, 0.55 ) ) | |
| 1327 | + ); | |
| 1328 | + background-image: none; | |
| 1329 | + backdrop-filter: none; | |
| 1330 | + -webkit-backdrop-filter: none; | |
| 1331 | + box-shadow: none; | |
| 1332 | + cursor: pointer; | |
| 1333 | +} | |
| 1334 | + | |
| 1335 | +.os-dock[ data-os-dock-behavior="dynamic" ]:not( .os-dock--revealed ) > * { | |
| 1336 | + display: none; | |
| 1337 | +} | |
| 1338 | + | |
| 1339 | +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="bottom" ]:not( .os-dock--revealed ) { | |
| 1340 | + width: var( --os-dock-indicator-length, 180px ); | |
| 1341 | + height: var( --os-dock-indicator-thickness, 5px ); | |
| 1342 | + bottom: 8px; | |
| 1343 | +} | |
| 1344 | + | |
| 1345 | +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ]:not( .os-dock--revealed ), | |
| 1346 | +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ]:not( .os-dock--revealed ) { | |
| 1347 | + width: var( --os-dock-indicator-thickness, 5px ); | |
| 1348 | + height: var( --os-dock-indicator-length, 180px ); | |
| 1349 | + inset-block: calc( 50% - var( --os-dock-indicator-length, 180px ) / 2 ); | |
| 1350 | +} | |
| 1351 | + | |
| 1352 | +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ]:not( .os-dock--revealed ) { | |
| 1353 | + inset-inline-start: 8px; | |
| 1354 | +} | |
| 1355 | + | |
| 1356 | +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ]:not( .os-dock--revealed ) { | |
| 1357 | + inset-inline-end: 8px; | |
| 1358 | +} | |
| 1359 | + | |
| 1360 | +/* | |
| 1361 | + * The morph. With the API, `src/dock-behavior.ts` names the rail for | |
| 1362 | + * the duration of one flip and the browser animates the old box into | |
| 1363 | + * the new one; the rail's contents cross-fade underneath. The root is | |
| 1364 | + * opted out of the snapshot so the rest of the desktop keeps | |
| 1365 | + * running while the line becomes a pill — a frozen page for 260ms | |
| 1366 | + * on every reveal would be the animation costing more than it | |
| 1367 | + * gives. Without the API, the properties that can transition do. | |
| 1368 | + */ | |
| 1369 | +@supports ( view-transition-name: none ) { | |
| 1370 | + html.os-dock-vt { | |
| 1371 | + view-transition-name: none; | |
| 1372 | + } | |
| 1373 | + | |
| 1374 | + html.os-dock-vt::view-transition-old( root ), | |
| 1375 | + html.os-dock-vt::view-transition-new( root ) { | |
| 1376 | + animation: none; | |
| 1377 | + } | |
| 1378 | + | |
| 1379 | + /* | |
| 1380 | + * The transition layer must not catch the pointer. By default it | |
| 1381 | + * does — the whole page reads as inert until the morph settles — | |
| 1382 | + * and with the root opted out of the snapshot that is worse than | |
| 1383 | + * pointless: the live desktop is right there under a layer that | |
| 1384 | + * swallows every mouseup and every pointerenter for 260ms. The | |
| 1385 | + * behavior module already holds a park back while a button is | |
| 1386 | + * down; this is the other half, so a hover that lands on a tile | |
| 1387 | + * while the rail is still morphing out starts its hover-intent | |
| 1388 | + * timer instead of waiting for the next pointer movement. | |
| 1389 | + */ | |
| 1390 | + html.os-dock-vt::view-transition { | |
| 1391 | + pointer-events: none; | |
| 1392 | + } | |
| 1393 | + | |
| 1394 | + ::view-transition-group( os-dock-os-dock ), | |
| 1395 | + ::view-transition-group( os-dock-os-side-dock ) { | |
| 1396 | + animation-duration: 260ms; | |
| 1397 | + animation-timing-function: cubic-bezier( 0.22, 1, 0.36, 1 ); | |
| 1398 | + } | |
| 1399 | + | |
| 1400 | + ::view-transition-old( os-dock-os-dock ), | |
| 1401 | + ::view-transition-old( os-dock-os-side-dock ) { | |
| 1402 | + animation: os-dock-vt-out 120ms ease-out both; | |
| 1403 | + } | |
| 1404 | + | |
| 1405 | + ::view-transition-new( os-dock-os-dock ), | |
| 1406 | + ::view-transition-new( os-dock-os-side-dock ) { | |
| 1407 | + animation: os-dock-vt-in 180ms ease-out 80ms both; | |
| 1408 | + } | |
| 1409 | + | |
| 1410 | + @keyframes os-dock-vt-out { | |
| 1411 | + to { | |
| 1412 | + opacity: 0; | |
| 1413 | + } | |
| 1414 | + } | |
| 1415 | + | |
| 1416 | + @keyframes os-dock-vt-in { | |
| 1417 | + from { | |
| 1418 | + opacity: 0; | |
| 1419 | + } | |
| 1420 | + } | |
| 1421 | +} | |
| 1422 | + | |
| 1423 | +@supports not ( view-transition-name: none ) { | |
| 1424 | + .os-dock[ data-os-dock-behavior="dynamic" ] { | |
| 1425 | + transition: | |
| 1426 | + width 240ms ease, | |
| 1427 | + height 240ms ease, | |
| 1428 | + opacity 240ms ease, | |
| 1429 | + border-radius 240ms ease, | |
| 1430 | + bottom 240ms ease, | |
| 1431 | + inset-block 240ms ease; | |
| 1432 | + } | |
| 1433 | +} | |
| 1434 | + | |
| 1435 | +@media ( prefers-reduced-motion: reduce ) { | |
| 1436 | + .os-dock[ data-os-dock-behavior="dynamic" ] { | |
| 1437 | + transition: none; | |
| 1247 | 1438 | } |
| 1248 | 1439 | } |