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
← All changes | assets/css/desktop.css +555 -40 1.1.8 → 1.1.13 View file →
@@ -226,17 +226,9 @@
226 226 }
227 227
228 228 body.os-active.os-admin-bar-dynamic #wpadminbar:hover,
229 229 body.os-active.os-admin-bar-dynamic #wpadminbar:focus-within,
230 -body.os-active.os-admin-bar-dynamic #wpadminbar:active,
231 -/*
232 - * …and while the notch is being used. The notch hangs from the same
233 - * edge and steps down to sit under the bar when it appears (see
234 - * notch.css), so letting the bar retract out from over it mid-reach
235 - * would drop the notch back up under the pointer.
236 - */
237 -body.os-active.os-admin-bar-dynamic:has( .os-notch:hover ) #wpadminbar,
238 -body.os-active.os-admin-bar-dynamic:has( .os-notch:focus-visible ) #wpadminbar {
230 +body.os-active.os-admin-bar-dynamic #wpadminbar:active {
239 231 inset-block-start: 0 !important;
240 232 }
241 233
242 234 /*
@@ -346,8 +338,132 @@
346 338 padding-top: 0 !important;
347 339 }
348 340
349 341 /*
342 + * A desktop theme that draws the toolbar.
343 + *
344 + * The toolbar's colours normally belong to the user's admin colour
345 + * scheme, whose own stylesheet paints it. A theme takes them over only
346 + * when it declares an `--os-toolbar-*` token, which is what puts
347 + * `os-toolbar-themed` on the body (PHP on first paint, the theme apply
348 + * pass on a live switch). Without that class none of this matches, so
349 + * a scheme's toolbar stays the scheme's under every other theme.
350 + *
351 + * The fallbacks are Core's own values for the default scheme.
352 + */
353 +body.os-active.os-toolbar-themed #wpadminbar {
354 + background: var(--os-toolbar-bg, #1d2327);
355 + box-shadow: var(--os-toolbar-shadow, none);
356 + -webkit-font-smoothing: var(--os-font-smoothing, subpixel-antialiased);
357 +}
358 +
359 +body.os-active.os-toolbar-themed #wpadminbar :not(.ab-icon):not([class*="dashicons"]) {
360 + font-family: var(--os-toolbar-font, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif);
361 + font-size: var(--os-toolbar-font-size, 13px);
362 +}
363 +
364 +/*
365 + * Core sets the bar's line heights in ems against its 13px type. A
366 + * face at another size keeps Core's pixel lines (the bar's height for a
367 + * top-level item, 26px for a menu row), or its labels ride high or low
368 + * against the icons. A face whose metrics sit differently from the
369 + * system font's nudges its labels with `--os-toolbar-line-height`.
370 + */
371 +body.os-active.os-toolbar-themed #wpadminbar .ab-top-menu > li > .ab-item,
372 +body.os-active.os-toolbar-themed #wpadminbar .ab-top-menu > li > .ab-item :not(.ab-icon):not(img) {
373 + line-height: var(--os-toolbar-line-height, var(--wp-admin--admin-bar--height, 32px));
374 +}
375 +
376 +body.os-active.os-toolbar-themed #wpadminbar .quicklinks .menupop ul li .ab-item {
377 + line-height: 26px;
378 +}
379 +
380 +body.os-active.os-toolbar-themed #wpadminbar .ab-empty-item,
381 +body.os-active.os-toolbar-themed #wpadminbar a.ab-item,
382 +body.os-active.os-toolbar-themed #wpadminbar > #wp-toolbar span.ab-label,
383 +body.os-active.os-toolbar-themed #wpadminbar > #wp-toolbar span.noticon {
384 + color: var(--os-toolbar-fg, #f0f0f1);
385 +}
386 +
387 +body.os-active.os-toolbar-themed #wpadminbar .ab-icon::before,
388 +body.os-active.os-toolbar-themed #wpadminbar .ab-item::before,
389 +body.os-active.os-toolbar-themed #wpadminbar #adminbarsearch::before {
390 + color: var(--os-toolbar-icon, rgba(240, 246, 252, 0.6));
391 +}
392 +
393 +body.os-active.os-toolbar-themed #wpadminbar:not(.mobile) .ab-top-menu > li > .ab-item:focus,
394 +body.os-active.os-toolbar-themed #wpadminbar:not(.mobile) .ab-top-menu > li:hover > .ab-item,
395 +body.os-active.os-toolbar-themed #wpadminbar .ab-top-menu > li.hover > .ab-item {
396 + background: var(--os-toolbar-hover-bg, #2c3338);
397 + color: var(--os-toolbar-hover-fg, #72aee6);
398 +}
399 +
400 +body.os-active.os-toolbar-themed #wpadminbar:not(.mobile) > #wp-toolbar li:hover span.ab-label,
401 +body.os-active.os-toolbar-themed #wpadminbar > #wp-toolbar li.hover span.ab-label,
402 +body.os-active.os-toolbar-themed #wpadminbar li:hover .ab-icon::before,
403 +body.os-active.os-toolbar-themed #wpadminbar li:hover .ab-item::before,
404 +body.os-active.os-toolbar-themed #wpadminbar li.hover .ab-icon::before,
405 +body.os-active.os-toolbar-themed #wpadminbar li.hover .ab-item::before {
406 + color: var(--os-toolbar-hover-fg, #72aee6);
407 +}
408 +
409 +body.os-active.os-toolbar-themed #wpadminbar .menupop .ab-sub-wrapper {
410 + background: var(--os-toolbar-menu-bg, #2c3338);
411 + box-shadow: var(--os-toolbar-menu-shadow, 0 3px 5px rgba(0, 0, 0, 0.2));
412 +}
413 +
414 +/* The WordPress menu's lower group sits on a darker panel of its own. */
415 +body.os-active.os-toolbar-themed #wpadminbar .quicklinks .menupop ul.ab-sub-secondary,
416 +body.os-active.os-toolbar-themed #wpadminbar .quicklinks .menupop ul.ab-sub-secondary .ab-submenu {
417 + background: var(--os-toolbar-menu-bg, #3c434a);
418 +}
419 +
420 +body.os-active.os-toolbar-themed #wpadminbar .ab-submenu .ab-item,
421 +body.os-active.os-toolbar-themed #wpadminbar .quicklinks .menupop ul li a,
422 +body.os-active.os-toolbar-themed #wpadminbar .quicklinks .menupop ul li a strong {
423 + color: var(--os-toolbar-menu-fg, rgba(240, 246, 252, 0.7));
424 +}
425 +
426 +/*
427 + * The account menu's name lines. Core colours them by ID, which
428 + * out-ranks the rows above, so they are restated here, hover included.
429 + */
430 +body.os-active.os-toolbar-themed #wpadminbar #wp-admin-bar-user-info .display-name {
431 + color: var(--os-toolbar-menu-fg, #fff);
432 +}
433 +
434 +body.os-active.os-toolbar-themed #wpadminbar #wp-admin-bar-user-info .username {
435 + color: var(--os-toolbar-menu-fg-muted, #a7aaad);
436 +}
437 +
438 +body.os-active.os-toolbar-themed #wpadminbar #wp-admin-bar-user-info a:hover span,
439 +body.os-active.os-toolbar-themed #wpadminbar #wp-admin-bar-user-info a:focus span {
440 + color: var(--os-toolbar-menu-hover-fg, #72aee6);
441 +}
442 +
443 +body.os-active.os-toolbar-themed #wpadminbar .quicklinks .menupop ul li a:hover,
444 +body.os-active.os-toolbar-themed #wpadminbar .quicklinks .menupop ul li a:focus,
445 +body.os-active.os-toolbar-themed #wpadminbar .quicklinks .menupop.hover ul li a:hover,
446 +body.os-active.os-toolbar-themed #wpadminbar .quicklinks .menupop.hover ul li a:focus {
447 + background: var(--os-toolbar-menu-hover-bg, transparent);
448 + color: var(--os-toolbar-menu-hover-fg, #72aee6);
449 +}
450 +
451 +/*
452 + * The time, for a theme that draws the toolbar as a menu bar
453 + * (`src/desktop-themes/toolbar-clock.ts`). It is not a control, so it
454 + * does not light up under the pointer.
455 + */
456 +body.os-active.os-toolbar-themed #wpadminbar #wp-admin-bar-os-clock {
457 + display: var(--os-toolbar-clock-display, none);
458 +}
459 +
460 +body.os-active.os-toolbar-themed #wpadminbar #wp-admin-bar-os-clock > .ab-item {
461 + background: none;
462 + color: var(--os-toolbar-fg, #f0f0f1);
463 +}
464 +
465 +/*
350 466 * Align the admin-bar W logo with the dock below it.
351 467 *
352 468 * Without this rule the admin bar's leftmost item (the WordPress W)
353 469 * renders at its Core-dictated ~44 px width, while the dock below it
@@ -359,8 +475,12 @@
359 475 * W inside, so the two columns share an axis. `--os-dock-width`
360 476 * is now set on :root (see settings.ts → apply()) so it reaches
361 477 * siblings of #os-shell like the admin bar.
362 478 */
479 +body.os-active #wpadminbar #wp-admin-bar-menu-toggle {
480 + display: none;
481 +}
482 +
363 483 body.os-active #wpadminbar #wp-admin-bar-wp-logo {
364 484 width: var( --os-dock-width, 56px );
365 485 }
366 486
@@ -380,8 +500,16 @@
380 500 margin: 0;
381 501 }
382 502
383 503 /*
504 + * Core nudges the W down 2px to centre it from the top of its cell.
505 + * The flex cell above already centres it, so the nudge would sit it low.
506 + */
507 +body.os-active #wpadminbar #wp-admin-bar-wp-logo > .ab-item > .ab-icon::before {
508 + top: 0;
509 +}
510 +
511 +/*
384 512 * Classic wpcontent carries a `margin-left: 160px` for the sidebar. With
385 513 * the sidebar hidden we let wpcontent fall back to flush — the shell is
386 514 * fixed-positioned anyway so this doesn't change the shell's layout, it
387 515 * just prevents a phantom sidebar gutter in any stray visible content.
@@ -429,8 +557,14 @@
429 557 * See `--os-backstop`.
430 558 */
431 559 background: var(--os-backstop, #1d2327);
432 560 /*
561 + * A bitmap face drawn on whole pixels wants plain greyscale
562 + * smoothing; the fallback is what the shell inherits from Core's
563 + * `#wpwrap` anyway.
564 + */
565 + -webkit-font-smoothing: var(--os-font-smoothing, subpixel-antialiased);
566 + /*
433 567 * Shell typeface. Every piece of chrome that doesn't set its own
434 568 * face inherits from here — dock labels, desktop icon labels,
435 569 * widgets, the overview. Windows override with `--os-ui-font` on
436 570 * their body, so a theme can run a display face on the desk and a
@@ -828,9 +962,10 @@
828 962 background-image: var(--os-tile-image, none);
829 963 background-repeat: var(--os-tile-image-repeat, no-repeat);
830 964 background-size: var(--os-tile-image-size, auto);
831 965 background-position: var(--os-tile-image-position, center);
832 - color: var(--os-fg, #fff);
966 + /* Also the glyph: silhouette art is masked with `currentColor`. */
967 + color: var(--os-desk-fg, var(--os-fg, #fff));
833 968 cursor: pointer;
834 969 border-radius: 8px;
835 970 transition: background-color 0.15s ease;
836 971 /* Positioning context for `.os-icon__badge`. */
@@ -892,9 +1027,9 @@
892 1027 .os-icon:focus-visible {
893 1028 /* background-COLOR, not the shorthand: the shorthand would reset
894 1029 * the ICON_TILE texture declared above, so a themed tile would
895 1030 * vanish on hover. */
896 - background-color: rgba(255, 255, 255, 0.12);
1031 + background-color: var(--os-icon-hover-bg, rgba(255, 255, 255, 0.12));
897 1032 outline: none;
898 1033 }
899 1034
900 1035 .os-icon:focus-visible {
@@ -918,12 +1053,20 @@
918 1053 max-height: 48px;
919 1054 object-fit: contain;
920 1055 }
921 1056
1057 +.os-icon__image {
1058 + filter: var(--os-icon-glyph-shadow, none);
1059 +}
1060 +
922 1061 .os-icon__label {
923 - font-size: 12px;
1062 + font-size: var(--os-icon-label-font-size, 12px);
924 1063 text-align: center;
925 - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
1064 + background: var(--os-icon-label-bg, transparent);
1065 + border-radius: var(--os-icon-label-radius, 6px);
1066 + /* Inline only: a two-line caption already fills the 96px grid row. */
1067 + padding: var(--os-icon-label-padding, 0 6px);
1068 + text-shadow: var(--os-icon-label-shadow, 0 1px 2px rgba(0, 0, 0, 0.45));
926 1069 line-height: 1.2;
927 1070 word-break: break-word;
928 1071 }
929 1072
@@ -935,21 +1078,38 @@
935 1078 * drag / resize / click-through behaviour is unaffected.
936 1079 * --------------------------------------------------------------- */
937 1080
938 1081 .os-widgets {
939 - border-radius: 14px;
940 1082 position: absolute;
941 1083 /*
942 1084 * Inside the work area vertically — see `.os-icons` for why the
943 - * insets are here. The inline edge stays a plain 16px: the column
944 - * hugs the trailing side, which is a logical choice, while the
945 - * side insets are physical; and a side rail never claims one
946 - * anyway (it is a flex sibling, the area is already narrower).
1085 + * insets are here. The inline edge is logical: the column hugs
1086 + * the trailing side, while the side insets are physical; and a
1087 + * side rail never claims one anyway (it is a flex sibling, the
1088 + * area is already narrower).
1089 + *
1090 + * The cards sit 16px in from the area's top and trailing edges
1091 + * and 16px above the work area's foot, but the box reaches
1092 + * further than that and pads back in. The column scrolls, and a
1093 + * scroll container clips everything inside it to its padding
1094 + * box, the cards' drop shadow included: with the box hugging the
1095 + * cards, the shadow ended in a hard rectangle the size of the
1096 + * column, easy to see on a light wallpaper. The padding is the
1097 + * room the shadow needs (it reaches 28px out and 36px down, more
1098 + * on hover). The trailing side only gets the 16px to the area's
1099 + * edge, since the area clips there anyway. The column is
1100 + * pointer-transparent, so the extra room catches nothing; the
1101 + * add-widget pill measures the content box, not this one.
947 1102 */
948 - top: calc( var( --os-work-area-inset-top, 0px ) + 16px );
949 - bottom: calc( var( --os-work-area-inset-bottom, 80px ) + 16px );
950 - inset-inline-end: 16px;
1103 + box-sizing: content-box;
1104 + top: var( --os-work-area-inset-top, 0px );
1105 + bottom: calc( var( --os-work-area-inset-bottom, 80px ) - 24px );
1106 + inset-inline-end: 0;
951 1107 width: 320px;
1108 + --_os-widgets-room-start: 40px;
1109 + --_os-widgets-room-end: 16px;
1110 + padding-block: 16px 40px;
1111 + padding-inline: var( --_os-widgets-room-start ) var( --_os-widgets-room-end );
952 1112 display: flex;
953 1113 flex-direction: column;
954 1114 gap: 12px;
955 1115 z-index: 1;
@@ -1014,16 +1174,20 @@
1014 1174 background-image: var( --os-widget-image, none );
1015 1175 background-repeat: var( --os-widget-image-repeat, repeat );
1016 1176 background-size: var( --os-widget-image-size, auto );
1017 1177 background-position: var( --os-widget-image-position, center );
1018 - backdrop-filter: blur( 18px ) saturate( 140% );
1019 - -webkit-backdrop-filter: blur( 18px ) saturate( 140% );
1020 - border: 1px solid rgba( 255, 255, 255, 0.08 );
1021 - border-radius: 14px;
1022 - color: var( --os-ui-fg-on-accent, #fff );
1023 - box-shadow:
1178 + backdrop-filter: var( --os-widget-card-backdrop, blur( 18px ) saturate( 140% ) );
1179 + -webkit-backdrop-filter: var( --os-widget-card-backdrop, blur( 18px ) saturate( 140% ) );
1180 + border: var( --os-widget-card-border, 1px solid rgba( 255, 255, 255, 0.08 ) );
1181 + border-radius: var( --os-widget-card-radius, 14px );
1182 + /* The widget contract's text colour, which is what a widget's own
1183 + * stylesheet is told to read. */
1184 + color: var( --os-ui-color-text, var( --os-ui-fg-on-accent, #fff ) );
1185 + box-shadow: var(
1186 + --os-widget-card-shadow,
1024 1187 0 8px 28px rgba( 0, 0, 0, 0.35 ),
1025 - inset 0 0 0 1px rgba( 255, 255, 255, 0.04 );
1188 + inset 0 0 0 1px rgba( 255, 255, 255, 0.04 )
1189 + );
1026 1190 transition:
1027 1191 box-shadow 0.18s ease,
1028 1192 opacity 0.18s ease;
1029 1193 }
@@ -1031,11 +1195,13 @@
1031 1195 /* Hover feedback is shadow-only — no transform. Cards must feel
1032 1196 * anchored; a hover nudge reads as the widget drifting under the
1033 1197 * pointer (and fought the user's chosen position on floating cards). */
1034 1198 .os-widgets__card:hover {
1035 - box-shadow:
1199 + box-shadow: var(
1200 + --os-widget-card-shadow-hover,
1036 1201 0 12px 36px rgba( 0, 0, 0, 0.45 ),
1037 - inset 0 0 0 1px rgba( 255, 255, 255, 0.08 );
1202 + inset 0 0 0 1px rgba( 255, 255, 255, 0.08 )
1203 + );
1038 1204 }
1039 1205
1040 1206 .os-widgets__card-body {
1041 1207 padding: 16px;
@@ -1074,8 +1240,21 @@
1074 1240 .os-widgets__card-close:focus-visible {
1075 1241 opacity: 1;
1076 1242 }
1077 1243
1244 +/*
1245 + * The corner close button rests at `opacity: 0` and only the card's
1246 + * hover reveals it, so on touch it is invisible and the widget cannot
1247 + * be dismissed at all. Rest it at the same 0.7 the chrome header's
1248 + * close button uses there, rather than a full 1, so a card that is
1249 + * only being read does not wear a hard Ă— in its corner.
1250 + */
1251 +@media ( hover: none ) {
1252 + .os-widgets__card-close {
1253 + opacity: 0.7;
1254 + }
1255 +}
1256 +
1078 1257 .os-widgets__card-close:hover {
1079 1258 background: var( --os-ui-danger, #d63638 );
1080 1259 color: var( --os-ui-fg-on-accent, #fff );
1081 1260 }
@@ -1188,9 +1367,9 @@
1188 1367 .os-widgets__card-redock:hover,
1189 1368 .os-widgets__card-redock:focus-visible {
1190 1369 opacity: 1;
1191 1370 background: var( --wp-admin-theme-color, #2271b1 );
1192 - color: var( --os-ui-fg-on-accent, #fff );
1371 + color: var( --os-ui-accent-ink, var( --os-ui-fg-on-accent, #fff ) );
1193 1372 }
1194 1373
1195 1374 /*
1196 1375 * Floating widgets — absolute-positioned, inline top/left/width/height
@@ -1320,10 +1499,14 @@
1320 1499 */
1321 1500 .os-widgets__add {
1322 1501 position: absolute;
1323 1502 top: 0;
1324 - left: 50%;
1325 - transform: translateX( -50% );
1503 + /* Centred over the cards, not over the column's box: the column
1504 + * pads out unevenly to give the card shadows room (see
1505 + * `.os-widgets`), so the box's own middle is off to one side. */
1506 + inset-inline: var( --_os-widgets-room-start, 0px ) var( --_os-widgets-room-end, 0px );
1507 + width: fit-content;
1508 + margin-inline: auto;
1326 1509 padding: 8px 22px;
1327 1510 display: flex;
1328 1511 align-items: center;
1329 1512 justify-content: center;
@@ -1328,12 +1511,17 @@
1328 1511 align-items: center;
1329 1512 justify-content: center;
1330 1513 gap: 7px;
1331 1514 pointer-events: none;
1332 - background: transparent;
1333 - border: 1px dashed rgba( 255, 255, 255, 0.22 );
1515 + /* `.os-widgets__card`'s glass, to the number: the pill trails a
1516 + stack of those cards. Its dashed edge is what makes it a slot. */
1517 + background: var( --os-widgets-add-bg, transparent );
1518 + backdrop-filter: blur( 18px ) saturate( 140% );
1519 + -webkit-backdrop-filter: blur( 18px ) saturate( 140% );
1520 + box-shadow: 0 8px 28px var( --os-widgets-add-shadow, rgba( 0, 0, 0, 0.35 ) );
1521 + border: 1px dashed var( --os-widgets-add-border, rgba( 255, 255, 255, 0.22 ) );
1334 1522 border-radius: 10px;
1335 - color: rgba( 255, 255, 255, 0.72 );
1523 + color: var( --os-ui-color-text, var( --os-fg, #fff ) );
1336 1524 font: inherit;
1337 1525 font-weight: 500;
1338 1526 cursor: pointer;
1339 1527 opacity: 0;
@@ -1371,10 +1559,10 @@
1371 1559 }
1372 1560
1373 1561 .os-widgets__add:hover {
1374 1562 border-color: rgba( 255, 255, 255, 0.4 );
1375 - background: rgba( 255, 255, 255, 0.06 );
1376 - color: var( --os-ui-fg-on-accent, #fff );
1563 + background: var( --os-widgets-add-bg-hover, rgba( 255, 255, 255, 0.06 ) );
1564 + color: var( --os-fg, #fff );
1377 1565 }
1378 1566
1379 1567 .os-widgets__add:focus-visible {
1380 1568 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
@@ -1412,9 +1600,9 @@
1412 1600
1413 1601 .os-widget-clock__date {
1414 1602 margin-top: 4px;
1415 1603 font-size: 13px;
1416 - color: rgba( 255, 255, 255, 0.75 );
1604 + color: var( --os-ui-color-text-subtle, rgba( 255, 255, 255, 0.75 ) );
1417 1605 letter-spacing: 0.02em;
1418 1606 }
1419 1607
1420 1608 /* ---------------------------------------------------------------
@@ -1424,9 +1612,13 @@
1424 1612 * --------------------------------------------------------------- */
1425 1613
1426 1614 .os-widget-picker {
1427 1615 position: fixed;
1428 - width: 320px;
1616 + /* Border-box so the picker script can cap max-height to the
1617 + * room it measured, frame included. 342 = the 320px list plus
1618 + * the padding and border it always carried. */
1619 + box-sizing: border-box;
1620 + width: 342px;
1429 1621 max-height: 60vh;
1430 1622 overflow-y: auto;
1431 1623 padding: 10px;
1432 1624 border-radius: 14px;
@@ -1742,5 +1934,328 @@
1742 1934 .os-shell--revealing #os-area,
1743 1935 .os-shell--revealing .os-dock {
1744 1936 transition: none;
1745 1937 }
1938 +}
1939 +
1940 +/* A residency frame follows the window body without scrolling with its contents. */
1941 +.os-mio-residence {
1942 + position: absolute;
1943 + z-index: 50;
1944 + pointer-events: none;
1945 + overflow: hidden;
1946 + container-type: inline-size;
1947 +}
1948 +
1949 +.os-mio-residence[hidden],
1950 +.os-mio-chat-launcher[hidden] {
1951 + display: none;
1952 +}
1953 +
1954 +.os-mio-chat-launcher {
1955 + position: absolute;
1956 + inset-inline-end: 20px;
1957 + inset-block-end: 18px;
1958 + pointer-events: auto;
1959 +}
1960 +
1961 +.os-mio-chat {
1962 + position: absolute;
1963 + inset-inline-end: 16px;
1964 + inset-block-end: 16px;
1965 + inline-size: min(380px, calc(100% - 32px));
1966 + max-block-size: calc(100% - 32px);
1967 + box-sizing: border-box;
1968 + display: flex;
1969 + flex-direction: column;
1970 + gap: 12px;
1971 + padding: 18px;
1972 + pointer-events: auto;
1973 + color: var(--os-mio-chat-fg, #1d2327);
1974 + background: var(--os-mio-chat-bg, #fff);
1975 + border: 1px solid var(--os-mio-chat-border, #c3c4c7);
1976 + border-radius: var(--os-mio-chat-radius, 16px);
1977 + box-shadow: 0 8px 40px var(--os-mio-chat-glow, #00000020);
1978 + animation: os-mio-chat-enter 180ms ease-out;
1979 +}
1980 +
1981 +.os-mio-chat header {
1982 + display: flex;
1983 + align-items: center;
1984 + gap: 12px;
1985 +}
1986 +
1987 +.os-mio-chat header strong {
1988 + letter-spacing: .14em;
1989 +}
1990 +
1991 +.os-mio-chat header span {
1992 + flex: 1;
1993 + font-size: 11px;
1994 + color: var(--os-mio-chat-muted, #646970);
1995 +}
1996 +
1997 +.os-mio-chat__log {
1998 + position: relative;
1999 + min-block-size: 60px;
2000 + max-block-size: 300px;
2001 + overflow: auto;
2002 + overscroll-behavior: contain;
2003 +}
2004 +
2005 +.os-mio-chat__message {
2006 + margin-block: 0 10px;
2007 + padding: 10px 12px;
2008 + line-height: 1.6;
2009 + white-space: pre-wrap;
2010 + overflow-wrap: anywhere;
2011 +}
2012 +
2013 +.os-mio-chat__message--user {
2014 + border-radius: 16px 16px 4px 16px;
2015 + background: var(--os-mio-chat-user-bg, #f0f0f1);
2016 +}
2017 +
2018 +.os-mio-chat__composer {
2019 + display: flex;
2020 + align-items: center;
2021 + gap: 8px;
2022 +}
2023 +
2024 +.os-mio-chat__composer os-textarea {
2025 + flex: 1;
2026 + min-inline-size: 0;
2027 +}
2028 +
2029 +.os-mio-chat__status,
2030 +.os-mio-chat small {
2031 + margin: 0;
2032 + font-size: 11px;
2033 + line-height: 1.5;
2034 + color: var(--os-mio-chat-muted, #646970);
2035 +}
2036 +
2037 +@keyframes os-mio-chat-enter {
2038 + from { opacity: 0; transform: translateY(6px) scale(.98); }
2039 + to { opacity: 1; transform: translateY(0) scale(1); }
2040 +}
2041 +
2042 +@media (prefers-reduced-motion: reduce) {
2043 + .os-mio-chat { animation: none; }
2044 +}
2045 +
2046 +.os-mio-residence > .os-mio { z-index: 0; }
2047 +.os-mio-chat, .os-mio-chat-launcher { z-index: 1; }
2048 +
2049 +.os-mio-chat [hidden] {
2050 + display: none !important;
2051 +}
2052 +
2053 +.os-mio-chat__message--assistant {
2054 + white-space: normal;
2055 +}
2056 +
2057 +.os-mio-chat__message p {
2058 + margin-block: 0 8px;
2059 +}
2060 +
2061 +.os-mio-chat__message ul,
2062 +.os-mio-chat__message ol {
2063 + margin-block: 8px;
2064 + padding-inline-start: 20px;
2065 +}
2066 +
2067 +.os-mio-chat__message a {
2068 + color: inherit;
2069 + text-decoration: underline;
2070 +}
2071 +
2072 +/* Shared MIO chrome: the same portrait with a softly revealed off-state slash. */
2073 +.os-mio-window-toggle svg {
2074 + width: 20px;
2075 + height: 20px;
2076 + overflow: visible;
2077 +}
2078 +
2079 +.os-mio-window-toggle__slash {
2080 + fill: none;
2081 + stroke: currentColor;
2082 + stroke-width: 2.4;
2083 + stroke-linecap: round;
2084 + opacity: 0;
2085 + stroke-dasharray: 26;
2086 + stroke-dashoffset: 26;
2087 + transition: opacity 240ms ease, stroke-dashoffset 240ms ease;
2088 +}
2089 +
2090 +.os-mio-window-toggle[data-mio-disabled="true"] .os-mio-window-toggle__slash {
2091 + opacity: 1;
2092 + stroke-dashoffset: 0;
2093 +}
2094 +
2095 +.os-mio-chat__activity {
2096 + display: flex;
2097 + align-items: center;
2098 + gap: 8px;
2099 + min-height: 18px;
2100 +}
2101 +
2102 +.os-mio-chat__thinking {
2103 + display: inline-flex;
2104 + align-items: center;
2105 + gap: 3px;
2106 + width: 24px;
2107 + flex-shrink: 0;
2108 + opacity: 0;
2109 + color: var(--os-mio-chat-muted, #646970);
2110 + transition: opacity 240ms ease;
2111 +}
2112 +
2113 +.os-mio-chat[data-thinking="true"] .os-mio-chat__thinking {
2114 + opacity: 1;
2115 +}
2116 +
2117 +.os-mio-chat__thinking i {
2118 + width: 4px;
2119 + height: 4px;
2120 + border-radius: 50%;
2121 + background: currentColor;
2122 + animation: os-mio-thinking 1.6s ease-in-out infinite;
2123 + animation-play-state: paused;
2124 +}
2125 +
2126 +.os-mio-chat__thinking i:nth-child(2) { animation-delay: 160ms; }
2127 +.os-mio-chat__thinking i:nth-child(3) { animation-delay: 320ms; }
2128 +.os-mio-chat[data-thinking="true"] .os-mio-chat__thinking i { animation-play-state: running; }
2129 +.os-mio-window-toggle[data-mio-thinking="true"] circle { animation: os-mio-thinking 2.4s ease-in-out infinite; }
2130 +
2131 +@keyframes os-mio-thinking {
2132 + 0%, 100% { opacity: 0.45; }
2133 + 50% { opacity: 1; }
2134 +}
2135 +
2136 +@media (prefers-reduced-motion: reduce) {
2137 + .os-mio-window-toggle__slash,
2138 + .os-mio-chat__thinking { transition: none; }
2139 + .os-mio-chat__thinking i,
2140 + .os-mio-window-toggle[data-mio-thinking="true"] circle { animation: none; }
2141 +}
2142 +
2143 +.os-mio-window-toggle {
2144 + max-inline-size: 30px;
2145 + opacity: 1;
2146 + visibility: visible;
2147 + transition: opacity 180ms ease, max-inline-size 240ms ease, visibility 0s;
2148 +}
2149 +
2150 +.os-mio-window-toggle[data-mio-available="false"] {
2151 + max-inline-size: 0;
2152 + opacity: 0;
2153 + visibility: hidden;
2154 + overflow: hidden;
2155 + pointer-events: none;
2156 + transition: opacity 180ms ease, max-inline-size 240ms ease, visibility 0s 180ms;
2157 +}
2158 +
2159 +@media (prefers-reduced-motion: reduce) {
2160 + .os-mio-window-toggle,
2161 + .os-mio-window-toggle[data-mio-available="false"] { transition: none; }
2162 +}
2163 +
2164 +/* The launcher stays available; the companion only speaks when invited. */
2165 +.os-mio > canvas,
2166 +.os-mio > .os-mio__handle {
2167 + transition: opacity 180ms ease, visibility 180ms;
2168 +}
2169 +
2170 +.os-mio[data-mio-visible="false"] > canvas,
2171 +.os-mio[data-mio-visible="false"] > .os-mio__handle {
2172 + opacity: 0;
2173 + visibility: hidden;
2174 + pointer-events: none;
2175 +}
2176 +
2177 +.os-mio-callout {
2178 + position: absolute;
2179 + z-index: 2;
2180 + display: flex;
2181 + align-items: center;
2182 + gap: 10px;
2183 + max-inline-size: calc(100% - 16px);
2184 + box-sizing: border-box;
2185 + padding-block: 6px;
2186 + padding-inline: 15px 6px;
2187 + border: 1px solid var(--os-mio-callout-border, #d6b8cb);
2188 + border-radius: 22px;
2189 + background: var(--os-mio-callout-bg, #ffe0cf);
2190 + color: var(--os-mio-callout-fg, #382b38);
2191 + box-shadow: 0 4px 16px var(--os-mio-chat-glow, #00000020);
2192 + font-size: 12px;
2193 + line-height: 1.5;
2194 + font-weight: 500;
2195 + pointer-events: auto;
2196 + animation: os-mio-chat-enter 180ms ease-out;
2197 +}
2198 +
2199 +.os-mio-callout os-button::part(button) {
2200 + inline-size: 26px;
2201 + block-size: 26px;
2202 + font-size: 17px;
2203 + line-height: 1;
2204 +}
2205 +
2206 +/* A small rounded tail joins the companion without a second outlined shape. */
2207 +.os-mio-callout::after {
2208 + content: "";
2209 + position: absolute;
2210 + inset-block-end: -4px;
2211 + inset-inline-end: 18px;
2212 + inline-size: 8px;
2213 + block-size: 8px;
2214 + background: inherit;
2215 + border-inline-end: inherit;
2216 + border-block-end: inherit;
2217 + border-end-end-radius: 3px;
2218 + transform: rotate(45deg);
2219 +}
2220 +
2221 +.os-mio-callout[hidden] { display: none; }
2222 +
2223 +@media (prefers-reduced-motion: reduce) {
2224 + .os-mio > canvas,
2225 + .os-mio > .os-mio__handle { transition: none; }
2226 + .os-mio-callout { animation: none; }
2227 +}
2228 +
2229 +/* Response controls belong to their message; all layout follows inline flow. */
2230 +.os-mio-chat__actions {
2231 + display: flex;
2232 + flex-wrap: wrap;
2233 + gap: 8px;
2234 + margin-block-start: 12px;
2235 +}
2236 +
2237 +.os-mio-chat__action {
2238 + display: flex;
2239 + flex-direction: column;
2240 + align-items: flex-start;
2241 + gap: 4px;
2242 + min-inline-size: 0;
2243 + max-inline-size: 100%;
2244 +}
2245 +
2246 +.os-mio-chat__action os-button::part(button) {
2247 + white-space: normal;
2248 + overflow-wrap: anywhere;
2249 +}
2250 +
2251 +.os-mio-chat__action-status {
2252 + font-size: 12px;
2253 + color: var(--os-mio-chat-muted, #646970);
2254 + overflow-wrap: anywhere;
2255 +}
2256 +
2257 +.os-mio-chat__composer os-textarea::part(textarea) {
2258 + white-space: pre-wrap;
2259 + overflow-wrap: anywhere;
2260 + overflow-x: hidden;
1746 2261 }