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 / my-wordpress.css

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

2,955 lines 76.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * My WordPress — window-local layout.
3 *
4 * Two-pane file-explorer for browsing WordPress entities. Mirrors the
5 * Recycle Bin and Posts window CSS conventions (`os-*`
6 * BEM-ish prefix, container-relative geometry, theming via the
7 * os-variables CSS custom properties).
8 *
9 */
10
11 .desktop-mode-my-wordpress {
12 display: flex;
13 flex-direction: column;
14 width: 100%;
15 height: 100%;
16 min-height: 0;
17 font-family: var(
18 --os-font,
19 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif
20 );
21 color: var( --os-my-wordpress-fg, var( --os-ui-fg, #1d2327 ) );
22 /* Window background — separate from the wallpaper bg so the
23 * folder window reads as a real OS window even when the desktop
24 * gradient is dark. Themes / plugins can override
25 * `--os-my-wordpress-bg` to repaint just this surface
26 * without touching every other window. */
27 background: var(
28 --os-my-wordpress-bg,
29 var( --os-window-bg, #fff )
30 );
31 /* Tile color tokens — same recipe as the folder window. Both
32 * chain through the palette so the surface follows the active
33 * desktop theme, with the pre-brand WordPress-admin literal as
34 * the floor if no stylesheet declares one.
35 *
36 * `--os-tile-fg-muted` used to be the bare literal below, which
37 * outranked the palette's own value and painted every tile's
38 * secondary line near-black — invisible on a dark station, while
39 * the label right beside it followed the theme correctly. A
40 * colour declared next to the thing it paints is out of reach of
41 * the palette AND of every desktop theme. */
42 --os-tile-fg: var( --os-my-wordpress-fg, var( --os-ui-fg, #1d2327 ) );
43 --os-tile-fg-muted: var(
44 --os-my-wordpress-fg-muted,
45 var( --os-ui-fg-muted, rgba( 0, 0, 0, 0.55 ) )
46 );
47 --os-tile-hover-bg: var(
48 --os-hover,
49 rgba( 0, 0, 0, 0.06 )
50 );
51 /* Light-context label rendering — same recipe as the folder
52 * window. Defined as tokens in `variables.css`; the single
53 * `.os-file-tile__label` rule reads them for every
54 * surface. */
55 --os-tile-label-shadow: none;
56 --os-tile-label-weight: 500;
57 --os-tile-label-smoothing: antialiased;
58 --os-tile-label-color: var( --os-my-wordpress-fg, var( --os-ui-fg, #1d2327 ) );
59 }
60
61 /* Pinned-icon visual cue — used by `os-icon--pinned` (the
62 * generic flag added in 0.8.0). The icon-rail itself owns the
63 * non-draggable invariant; this rule just removes the grab affordance
64 * if the framework ever attaches one in a later phase.
65 */
66 .os-icon--pinned {
67 cursor: pointer;
68 }
69
70 /* Breadcrumb header chrome — owned by the shared
71 * `.os-breadcrumbs` rules in `desktop-files.css`. No
72 * per-surface duplication. */
73
74 /* ----- Body host ----- */
75
76 .os-my-wordpress__body {
77 flex: 1 1 auto;
78 min-height: 0;
79 overflow: hidden;
80 position: relative;
81 display: flex;
82 flex-direction: column;
83 }
84
85 /* List-view toolbar — sits between the breadcrumb header and the
86 * split pane in every entity list view (Posts, Pages, Users, Media).
87 * Holds the server-side search input today; reserved for future
88 * filter / sort controls. Visual treatment mirrors the Content
89 * Graph toolbar's search row so the two surfaces read as the same
90 * control. */
91 .os-my-wordpress__list-toolbar {
92 flex: 0 0 auto;
93 display: flex;
94 align-items: center;
95 gap: 8px;
96 padding: 8px 12px;
97 border-bottom: 1px solid var( --os-ui-border, #dcdcde );
98 /* Opaque and above the canvas: the tile canvas scrolls beneath
99 * this row, and a transparent toolbar let tiles read through it. */
100 position: relative;
101 z-index: 4;
102 background: var( --os-ui-bg, #fff );
103 }
104
105 .os-my-wordpress__list-toolbar-search {
106 position: relative;
107 flex: 1 1 auto;
108 min-width: 0;
109 max-width: 360px;
110 }
111
112 .os-my-wordpress__list-toolbar-search-input {
113 width: 100%;
114 padding: 6px 10px;
115 border: 1px solid var( --os-ui-border, #d8dde4 );
116 border-radius: 6px;
117 background: var( --os-ui-surface, #fff );
118 color: inherit;
119 font-size: 13px;
120 box-sizing: border-box;
121 }
122
123 .os-my-wordpress__list-toolbar-search-input:focus {
124 outline: none;
125 border-color: var( --os-ui-accent, #2c6be5 );
126 box-shadow: 0 0 0 3px var( --os-ui-accent-soft, rgba( 44, 107, 229, 0.18 ) );
127 }
128
129 /* When the list-toolbar mounts, the split pane needs to claim the
130 * remaining vertical space inside the flex column body — without
131 * this rule it would size to its content (which, for an empty list,
132 * is zero) and the tile canvas would collapse. */
133 .os-my-wordpress__body > .os-my-wordpress__split {
134 flex: 1 1 auto;
135 min-height: 0;
136 }
137
138 /* Search-in-progress dimming. The previous result set stays in place
139 * (no skeleton flash, no empty intermediate page) but reads as
140 * "stale" until the new page lands and the renderer atomically swaps
141 * the tiles. `pointer-events: none` blocks the user from clicking
142 * a tile that's about to disappear. */
143 .os-my-wordpress__tiles--searching,
144 .os-my-wordpress__media-grid--searching {
145 opacity: 0.45;
146 pointer-events: none;
147 transition: opacity 0.12s ease-out;
148 }
149
150 /* Reuse the file-system folder window's status-bar element
151 * (`.os-folder-status-bar`) so segments render with the
152 * exact same DOM the file-system uses — but retone it for the
153 * white window background. The dark default (white-on-dark) reads
154 * as invisible against `--os-window-bg`. */
155 .desktop-mode-my-wordpress > .os-folder-status-bar {
156 border-top: 1px solid var( --os-ui-border, #dcdcde );
157 background: transparent;
158 color: var( --os-ui-fg-muted, #50575e );
159 }
160
161 .desktop-mode-my-wordpress
162 > .os-folder-status-bar
163 button.os-folder-status-bar__segment:hover {
164 background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ) );
165 }
166
167 /* ----- Tile canvas (root + entity list) -----
168 *
169 * Both views use the SAME tile element (`.os-file-tile`)
170 * the wallpaper uses, so visuals stay 1:1 with the desktop. The
171 * canvas is `position: relative`; tiles are absolute-positioned and
172 * draggable — same interaction pattern as the wallpaper, just
173 * scoped to the window. Positions persist per-window in
174 * `localStorage` (see `createTileLayout` in src/my-wordpress).
175 */
176
177 .os-my-wordpress__canvas {
178 position: relative;
179 min-height: 100%;
180 box-sizing: border-box;
181 /* Note — DO NOT add `overflow: auto` here. Scroll is owned by
182 * the surrounding pane (`__list` for entity views, `__body` for
183 * the root view via `__grid` below). A second scroller here
184 * would steal the wheel from the outer pane and break the
185 * IntersectionObserver-based infinite scroll. */
186 }
187
188 .os-my-wordpress__grid {
189 height: 100%;
190 overflow-y: scroll;
191 }
192
193 /* Override the wallpaper file-tile palette for in-window context.
194 * On the wallpaper the file tile sits on a dark gradient and uses
195 * white text + a 12% white hover — both of which would be invisible
196 * on a white window background. Inside My WordPress we re-tone the
197 * same element to read on light surfaces. The dimensions / tile
198 * structure are inherited from `.os-file-tile`.
199 */
200 .desktop-mode-my-wordpress .os-file-tile {
201 /* Color + hover come from `--os-tile-*` — see the
202 * scope-level variable overrides on `.desktop-mode-my-wordpress`
203 * above. We only override structural / animation bits here.
204 *
205 * Drop the wallpaper's `transition: transform` — it hints at
206 * GPU compositing in Chrome, and the moment the layer is
207 * composited the label rasterizes against fractional device
208 * pixels (sub-pixel blur on HiDPI). We don't animate transform
209 * here, so the transition is dead code anyway. */
210 transition: background-color 0.15s ease;
211 transform: none;
212 }
213
214 /* Selected state for every tile in My WordPress is rendered by
215 * the canonical `.os-file-tile--selected` rule in
216 * `desktop-files.css` — same accent border + tinted background
217 * the folder windows + wallpaper tiles use. No section-specific
218 * override here — visual parity across the whole shell.
219 */
220
221 /* Large tiles — sections that opt in with `tileSize: 'large'`.
222 *
223 * A section whose rows carry a photograph (a shop's products) reads
224 * as a catalogue, not a file list: the picture is what's being
225 * scanned, and at 48px it's a thumbnail of a thumbnail. Roughly
226 * doubling the icon well also gives a corner ribbon room to be a
227 * corner flash instead of covering the subject.
228 *
229 * The cell pitch lives in `TILE_METRICS_LARGE` (src/my-wordpress/
230 * index.ts) and the two MUST move together — a wider tile in a cell
231 * that didn't grow overlaps its neighbour. */
232 /*
233 * Image-led sections re-point the tile tokens for their whole
234 * subtree rather than overriding the tile's width directly. The base
235 * `.os-file-tile` rule then sizes itself from them, and so does
236 * everything derived from them (the label's max-width, the loading
237 * skeletons) — one declaration instead of a chain of overrides that
238 * have to be kept in step.
239 */
240 .os-my-wordpress__tiles--large {
241 --os-tile-w: var( --os-tile-w-large, 132px );
242 --os-tile-h: var( --os-tile-h-large, 160px );
243 }
244
245 .os-my-wordpress__tiles--large .os-file-tile__visual,
246 .os-my-wordpress__tiles--large .os-file-tile__icon {
247 width: 104px;
248 height: 104px;
249 font-size: 64px;
250 }
251
252 .os-my-wordpress__tiles--large .os-file-tile__preview {
253 width: 104px;
254 height: 104px;
255 border-radius: 8px;
256 }
257
258 /* Three lines of title at this width instead of two — a product name
259 * is long and the extra width earns the room. Matches the taller
260 * cell in `TILE_METRICS_LARGE`. */
261 .os-my-wordpress__tiles--large .os-file-tile__label {
262 -webkit-line-clamp: 3;
263 }
264
265 /* Featured-image tiles. Entries that have a featured image render it
266 * in the icon well instead of the section dashicon — decisive for
267 * image-led types like WooCommerce products. The base
268 * `.os-file-tile__preview` rule in `desktop-files.css`
269 * already sizes and crops it; all that's added here is a hairline
270 * border so a light-on-light photo still reads as a distinct object
271 * against the wallpaper, plus a neutral well behind it so a
272 * transparent PNG or a slow-loading image doesn't flash the
273 * wallpaper through. */
274 .os-my-wordpress__tile--entry .os-file-tile__preview {
275 background: var(
276 --os-media-tile-bg,
277 rgba( 0, 0, 0, 0.05 )
278 );
279 /* The hairline was a flat 12%-alpha black — right on the pre-brand
280 * white canvas, and nothing at all on a dark one, where a black
281 * line over a near-black surface leaves the photo edgeless. It
282 * follows the border token now, so it inverts with the theme. */
283 box-shadow: inset 0 0 0 1px var( --os-ui-border, rgba( 0, 0, 0, 0.12 ) );
284 }
285
286 /* Banded list views. A section can split its tiles into labelled
287 * bands (WooCommerce Orders groups by status, needs-attention first)
288 * instead of one flat canvas. The root container stops being a
289 * positioned canvas and becomes a plain stack; each band owns its
290 * own canvas below its heading. */
291 .os-my-wordpress__tiles--banded {
292 position: static;
293 min-height: 0;
294 }
295
296 .os-my-wordpress__band + .os-my-wordpress__band {
297 margin-block-start: 8px;
298 }
299
300 /* Bands are laid out in order before the first page arrives so none
301 * appears mid-scroll, but one that hasn't received a row yet stays
302 * out of the way rather than showing a heading over nothing. */
303 .os-my-wordpress__band--empty {
304 display: none;
305 }
306
307 .os-my-wordpress__band-title {
308 display: flex;
309 align-items: center;
310 gap: 8px;
311 position: sticky;
312 /* Keeps the band label visible while scrolling a long band —
313 * the whole point of banding is knowing what you're looking at. */
314 top: 0;
315 /* Above the absolutely-positioned tiles of every band, including
316 * the ones that come later in the DOM. */
317 z-index: 3;
318 margin: 0;
319 padding: 6px 12px;
320 font-size: 11px;
321 font-weight: 600;
322 letter-spacing: 0.06em;
323 text-transform: uppercase;
324 color: var( --os-ui-fg-muted, #787c82 );
325 /* Near-opaque on purpose. Tiles scrolling underneath are behind
326 * the bar, but at 85% they still read as ghostly blurred shapes
327 * through it, which looks like a rendering fault rather than
328 * frosted glass. The blur + saturate keeps the frosted feel while
329 * the alpha stops the content beneath resolving into shapes. */
330 background: color-mix(
331 in srgb,
332 var( --os-ui-bg, #fff ) 94%,
333 transparent
334 );
335 backdrop-filter: blur( 14px ) saturate( 180% );
336 border-block-end: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) );
337 }
338
339 /* Browsers without `color-mix()` fall back to the flat surface —
340 * still opaque, just without the translucency. */
341 @supports not ( background: color-mix( in srgb, red 50%, transparent ) ) {
342 .os-my-wordpress__band-title {
343 background: var( --os-ui-bg, #fff );
344 }
345 }
346
347 /* Attention-carrying bands (WooCommerce's "Needs attention",
348 * "Problems") tint their bar so the eye lands there first instead of
349 * having to read every heading. */
350 .os-my-wordpress__band-title--warn {
351 color: var( --os-ui-warning, #8a6116 );
352 background: color-mix(
353 in srgb,
354 var( --os-ui-warning, #8a6116 ) 12%,
355 var( --os-ui-bg, #fff )
356 );
357 border-block-end-color: color-mix(
358 in srgb,
359 var( --os-ui-warning, #8a6116 ) 35%,
360 transparent
361 );
362 }
363
364 .os-my-wordpress__band-title--danger {
365 color: var( --os-ui-danger, #b32d2e );
366 background: color-mix(
367 in srgb,
368 var( --os-ui-danger, #b32d2e ) 12%,
369 var( --os-ui-bg, #fff )
370 );
371 border-block-end-color: color-mix(
372 in srgb,
373 var( --os-ui-danger, #b32d2e ) 35%,
374 transparent
375 );
376 }
377
378 .os-my-wordpress__band-count {
379 padding: 0 6px;
380 border-radius: 999px;
381 font-size: 11px;
382 line-height: 1.7;
383 background: color-mix( in srgb, currentColor 16%, transparent );
384 color: inherit;
385 }
386
387 /* Lock state — another user is currently editing this post. We dim
388 * the tile a touch and overlay a small dashicon-lock badge on the
389 * top-right of the icon. The whole tile stays clickable; the lock
390 * is just a visual heads-up so the user knows the takeover dialog
391 * is coming if they double-click. */
392 .os-my-wordpress__tile--locked {
393 opacity: 0.85;
394 }
395
396 .os-my-wordpress__tile-lock {
397 position: absolute;
398 top: 4px;
399 inset-inline-end: 14px;
400 width: 18px;
401 height: 18px;
402 font-size: 12px;
403 line-height: 18px;
404 text-align: center;
405 border-radius: 50%;
406 background: var( --os-danger, var( --os-ui-danger, #b32d2e ) );
407 color: var( --os-ui-fg-on-accent, #fff );
408 box-shadow:
409 0 0 0 1.5px rgba( 0, 0, 0, 0.4 ),
410 0 1px 3px rgba( 0, 0, 0, 0.35 );
411 pointer-events: none;
412 }
413
414 /* Hover-card lock banner — shown above the excerpt when present. The
415 * wash was a fixed 8%-alpha red, which on a dark card was a smear too
416 * faint to read as a banner at all; it follows the badge-danger token
417 * now, so it lands at the alpha the active theme picked. */
418 .os-my-wordpress__tooltip-lock {
419 display: flex;
420 align-items: center;
421 gap: 6px;
422 margin: 0 0 8px;
423 padding: 6px 8px;
424 border-radius: 4px;
425 background: var(
426 --os-my-wordpress-card-lock-bg,
427 rgba( 179, 45, 46, 0.08 )
428 );
429 color: var( --os-danger, var( --os-ui-danger, #b32d2e ) );
430 font-size: 12px;
431 font-weight: 600;
432 }
433
434 .os-my-wordpress__tooltip-lock .dashicons {
435 font-size: 14px;
436 width: 14px;
437 height: 14px;
438 }
439
440 /* Plain two-line clamp without `-webkit-line-clamp`. The webkit
441 * variant promotes the label to its own composited layer, which on
442 * HiDPI screens snaps text to fractional device pixels and renders
443 * blurry. `max-height: 2 × line-height` + `overflow: hidden` looks
444 * the same and stays sharp.
445 */
446 .os-my-wordpress__tile .os-file-tile__label {
447 font-size: 12px;
448 line-height: 1.3;
449 max-height: calc( 1.3em * 2 );
450 text-align: center;
451 overflow: hidden;
452 max-width: 100%;
453 word-break: break-word;
454 }
455
456 /* ----- Entity list: two-pane split ----- */
457
458 .os-my-wordpress__split {
459 display: grid;
460 grid-template-columns: minmax( 240px, 60% ) minmax( 0, 1fr );
461 height: 100%;
462 min-height: 0;
463 }
464
465 .os-my-wordpress__list {
466 overflow-y: scroll;
467 border-inline-end: 1px solid var( --os-ui-border, #dcdcde );
468 min-width: 0;
469 }
470
471 /* Always-visible scrollbar styling for the icon canvases. macOS
472 * defaults to overlay scrollbars that hide until a wheel/scroll
473 * event — fine for documents but actively confusing on a tile
474 * canvas where the user can't tell whether more icons live below
475 * or to the right. We force the scrollbar to render at all times
476 * via `::-webkit-scrollbar` (works in WebKit / Blink / Edge), and
477 * the explicit `scrollbar-width: thin` covers Firefox. */
478 .os-my-wordpress__list,
479 .os-my-wordpress__grid {
480 scrollbar-width: thin;
481 scrollbar-color: rgba( 0, 0, 0, 0.25 ) transparent;
482 }
483
484 .os-my-wordpress__list::-webkit-scrollbar,
485 .os-my-wordpress__grid::-webkit-scrollbar {
486 width: 12px;
487 height: 12px;
488 }
489
490 .os-my-wordpress__list::-webkit-scrollbar-thumb,
491 .os-my-wordpress__grid::-webkit-scrollbar-thumb {
492 background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.25 ) );
493 border-radius: 6px;
494 border: 3px solid transparent;
495 background-clip: padding-box;
496 }
497
498 .os-my-wordpress__list::-webkit-scrollbar-thumb:hover,
499 .os-my-wordpress__grid::-webkit-scrollbar-thumb:hover {
500 background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.4 ) );
501 background-clip: padding-box;
502 }
503
504 .os-my-wordpress__list::-webkit-scrollbar-track,
505 .os-my-wordpress__grid::-webkit-scrollbar-track {
506 background: transparent;
507 }
508
509 .os-my-wordpress__sentinel {
510 height: 1px;
511 }
512
513 /* Tile-grid loading skeleton — placeholder tiles that mimic the
514 * icon + label shape of what's about to load. We dropped the
515 * spinning WordPress mark here because it read as an alien
516 * affordance inside the file-explorer surface; a skeleton signals
517 * "tiles are landing in these slots" instead of "the system is
518 * busy doing something opaque."
519 *
520 * Each placeholder is parented directly to the positioned canvas
521 * (`__canvas--positioned`) and `position: absolute` at the
522 * (left, top) the next real tile will occupy — see
523 * `layout.peekNextCells()` and `showLoadingSkeleton()` in
524 * `src/my-wordpress/index.ts`. Per-tile shimmer delay and label
525 * width are inlined so a row of placeholders reads as a cascade,
526 * not a uniform pulse.
527 *
528 * Tile dimensions mirror the real `.os-file-tile` width
529 * so a placeholder sits cleanly in the cell its successor will
530 * inherit.
531 */
532 .os-my-wordpress__skeleton-tile {
533 position: absolute;
534 display: flex;
535 flex-direction: column;
536 align-items: center;
537 gap: 6px;
538 /* Same box as the real tile it stands in for — a skeleton that
539 * doesn't match the grid makes the list jump when rows land. */
540 width: var( --os-tile-w, 88px );
541 height: var( --os-tile-h, 104px );
542 padding: 8px 4px;
543 box-sizing: border-box;
544 pointer-events: none;
545 }
546
547 .os-my-wordpress__skeleton-icon,
548 .os-my-wordpress__skeleton-label {
549 background: linear-gradient(
550 90deg,
551 var( --os-skeleton-low, var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) ) ) 0%,
552 var( --os-skeleton-high, var( --os-ui-hover, rgba( 0, 0, 0, 0.13 ) ) ) 50%,
553 var( --os-skeleton-low, var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) ) ) 100%
554 );
555 background-size: 200% 100%;
556 animation: os-my-wordpress-skeleton-shimmer 1.4s ease-in-out
557 var( --os-skeleton-delay, 0s ) infinite;
558 }
559
560 .os-my-wordpress__skeleton-icon {
561 width: 44px;
562 height: 44px;
563 border-radius: 10px;
564 }
565
566 .os-my-wordpress__skeleton-label {
567 width: 72%;
568 height: 10px;
569 border-radius: 4px;
570 }
571
572 @keyframes os-my-wordpress-skeleton-shimmer {
573 from {
574 background-position: 200% 0;
575 }
576 to {
577 background-position: -200% 0;
578 }
579 }
580
581 @media ( prefers-reduced-motion: reduce ) {
582 .os-my-wordpress__skeleton-icon,
583 .os-my-wordpress__skeleton-label {
584 animation: none;
585 }
586 }
587
588 /* ----- Right pane: preview ----- */
589
590 .os-my-wordpress__preview {
591 overflow: auto;
592 min-width: 0;
593 background: transparent;
594 }
595
596 .os-my-wordpress__preview-empty {
597 display: flex;
598 flex-direction: column;
599 gap: 6px;
600 align-items: center;
601 justify-content: center;
602 height: 100%;
603 color: var( --os-ui-fg-muted, #787c82 );
604 font-size: 13px;
605 padding: 24px;
606 text-align: center;
607 }
608
609 /* Type / status breakdown under a multi-selection's count. */
610 .os-my-wordpress__preview-selection-breakdown {
611 font-size: 12px;
612 opacity: 0.75;
613 }
614
615 /* One labelled control in the bulk-edit modal. Label above field,
616 * because the fields are wide (a category tree, a tag row) and a
617 * two-column grid would put a one-word label beside a control four
618 * times its height. */
619 .os-my-wordpress__bulk-row {
620 display: flex;
621 flex-direction: column;
622 gap: 6px;
623 margin-block-end: 16px;
624 }
625
626 .os-my-wordpress__bulk-label {
627 font-size: 12px;
628 font-weight: 600;
629 color: var( --os-ui-fg-muted, #787c82 );
630 }
631
632 .os-my-wordpress__preview-loading {
633 display: flex;
634 align-items: center;
635 justify-content: center;
636 height: 100%;
637 min-height: 320px;
638 padding: 32px;
639 box-sizing: border-box;
640 }
641
642 /* Preview-pane loader — same scale curve as the tile-grid first
643 * spinner and the window-loading overlay. The JS used to hard-code
644 * `size="128"`, which set an inline `--os-ui-spinner-size` that
645 * overrode any CSS knob; the size attribute is now omitted so this
646 * rule is the single source of truth for both the My WordPress
647 * preview and the wallpaper file-preview pane (which reuses this
648 * class deliberately).
649 */
650 .os-my-wordpress__preview-loading os-spinner {
651 --os-ui-spinner-size: clamp( 96px, 14vw, 192px );
652 }
653
654 .os-my-wordpress__article {
655 padding: 24px 32px;
656 max-width: 720px;
657 margin: 0 auto;
658 }
659
660 .os-my-wordpress__article-title {
661 font-size: 22px;
662 margin: 0 0 8px;
663 line-height: 1.3;
664 }
665
666 .os-my-wordpress__article-meta {
667 margin: 0 0 16px;
668 color: var( --os-ui-fg-muted, #50575e );
669 font-size: 12px;
670 }
671
672 .os-my-wordpress__article-hero {
673 width: 100%;
674 height: auto;
675 border-radius: 6px;
676 margin-bottom: 16px;
677 display: block;
678 }
679
680 .os-my-wordpress__article-content {
681 font-size: 14px;
682 line-height: 1.6;
683 }
684
685 .os-my-wordpress__article-content img {
686 max-width: 100%;
687 height: auto;
688 }
689
690 .os-my-wordpress__article-footer {
691 margin-top: 24px;
692 padding-top: 16px;
693 border-top: 1px solid var( --os-ui-border, #dcdcde );
694 display: flex;
695 justify-content: flex-end;
696 align-items: center;
697 gap: 8px;
698 flex-wrap: wrap;
699 }
700
701 /* ----- Empty / error states ----- */
702
703 .os-my-wordpress__empty,
704 .os-my-wordpress__error {
705 padding: 32px 24px;
706 text-align: center;
707 color: var( --os-ui-fg-muted, #787c82 );
708 font-size: 13px;
709 }
710
711 .os-my-wordpress__error {
712 color: var( --os-danger, var( --os-ui-danger, #b32d2e ) );
713 }
714
715 /* ----- Hover card (floats over body) -----
716 *
717 * The card is summoned from a tile and belongs to the window that
718 * tile is in, so it follows the active desktop theme rather than the
719 * shell's tooltip chip. Every colour chains
720 * `--os-my-wordpress-card-*` first — derived in `variables.css` from
721 * the window family, so a theme that repaints the window repaints the
722 * card with it — then the chip tokens, then the pre-brand literal.
723 *
724 * It is appended to `document.body`, not to the window, because it is
725 * `position: fixed` and a window with `overflow: hidden` would clip
726 * it. Theme tokens still reach it: a desktop theme declares on
727 * `body.os-desktop-theme-<slug>`, an ancestor of both. What does NOT
728 * reach it is anything scoped to `.desktop-mode-my-wordpress`, which
729 * is why the card family is declared at palette level. */
730
731 .os-my-wordpress__tooltip {
732 position: fixed;
733 z-index: 100000;
734 max-width: 320px;
735 background: var(
736 --os-my-wordpress-card-bg,
737 var(
738 --os-tooltip-bg,
739 var( --os-surface, var( --os-ui-surface, #fff ) )
740 )
741 );
742 color: var(
743 --os-my-wordpress-card-fg,
744 var( --os-tooltip-fg, var( --os-fg, #1d2327 ) )
745 );
746 /* The card lands on the same colour as the window under it — the
747 * border and the shadow are the only reason it reads as a separate
748 * object, so neither is decoration. */
749 border: 1px solid var( --os-my-wordpress-card-border, #dcdcde );
750 border-radius: 6px;
751 padding: 12px;
752 box-shadow: var(
753 --os-my-wordpress-card-shadow,
754 0 6px 24px rgba( 0, 0, 0, 0.18 )
755 );
756 font-size: 12px;
757 pointer-events: none;
758 }
759
760 .os-my-wordpress__tooltip-title {
761 font-weight: 600;
762 margin-bottom: 6px;
763 font-size: 13px;
764 }
765
766 /* Same neutral well the entry and media tiles give their thumbnails:
767 * a transparent PNG or a still-loading image would otherwise show the
768 * card straight through, and a photo whose edge matches the card
769 * would have no boundary. */
770 .os-my-wordpress__tooltip-thumb {
771 display: block;
772 max-width: 100%;
773 height: auto;
774 border-radius: 4px;
775 margin: 6px 0;
776 background: var( --os-my-wordpress-card-thumb-bg, rgba( 0, 0, 0, 0.05 ) );
777 box-shadow: inset 0 0 0 1px var(
778 --os-my-wordpress-card-border,
779 #dcdcde
780 );
781 }
782
783 .os-my-wordpress__tooltip-excerpt {
784 margin: 0;
785 color: var( --os-my-wordpress-card-fg-muted, #50575e );
786 line-height: 1.4;
787 }
788
789 /* ----- Context menu host ----- */
790
791 .os-my-wordpress__menu {
792 position: fixed;
793 z-index: 100001;
794 }
795
796 /* ---------------------------------------------------------------
797 * User dossier — right-pane preview when a person is selected.
798 * Header (avatar + name + roles + links), bio, stat cards row,
799 * 12-month activity sparkline, milestones, recent posts, top
800 * categories. Designed to fit comfortably on a 360px-wide pane
801 * but to stretch nicely up to ~720px before the line-length stops
802 * helping readability.
803 * --------------------------------------------------------------- */
804
805 .os-my-wordpress__user {
806 max-width: 720px;
807 }
808
809 .os-my-wordpress__user-header {
810 display: flex;
811 gap: 16px;
812 align-items: center;
813 margin-bottom: 16px;
814 }
815
816 .os-my-wordpress__user-avatar {
817 width: 96px;
818 height: 96px;
819 border-radius: 50%;
820 flex-shrink: 0;
821 box-shadow: 0 2px 8px rgba( 0, 0, 0, 0.12 );
822 }
823
824 .os-my-wordpress__user-headline {
825 flex: 1 1 auto;
826 min-width: 0;
827 }
828
829 .os-my-wordpress__user
830 .os-my-wordpress__article-title {
831 margin: 0 0 4px;
832 }
833
834 .os-my-wordpress__user-roles {
835 display: flex;
836 flex-wrap: wrap;
837 gap: 6px;
838 margin-bottom: 6px;
839 }
840
841 .os-my-wordpress__user-role {
842 display: inline-flex;
843 align-items: center;
844 padding: 2px 8px;
845 border-radius: 10px;
846 font-size: 11px;
847 font-weight: 600;
848 text-transform: uppercase;
849 letter-spacing: 0.04em;
850 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 12%, transparent );
851 color: var( --wp-admin-theme-color, #2271b1 );
852 }
853
854 .os-my-wordpress__user-links {
855 display: flex;
856 gap: 12px;
857 font-size: 12px;
858 }
859
860 .os-my-wordpress__user-links a {
861 color: var( --os-link, var( --os-ui-accent, #2271b1 ) );
862 text-decoration: none;
863 }
864
865 .os-my-wordpress__user-links a:hover {
866 text-decoration: underline;
867 }
868
869 .os-my-wordpress__user-bio {
870 font-size: 14px;
871 line-height: 1.5;
872 color: var( --os-fg, #1d2327 );
873 margin: 0 0 16px;
874 padding-left: 12px;
875 border-left: 3px solid var( --os-ui-border, #dcdcde );
876 }
877
878 /* ----- Stat cards row ----- */
879
880 .os-my-wordpress__user-stats {
881 display: grid;
882 grid-template-columns: repeat( auto-fit, minmax( 120px, 1fr ) );
883 gap: 8px;
884 margin-bottom: 20px;
885 }
886
887 .os-my-wordpress__user-stat {
888 display: flex;
889 flex-direction: column;
890 gap: 2px;
891 padding: 12px 14px;
892 border-radius: 8px;
893 background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) ) );
894 border: 1px solid var( --os-ui-border, #dcdcde );
895 }
896
897 .os-my-wordpress__user-stat-value {
898 font-size: 26px;
899 font-weight: 700;
900 line-height: 1.1;
901 color: var( --wp-admin-theme-color, #2271b1 );
902 font-variant-numeric: tabular-nums;
903 }
904
905 .os-my-wordpress__user-stat-label {
906 font-size: 12px;
907 color: var( --os-ui-fg-muted, #50575e );
908 text-transform: uppercase;
909 letter-spacing: 0.04em;
910 }
911
912 .os-my-wordpress__user-stat-caption {
913 font-size: 11px;
914 color: var( --os-ui-fg-muted, #787c82 );
915 }
916
917 /* ----- 12-month activity sparkline ----- */
918
919 .os-my-wordpress__user-section {
920 margin-bottom: 20px;
921 }
922
923 .os-my-wordpress__user-section h3 {
924 font-size: 13px;
925 font-weight: 600;
926 color: var( --os-ui-fg-muted, #50575e );
927 text-transform: uppercase;
928 letter-spacing: 0.04em;
929 margin: 0 0 8px;
930 }
931
932 .os-my-wordpress__user-spark-chart {
933 display: grid;
934 grid-template-columns: repeat( 12, 1fr );
935 gap: 4px;
936 height: 80px;
937 align-items: end;
938 padding: 6px 0;
939 }
940
941 .os-my-wordpress__user-spark-col {
942 display: flex;
943 flex-direction: column;
944 align-items: stretch;
945 justify-content: flex-end;
946 gap: 4px;
947 height: 100%;
948 min-height: 0;
949 }
950
951 .os-my-wordpress__user-spark-bar {
952 background: linear-gradient(
953 180deg,
954 var( --wp-admin-theme-color, #2271b1 ) 0%,
955 color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 55%, transparent ) 100%
956 );
957 border-radius: 3px 3px 0 0;
958 min-height: 2px;
959 transition: height 0.3s ease;
960 }
961
962 .os-my-wordpress__user-spark-bar--empty {
963 background: var( --os-ui-border, #dcdcde );
964 min-height: 2px;
965 height: 2px !important;
966 }
967
968 .os-my-wordpress__user-spark-label {
969 font-size: 10px;
970 text-align: center;
971 color: var( --os-ui-fg-muted, #787c82 );
972 }
973
974 /* ----- Milestones ----- */
975
976 .os-my-wordpress__user-milestones {
977 display: grid;
978 grid-template-columns: repeat( auto-fit, minmax( 140px, 1fr ) );
979 gap: 12px;
980 margin: 0 0 20px;
981 }
982
983 .os-my-wordpress__user-milestones dt {
984 font-size: 11px;
985 color: var( --os-ui-fg-muted, #50575e );
986 text-transform: uppercase;
987 letter-spacing: 0.04em;
988 margin-bottom: 2px;
989 }
990
991 .os-my-wordpress__user-milestones dd {
992 font-size: 14px;
993 font-weight: 600;
994 margin: 0;
995 }
996
997 /* ----- Recent posts list ----- */
998
999 .os-my-wordpress__user-recent {
1000 list-style: none;
1001 margin: 0;
1002 padding: 0;
1003 display: flex;
1004 flex-direction: column;
1005 gap: 6px;
1006 }
1007
1008 .os-my-wordpress__user-recent li {
1009 display: flex;
1010 flex-direction: column;
1011 gap: 2px;
1012 padding: 8px 10px;
1013 border-radius: 6px;
1014 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.02 ) );
1015 }
1016
1017 .os-my-wordpress__user-recent a {
1018 font-weight: 600;
1019 color: var( --os-link, var( --os-ui-accent, #2271b1 ) );
1020 text-decoration: none;
1021 }
1022
1023 .os-my-wordpress__user-recent a:hover {
1024 text-decoration: underline;
1025 }
1026
1027 .os-my-wordpress__user-recent-meta {
1028 font-size: 11px;
1029 color: var( --os-ui-fg-muted, #787c82 );
1030 }
1031
1032 /* ----- Top categories chips ----- */
1033
1034 .os-my-wordpress__user-chips {
1035 display: flex;
1036 flex-wrap: wrap;
1037 gap: 6px;
1038 }
1039
1040 .os-my-wordpress__user-chip {
1041 display: inline-flex;
1042 align-items: center;
1043 gap: 6px;
1044 padding: 4px 10px;
1045 border-radius: 999px;
1046 font-size: 12px;
1047 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) );
1048 border: 1px solid var( --os-ui-border, #dcdcde );
1049 }
1050
1051 .os-my-wordpress__user-chip--tag {
1052 background: rgba( 124, 58, 237, 0.08 );
1053 border-color: rgba( 124, 58, 237, 0.25 );
1054 }
1055
1056 .os-my-wordpress__user-chip-count {
1057 font-weight: 700;
1058 color: var( --wp-admin-theme-color, #2271b1 );
1059 font-variant-numeric: tabular-nums;
1060 }
1061
1062 /* ---------------------------------------------------------------
1063 * Term dossier — same skeleton as the user dossier (header, stat
1064 * cards, sparkline, milestones, recent list, chips) plus a few
1065 * term-specific bits below: a colored taxonomy icon medallion in
1066 * the header, role badge variants for category vs tag, and a
1067 * "Top contributors" grid that pairs an author avatar with a
1068 * post count.
1069 * --------------------------------------------------------------- */
1070
1071 .os-my-wordpress__term-header {
1072 display: flex;
1073 align-items: center;
1074 gap: 16px;
1075 margin-bottom: 16px;
1076 }
1077
1078 .os-my-wordpress__term-icon {
1079 width: 64px;
1080 height: 64px;
1081 border-radius: 12px;
1082 display: inline-flex;
1083 align-items: center;
1084 justify-content: center;
1085 flex-shrink: 0;
1086 color: var( --os-ui-fg-on-accent, #fff );
1087 }
1088
1089 .os-my-wordpress__term-icon--category {
1090 background: linear-gradient( 135deg, var( --os-ui-accent, #2271b1 ) 0%, var( --os-ui-accent-strong, #135e96 ) 100% );
1091 box-shadow: 0 4px 12px color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 32%, transparent );
1092 }
1093
1094 .os-my-wordpress__term-icon--tag {
1095 background: linear-gradient( 135deg, #7c3aed 0%, #5b21b6 100% );
1096 box-shadow: 0 4px 12px rgba( 124, 58, 237, 0.32 );
1097 }
1098
1099 .os-my-wordpress__user-role--category {
1100 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 12%, transparent );
1101 color: var( --os-ui-accent-strong, #135e96 );
1102 }
1103
1104 .os-my-wordpress__user-role--tag {
1105 background: rgba( 124, 58, 237, 0.12 );
1106 color: #6d28d9;
1107 }
1108
1109 /* Top contributors row — paired avatar + name + post count. */
1110 .os-my-wordpress__term-authors {
1111 display: grid;
1112 grid-template-columns: repeat( auto-fit, minmax( 180px, 1fr ) );
1113 gap: 8px;
1114 }
1115
1116 .os-my-wordpress__term-author {
1117 display: flex;
1118 align-items: center;
1119 gap: 10px;
1120 padding: 8px 12px;
1121 border-radius: 8px;
1122 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.03 ) );
1123 border: 1px solid var( --os-ui-border, #dcdcde );
1124 }
1125
1126 .os-my-wordpress__term-author-avatar {
1127 width: 36px;
1128 height: 36px;
1129 border-radius: 50%;
1130 flex-shrink: 0;
1131 }
1132
1133 .os-my-wordpress__term-author-text {
1134 display: flex;
1135 flex-direction: column;
1136 gap: 2px;
1137 min-width: 0;
1138 }
1139
1140 .os-my-wordpress__term-author-name {
1141 font-weight: 600;
1142 font-size: 13px;
1143 overflow: hidden;
1144 text-overflow: ellipsis;
1145 white-space: nowrap;
1146 }
1147
1148 .os-my-wordpress__term-author-count {
1149 font-size: 11px;
1150 color: var( --os-ui-fg-muted, #50575e );
1151 }
1152
1153 /* ---------------------------------------------------------------
1154 * Comment dossier — header (avatar + name + status / date / count
1155 * badges + author archive / website / moderate links), optional
1156 * "in reply to" quote, the comment body, parent-post card,
1157 * direct-reply thread, and a moderation strip (IP + user agent)
1158 * for users with moderate_comments.
1159 * --------------------------------------------------------------- */
1160
1161 .os-my-wordpress__comment-status--approved {
1162 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 12%, transparent );
1163 color: var( --os-ui-accent-strong, #135e96 );
1164 }
1165
1166 .os-my-wordpress__comment-status--pending {
1167 background: rgba( 217, 119, 6, 0.16 );
1168 color: #b45309;
1169 }
1170
1171 .os-my-wordpress__comment-status--spam {
1172 background: rgba( 179, 45, 46, 0.14 );
1173 color: var( --os-ui-danger-hover, #b32d2e );
1174 }
1175
1176 .os-my-wordpress__comment-status--trash {
1177 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.08 ) );
1178 color: var( --os-ui-fg-muted, #50575e );
1179 }
1180
1181 .os-my-wordpress__comment-date-badge {
1182 background: transparent;
1183 color: var( --os-ui-fg-muted, #50575e );
1184 border: 1px solid var( --os-ui-border, #dcdcde );
1185 text-transform: none;
1186 letter-spacing: normal;
1187 }
1188
1189 /* "In reply to ..." quote shown above the comment body when this
1190 * is a thread reply. */
1191 .os-my-wordpress__comment-quote {
1192 margin: 0 0 16px;
1193 padding: 10px 14px;
1194 border-left: 3px solid var( --wp-admin-theme-color, #2271b1 );
1195 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 6%, transparent );
1196 border-radius: 0 6px 6px 0;
1197 font-size: 13px;
1198 }
1199
1200 .os-my-wordpress__comment-quote-lead {
1201 font-size: 11px;
1202 font-weight: 600;
1203 color: var( --os-ui-fg-muted, #50575e );
1204 text-transform: uppercase;
1205 letter-spacing: 0.04em;
1206 margin-bottom: 4px;
1207 }
1208
1209 .os-my-wordpress__comment-quote p {
1210 margin: 0;
1211 color: var( --os-fg, #1d2327 );
1212 }
1213
1214 /* Comment body — same style as article-content but with a soft
1215 * card background so it reads as the focal element. */
1216 .os-my-wordpress__comment-body {
1217 padding: 14px 16px;
1218 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.03 ) );
1219 border-radius: 8px;
1220 border: 1px solid var( --os-ui-border, #dcdcde );
1221 }
1222
1223 /* Parent-post card under the comment body. */
1224 .os-my-wordpress__comment-post {
1225 display: flex;
1226 flex-direction: column;
1227 gap: 4px;
1228 padding: 10px 14px;
1229 border-radius: 8px;
1230 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) );
1231 border: 1px solid var( --os-ui-border, #dcdcde );
1232 }
1233
1234 .os-my-wordpress__comment-post-title {
1235 font-weight: 600;
1236 color: var( --os-link, var( --os-ui-accent, #2271b1 ) );
1237 text-decoration: none;
1238 }
1239
1240 .os-my-wordpress__comment-post-title:hover {
1241 text-decoration: underline;
1242 }
1243
1244 .os-my-wordpress__comment-post-meta {
1245 font-size: 11px;
1246 color: var( --os-ui-fg-muted, #787c82 );
1247 }
1248
1249 /* Replies list. */
1250 .os-my-wordpress__comment-replies {
1251 list-style: none;
1252 margin: 0;
1253 padding: 0;
1254 display: flex;
1255 flex-direction: column;
1256 gap: 10px;
1257 }
1258
1259 .os-my-wordpress__comment-reply {
1260 display: flex;
1261 gap: 10px;
1262 align-items: flex-start;
1263 padding: 10px 12px;
1264 border-radius: 8px;
1265 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.02 ) );
1266 border: 1px solid var( --os-ui-border, #dcdcde );
1267 }
1268
1269 .os-my-wordpress__comment-reply-avatar {
1270 width: 32px;
1271 height: 32px;
1272 border-radius: 50%;
1273 flex-shrink: 0;
1274 }
1275
1276 .os-my-wordpress__comment-reply-text {
1277 display: flex;
1278 flex-direction: column;
1279 gap: 2px;
1280 min-width: 0;
1281 }
1282
1283 .os-my-wordpress__comment-reply-head {
1284 display: flex;
1285 align-items: baseline;
1286 gap: 8px;
1287 flex-wrap: wrap;
1288 }
1289
1290 .os-my-wordpress__comment-reply-name {
1291 font-weight: 600;
1292 font-size: 13px;
1293 }
1294
1295 .os-my-wordpress__comment-reply-when {
1296 font-size: 11px;
1297 color: var( --os-ui-fg-muted, #787c82 );
1298 }
1299
1300 .os-my-wordpress__comment-reply-excerpt {
1301 margin: 0;
1302 font-size: 13px;
1303 color: var( --os-fg, #1d2327 );
1304 line-height: 1.45;
1305 }
1306
1307 /* =================================================================== *
1308 * USERS — list tiles + activity footprint surface.
1309 *
1310 * The Users folder reuses the same canvas + 96×92 tile grid the
1311 * post tiles use; the user-tile content (avatar circle + label +
1312 * small sub-line) fits inside the cell. The "View activity
1313 * footprint" surface is a full-width body view (no split pane)
1314 * with hero / stats / heatmap / rhythm / timeline.
1315 *
1316 * Since 0.20.0.
1317 * =================================================================== */
1318
1319 /* User tile — replaces the dashicon glyph with a real avatar */
1320 .os-my-wordpress__tile--user {
1321 display: flex;
1322 flex-direction: column;
1323 align-items: center;
1324 justify-content: flex-start;
1325 gap: 4px;
1326 padding: 8px 4px 6px;
1327 box-sizing: border-box;
1328 }
1329
1330 .os-my-wordpress__user-tile-avatar {
1331 display: inline-flex;
1332 align-items: center;
1333 justify-content: center;
1334 width: 44px;
1335 height: 44px;
1336 border-radius: 50%;
1337 overflow: hidden;
1338 background: linear-gradient(
1339 135deg,
1340 color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 18%, transparent ),
1341 color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 4%, transparent )
1342 );
1343 box-shadow:
1344 inset 0 0 0 1px rgba( 0, 0, 0, 0.08 ),
1345 0 2px 6px rgba( 0, 0, 0, 0.08 );
1346 flex: 0 0 auto;
1347 }
1348
1349 .os-my-wordpress__user-tile-avatar img {
1350 width: 100%;
1351 height: 100%;
1352 object-fit: cover;
1353 display: block;
1354 }
1355
1356 .os-my-wordpress__user-tile-initials {
1357 font-weight: 600;
1358 font-size: 14px;
1359 letter-spacing: 0.02em;
1360 color: var( --wp-admin-theme-color, #2271b1 );
1361 text-transform: uppercase;
1362 }
1363
1364 /* Single-line label for user tiles — we already have a generic
1365 * label clamp, but the user variant gets only ONE line so the
1366 * sub-line ("Editor · 12 posts") can fit beneath it. */
1367 .os-my-wordpress__tile--user .os-file-tile__label {
1368 font-size: 11px;
1369 font-weight: 500;
1370 line-height: 1.25;
1371 max-height: 1.25em;
1372 white-space: nowrap;
1373 overflow: hidden;
1374 text-overflow: ellipsis;
1375 width: 100%;
1376 padding: 0 4px;
1377 box-sizing: border-box;
1378 }
1379
1380 .os-my-wordpress__user-tile-sub {
1381 font-size: 10px;
1382 line-height: 1.2;
1383 color: var( --os-tile-fg-muted, rgba( 0, 0, 0, 0.55 ) );
1384 max-height: 1.2em;
1385 white-space: nowrap;
1386 overflow: hidden;
1387 text-overflow: ellipsis;
1388 width: 100%;
1389 text-align: center;
1390 padding: 0 4px;
1391 box-sizing: border-box;
1392 }
1393
1394 /* Selected/hover user tiles inherit the same chrome the post tiles
1395 * use — see `.os-my-wordpress__tile--selected` above. */
1396
1397 /* ----- Activity footprint — full-width body surface -----
1398 *
1399 * Opens via the right-click context menu on a user tile (or the
1400 * dossier's "View activity footprint" button). Replaces the split
1401 * list/preview with a wide canvas so each section gets enough room
1402 * to read.
1403 */
1404
1405 .os-my-wordpress__footprint {
1406 display: flex;
1407 flex-direction: column;
1408 gap: 20px;
1409 padding: 24px 28px 32px;
1410 box-sizing: border-box;
1411 overflow-y: auto;
1412 height: 100%;
1413 /* Scroll container spans the full window width so wheel + touch
1414 * scroll work no matter where the pointer is — including in the
1415 * empty margins to the left / right of the content. The 1080px
1416 * cap is applied to each immediate child instead (below). */
1417 align-items: center;
1418 }
1419
1420 .os-my-wordpress__footprint > * {
1421 width: 100%;
1422 max-width: 1080px;
1423 }
1424
1425 .os-my-wordpress__footprint-section {
1426 border: 1px solid var( --os-ui-border, #dcdcde );
1427 border-radius: 12px;
1428 padding: 18px 20px;
1429 background: var(
1430 --os-my-wordpress-surface,
1431 var( --os-surface, var( --os-ui-surface, rgba( 255, 255, 255, 0.6 ) ) )
1432 );
1433 box-shadow: 0 1px 2px rgba( 0, 0, 0, 0.04 );
1434 /* The parent `.__footprint` is a flex column with `overflow-y:
1435 * auto`. Flex items default to `flex-shrink: 1`, so each section
1436 * would compress along the column axis when the combined natural
1437 * height exceeds the container — clipping the calendar heatmap
1438 * (the calendar section also sets `overflow-x: auto`, which
1439 * doesn't surface a vertical scrollbar). Lock the intrinsic
1440 * height so the parent scroller, not the children, handles the
1441 * overflow. */
1442 flex: 0 0 auto;
1443 }
1444
1445 .os-my-wordpress__footprint-section h3 {
1446 margin: 0 0 12px;
1447 font-size: 13px;
1448 font-weight: 600;
1449 letter-spacing: 0.04em;
1450 text-transform: uppercase;
1451 color: var( --os-ui-fg-muted, #50575e );
1452 }
1453
1454 /* ----- Hero ----- */
1455
1456 .os-my-wordpress__footprint-hero {
1457 display: flex;
1458 gap: 20px;
1459 align-items: center;
1460 padding: 24px;
1461 border-radius: 16px;
1462 background: linear-gradient(
1463 135deg,
1464 color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 10%, transparent ) 0%,
1465 color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 2%, transparent ) 60%,
1466 transparent 100%
1467 );
1468 border: 1px solid var( --os-ui-border, #dcdcde );
1469 }
1470
1471 .os-my-wordpress__footprint-avatar {
1472 width: 112px;
1473 height: 112px;
1474 border-radius: 50%;
1475 overflow: hidden;
1476 flex: 0 0 auto;
1477 box-shadow:
1478 inset 0 0 0 2px rgba( 255, 255, 255, 0.8 ),
1479 0 6px 24px rgba( 0, 0, 0, 0.14 );
1480 background: linear-gradient(
1481 135deg,
1482 color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 18%, transparent ),
1483 color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 4%, transparent )
1484 );
1485 display: inline-flex;
1486 align-items: center;
1487 justify-content: center;
1488 }
1489
1490 .os-my-wordpress__footprint-avatar img {
1491 width: 100%;
1492 height: 100%;
1493 object-fit: cover;
1494 display: block;
1495 }
1496
1497 .os-my-wordpress__footprint-avatar
1498 .os-my-wordpress__user-tile-initials {
1499 font-size: 36px;
1500 }
1501
1502 .os-my-wordpress__footprint-headline {
1503 min-width: 0;
1504 flex: 1 1 auto;
1505 display: flex;
1506 flex-direction: column;
1507 gap: 8px;
1508 }
1509
1510 .os-my-wordpress__footprint-title {
1511 margin: 0;
1512 font-size: 28px;
1513 line-height: 1.15;
1514 font-weight: 700;
1515 color: var( --os-fg, #1d2327 );
1516 }
1517
1518 .os-my-wordpress__footprint-meta {
1519 display: flex;
1520 flex-wrap: wrap;
1521 gap: 6px;
1522 align-items: center;
1523 }
1524
1525 .os-my-wordpress__footprint-since {
1526 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) );
1527 color: var( --os-ui-fg-muted, #50575e );
1528 }
1529
1530 /* ----- Headline stats row (4 cards) ----- */
1531
1532 .os-my-wordpress__footprint-stats-row {
1533 display: grid;
1534 grid-template-columns: repeat( auto-fit, minmax( 160px, 1fr ) );
1535 gap: 12px;
1536 padding: 16px 18px;
1537 }
1538
1539 .os-my-wordpress__footprint-stats-row
1540 .os-my-wordpress__user-stat {
1541 background: var( --os-surface, var( --os-ui-surface, rgba( 255, 255, 255, 0.9 ) ) );
1542 box-shadow: 0 1px 4px rgba( 0, 0, 0, 0.04 );
1543 }
1544
1545 /* ----- Calendar heatmap (GitHub-style) ----- */
1546
1547 .os-my-wordpress__footprint-calendar-section {
1548 overflow-x: auto;
1549 }
1550
1551 .os-my-wordpress__footprint-calendar {
1552 display: flex;
1553 flex-direction: column;
1554 gap: 8px;
1555 /* Size to the grid itself so the legend (justify-content: flex-end
1556 * inside this same column) anchors to the grid's right edge instead
1557 * of the section's. Centered within the wider section by
1558 * `margin-inline: auto`. */
1559 width: max-content;
1560 max-width: 100%;
1561 margin-inline: auto;
1562 }
1563
1564 .os-my-wordpress__footprint-grid {
1565 display: grid;
1566 /* Row 1 holds month labels (auto height); rows 2–8 are the 7
1567 * weekdays at fixed 11px. Col 1 holds weekday labels (auto width);
1568 * subsequent cols are 11px-wide week tracks, set per-cell via
1569 * `grid-column` so the JS can place items explicitly. */
1570 grid-template-rows: auto repeat( 7, 11px );
1571 grid-template-columns: auto;
1572 grid-auto-columns: 11px;
1573 column-gap: 3px;
1574 row-gap: 3px;
1575 width: max-content;
1576 }
1577
1578 .os-my-wordpress__footprint-month,
1579 .os-my-wordpress__footprint-weekday {
1580 font-size: 10px;
1581 line-height: 11px;
1582 color: var( --os-ui-fg-muted, #50575e );
1583 white-space: nowrap;
1584 }
1585
1586 .os-my-wordpress__footprint-weekday {
1587 /* Sit flush to the right of the label gutter so each label reads
1588 * adjacent to its row. The 6px breathing room is in addition to
1589 * the grid's column-gap. */
1590 padding-inline-end: 6px;
1591 align-self: center;
1592 justify-self: end;
1593 }
1594
1595 .os-my-wordpress__footprint-month {
1596 padding-bottom: 2px;
1597 }
1598
1599 .os-my-wordpress__footprint-cell {
1600 width: 11px;
1601 height: 11px;
1602 border-radius: 2px;
1603 background: var( --os-ui-border, #ebedf0 );
1604 display: inline-block;
1605 transition: transform 0.12s ease;
1606 }
1607
1608 .os-my-wordpress__footprint-cell--pad {
1609 background: transparent;
1610 }
1611
1612 .os-my-wordpress__footprint-cell--l0 {
1613 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) );
1614 }
1615
1616 .os-my-wordpress__footprint-cell--l1 {
1617 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 25%, transparent );
1618 }
1619
1620 .os-my-wordpress__footprint-cell--l2 {
1621 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 50%, transparent );
1622 }
1623
1624 .os-my-wordpress__footprint-cell--l3 {
1625 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 75%, transparent );
1626 }
1627
1628 .os-my-wordpress__footprint-cell--l4 {
1629 background: var( --wp-admin-theme-color, #2271b1 );
1630 }
1631
1632 .os-my-wordpress__footprint-cell:hover {
1633 transform: scale( 1.4 );
1634 z-index: 1;
1635 }
1636
1637 .os-my-wordpress__footprint-legend {
1638 display: flex;
1639 gap: 4px;
1640 align-items: center;
1641 justify-content: flex-end;
1642 font-size: 11px;
1643 color: var( --os-ui-fg-muted, #50575e );
1644 }
1645
1646 .os-my-wordpress__footprint-legend-label {
1647 font-size: 11px;
1648 color: var( --os-ui-fg-muted, #50575e );
1649 padding: 0 4px;
1650 }
1651
1652 /* ----- Rhythm (weekday + hour bar charts) ----- */
1653
1654 .os-my-wordpress__footprint-rhythm-grid {
1655 display: grid;
1656 grid-template-columns: minmax( 0, 1fr ) minmax( 0, 1.7fr );
1657 gap: 24px;
1658 }
1659
1660 @container ( max-width: 720px ) {
1661 .os-my-wordpress__footprint-rhythm-grid {
1662 grid-template-columns: 1fr;
1663 }
1664 }
1665
1666 .os-my-wordpress__footprint-chart-caption {
1667 font-size: 12px;
1668 color: var( --os-ui-fg-muted, #50575e );
1669 margin-bottom: 6px;
1670 }
1671
1672 .os-my-wordpress__footprint-bars {
1673 display: flex;
1674 align-items: end;
1675 gap: 4px;
1676 height: 100px;
1677 padding-bottom: 18px;
1678 position: relative;
1679 }
1680
1681 .os-my-wordpress__footprint-bar-col {
1682 flex: 1 1 0;
1683 min-width: 0;
1684 display: flex;
1685 flex-direction: column;
1686 align-items: stretch;
1687 justify-content: flex-end;
1688 gap: 4px;
1689 height: 100%;
1690 position: relative;
1691 }
1692
1693 .os-my-wordpress__footprint-bar {
1694 background: linear-gradient(
1695 180deg,
1696 var( --wp-admin-theme-color, #2271b1 ) 0%,
1697 color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 55%, transparent ) 100%
1698 );
1699 border-radius: 3px 3px 0 0;
1700 min-height: 2px;
1701 transition: opacity 0.2s ease;
1702 }
1703
1704 .os-my-wordpress__footprint-bar--empty {
1705 background: var( --os-ui-border, #dcdcde );
1706 height: 2px !important;
1707 min-height: 2px;
1708 }
1709
1710 .os-my-wordpress__footprint-bar-label {
1711 position: absolute;
1712 bottom: -16px;
1713 left: 0;
1714 right: 0;
1715 text-align: center;
1716 font-size: 10px;
1717 color: var( --os-ui-fg-muted, #787c82 );
1718 }
1719
1720 /* ----- Most-prolific-month callout ----- */
1721
1722 .os-my-wordpress__footprint-callout {
1723 display: flex;
1724 flex-direction: column;
1725 gap: 4px;
1726 align-items: center;
1727 text-align: center;
1728 padding: 20px 24px;
1729 background: linear-gradient(
1730 135deg,
1731 color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 10%, transparent ),
1732 color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 2%, transparent )
1733 );
1734 }
1735
1736 .os-my-wordpress__footprint-callout-label {
1737 font-size: 11px;
1738 text-transform: uppercase;
1739 letter-spacing: 0.08em;
1740 color: var( --os-ui-fg-muted, #50575e );
1741 font-weight: 600;
1742 }
1743
1744 .os-my-wordpress__footprint-callout-value {
1745 margin: 2px 0;
1746 font-size: 24px;
1747 font-weight: 700;
1748 color: var( --os-fg, #1d2327 );
1749 }
1750
1751 .os-my-wordpress__footprint-callout-detail {
1752 margin: 0;
1753 font-size: 13px;
1754 color: var( --os-ui-fg-muted, #50575e );
1755 max-width: 480px;
1756 }
1757
1758 /* ----- Recent-activity timeline ----- */
1759
1760 .os-my-wordpress__footprint-timeline {
1761 list-style: none;
1762 margin: 0;
1763 padding: 0;
1764 position: relative;
1765 display: flex;
1766 flex-direction: column;
1767 gap: 12px;
1768 }
1769
1770 .os-my-wordpress__footprint-timeline::before {
1771 content: '';
1772 position: absolute;
1773 inset-inline-start: 13px;
1774 top: 4px;
1775 bottom: 4px;
1776 width: 2px;
1777 background: var( --os-ui-border, #dcdcde );
1778 border-radius: 1px;
1779 }
1780
1781 .os-my-wordpress__footprint-event {
1782 display: flex;
1783 gap: 12px;
1784 align-items: flex-start;
1785 position: relative;
1786 padding-inline-start: 36px;
1787 min-height: 28px;
1788 }
1789
1790 .os-my-wordpress__footprint-dot {
1791 position: absolute;
1792 inset-inline-start: 0;
1793 top: 0;
1794 width: 28px;
1795 height: 28px;
1796 border-radius: 50%;
1797 display: inline-flex;
1798 align-items: center;
1799 justify-content: center;
1800 background: var( --os-surface, var( --os-ui-surface, #fff ) );
1801 border: 2px solid var( --wp-admin-theme-color, #2271b1 );
1802 color: var( --wp-admin-theme-color, #2271b1 );
1803 }
1804
1805 .os-my-wordpress__footprint-event--comment
1806 .os-my-wordpress__footprint-dot {
1807 border-color: var( --os-ui-fg-muted, #787c82 );
1808 color: var( --os-ui-fg-muted, #787c82 );
1809 }
1810
1811 .os-my-wordpress__footprint-dot .dashicons {
1812 font-size: 14px;
1813 width: 14px;
1814 height: 14px;
1815 line-height: 1;
1816 }
1817
1818 .os-my-wordpress__footprint-event-body {
1819 display: flex;
1820 flex-direction: column;
1821 gap: 2px;
1822 min-width: 0;
1823 flex: 1 1 auto;
1824 }
1825
1826 .os-my-wordpress__footprint-event-title {
1827 font-weight: 600;
1828 font-size: 13px;
1829 color: var( --os-link, var( --os-ui-accent, #2271b1 ) );
1830 text-decoration: none;
1831 overflow: hidden;
1832 text-overflow: ellipsis;
1833 white-space: nowrap;
1834 }
1835
1836 a.os-my-wordpress__footprint-event-title:hover {
1837 text-decoration: underline;
1838 }
1839
1840 .os-my-wordpress__footprint-event-meta {
1841 font-size: 11px;
1842 color: var( --os-ui-fg-muted, #787c82 );
1843 }
1844
1845 /* ----- Footer actions ----- */
1846
1847 .os-my-wordpress__footprint-footer {
1848 display: flex;
1849 justify-content: flex-end;
1850 align-items: center;
1851 gap: 8px;
1852 flex-wrap: wrap;
1853 background: transparent;
1854 border-color: transparent;
1855 box-shadow: none;
1856 padding: 0;
1857 }
1858
1859 /* ============================================================ *
1860 * Status ribbon — diagonal corner banner for non-published tiles
1861 * (since 0.21.0)
1862 *
1863 * Classic 45° corner ribbon — same affordance as a stamp on a
1864 * document. Applied to every tile whose post status isn't
1865 * `publish` across every My WordPress section (Posts, Pages,
1866 * Media drill-in, plugin kinds).
1867 *
1868 * Implementation:
1869 * 1. Tiles with a status modifier get `overflow: hidden` so the
1870 * ribbon's overshoot clips to the tile bounds.
1871 * 2. Ribbon is a wide strip (`width: 110px`) anchored to the
1872 * top-right corner, rotated +45° so it forms a diagonal
1873 * banner across that corner. The strip extends past the
1874 * tile edges; `overflow: hidden` on the parent crops it
1875 * into a clean triangle.
1876 *
1877 * The status tiles need a positioning context. `.os-
1878 * file-tile` is `position: absolute` (canvas layout) on the
1879 * Posts/Pages browse views — that satisfies the positioning-
1880 * context requirement. For the Media drill-in usage tiles we
1881 * swap from `static` to `relative` via the per-status rule
1882 * below so the ribbon stays anchored to its own tile.
1883 * ============================================================ */
1884
1885 /* Status ribbon — the diagonal corner banner — is now drawn by
1886 * the `<os-ribbon>` web component slotted inside `<os-tile>`.
1887 * The component's own shadow-DOM styles own placement, rotation,
1888 * background, lettering, and the WP admin-theme tint. The only
1889 * surface-specific rule left here is dimming the icon on non-
1890 * published tiles so the unpublished signal carries even when
1891 * the ribbon is hidden via the OS-settings toggle. */
1892
1893 .os-my-wordpress__tile--draft,
1894 .os-my-wordpress__tile--pending,
1895 .os-my-wordpress__tile--private,
1896 .os-my-wordpress__tile--future {
1897 overflow: hidden;
1898 }
1899
1900 os-tile[ status='draft' ] .os-file-tile__icon,
1901 os-tile[ status='pending' ] .os-file-tile__icon,
1902 os-tile[ status='private' ] .os-file-tile__icon,
1903 os-tile[ status='future' ] .os-file-tile__icon {
1904 opacity: 0.7;
1905 }
1906
1907 /* ============================================================ *
1908 * Media section (since 0.21.0)
1909 * ============================================================ */
1910
1911 .os-my-wordpress__media-grid {
1912 display: grid;
1913 grid-template-columns: repeat( auto-fill, minmax( 120px, 1fr ) );
1914 /* A flow grid rather than the absolute canvas, but the air
1915 * between two icons should not depend on which window you are
1916 * looking at — so the gaps and the gutter come from the same
1917 * tokens the canvas pitch is built from. */
1918 gap: var( --os-grid-gap-y, 16px ) var( --os-grid-gap-x, 20px );
1919 padding: var( --os-grid-padding, 16px );
1920 overflow-y: auto;
1921 align-content: start;
1922 }
1923
1924 .os-my-wordpress__media-tile {
1925 /* Override the base `.os-file-tile` (position: absolute,
1926 * width: 88px) so media tiles flow inside the CSS Grid instead of
1927 * stacking at (0,0) like the absolute-positioned post/user tiles.
1928 *
1929 * `relative` (not `static`) so the tile remains a containing
1930 * block for absolutely-positioned descendants — the `::after`
1931 * shortcut-arrow badge on attachment tiles, and the
1932 * `<os-ribbon>` post-status corner ribbon — both of which
1933 * would otherwise escape to the nearest positioned ancestor
1934 * (the window, the desktop, the viewport) and render way off
1935 * the tile. */
1936 position: relative;
1937 width: auto;
1938 /* …and out of the fixed tile height for the same reason: a media
1939 * tile is a square thumbnail plus a caption, sized by the grid
1940 * column it lands in, not by the icon-canvas cell. */
1941 height: auto;
1942 display: flex;
1943 flex-direction: column;
1944 align-items: stretch;
1945 gap: 6px;
1946 padding: 8px;
1947 background: transparent;
1948 border: 1px solid transparent;
1949 border-radius: 8px;
1950 cursor: pointer;
1951 text-align: center;
1952 min-width: 0;
1953 }
1954
1955 .os-my-wordpress__media-tile:hover {
1956 background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) ) );
1957 }
1958
1959 /* Media tiles inherit the canonical `.os-file-tile--selected`
1960 * rule from `desktop-files.css` — accent inset border + tinted
1961 * background — same as every other tile in the shell. */
1962
1963 /* Override the canonical 48×48 visual/preview sizing — media tiles
1964 * want the visual to fill the full grid-cell width as a square
1965 * thumbnail (or icon-fallback square). Photos browser look, not
1966 * folder-icon look. Applies to BOTH the `<img>` (real thumbnail)
1967 * and the dashicon fallback (non-image MIME), so the two tile
1968 * shapes are visually consistent in the grid. */
1969 .os-my-wordpress__media-tile .os-file-tile__visual {
1970 display: flex;
1971 align-items: center;
1972 justify-content: center;
1973 width: 100%;
1974 height: auto;
1975 aspect-ratio: 1 / 1;
1976 overflow: hidden;
1977 border-radius: 6px;
1978 background: var(
1979 --os-media-tile-bg,
1980 rgba( 0, 0, 0, 0.05 )
1981 );
1982 /* Reset the base 32px font + 48px size since we're sizing the
1983 * inner img/icon explicitly below. */
1984 font-size: 0;
1985 line-height: 1;
1986 }
1987
1988 .os-my-wordpress__media-tile .os-file-tile__preview {
1989 width: 100%;
1990 height: 100%;
1991 object-fit: cover;
1992 border-radius: 0;
1993 }
1994
1995 .os-my-wordpress__media-tile .os-file-tile__icon {
1996 /* Centered dashicon fallback — proportional to the larger
1997 * thumbnail well so it doesn't look like a postage stamp. */
1998 width: auto;
1999 height: auto;
2000 font-size: 36px;
2001 color: var( --os-ui-fg-muted, #787c82 );
2002 }
2003
2004 .os-my-wordpress__media-tile .os-file-tile__label {
2005 font-size: 12px;
2006 line-height: 1.3;
2007 overflow: hidden;
2008 text-overflow: ellipsis;
2009 display: -webkit-box;
2010 -webkit-line-clamp: 2;
2011 -webkit-box-orient: vertical;
2012 }
2013
2014 /* ----- Media preview pane ----- */
2015
2016 .os-my-wordpress__media-pane {
2017 display: flex;
2018 flex-direction: column;
2019 gap: 16px;
2020 padding: 16px;
2021 }
2022
2023 .os-my-wordpress__media-title {
2024 font-size: 16px;
2025 font-weight: 600;
2026 margin: 0;
2027 }
2028
2029 .os-my-wordpress__media-visual {
2030 display: flex;
2031 align-items: center;
2032 justify-content: center;
2033 background: var( --os-media-visual-bg, var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) ) );
2034 border-radius: 8px;
2035 min-height: 200px;
2036 max-height: 480px;
2037 overflow: hidden;
2038 }
2039
2040 .os-my-wordpress__media-image,
2041 .os-my-wordpress__media-video {
2042 max-width: 100%;
2043 max-height: 480px;
2044 object-fit: contain;
2045 }
2046
2047 .os-my-wordpress__media-fallback-icon {
2048 font-size: 96px;
2049 width: 96px;
2050 height: 96px;
2051 color: var( --os-ui-fg-muted, #787c82 );
2052 }
2053
2054 .os-my-wordpress__media-doc-link {
2055 margin-inline-start: 16px;
2056 align-self: center;
2057 }
2058
2059 .os-my-wordpress__media-audio-stack {
2060 display: flex;
2061 flex-direction: column;
2062 align-items: center;
2063 gap: 12px;
2064 padding: 24px;
2065 }
2066
2067 .os-my-wordpress__media-meta {
2068 display: grid;
2069 grid-template-columns: max-content 1fr;
2070 gap: 4px 16px;
2071 font-size: 13px;
2072 margin: 0;
2073 }
2074
2075 .os-my-wordpress__media-meta-term {
2076 color: var( --os-ui-fg-muted, #787c82 );
2077 margin: 0;
2078 }
2079
2080 .os-my-wordpress__media-meta-value {
2081 color: var( --os-ui-fg, #1d2327 );
2082 word-break: break-word;
2083 margin: 0;
2084 }
2085
2086 .os-my-wordpress__media-actions {
2087 display: flex;
2088 flex-wrap: wrap;
2089 gap: 8px;
2090 padding-top: 8px;
2091 border-top: 1px solid var( --os-ui-border, #dcdcde );
2092 }
2093
2094 /* ----- Media drill-in ----- */
2095
2096 /* Drill-in: referencing-posts list. Visually identical to the
2097 * Posts / Pages tiles — same `os-file-tile` chrome (48px
2098 * dashicon + 88px-wide two-line label). We can't reuse the
2099 * absolute-positioning canvas layout from `renderEntityList` here
2100 * (those internals don't export), so this surface uses a flex-wrap
2101 * of fixed-width tiles to reproduce the look.
2102 *
2103 * NOTE: this rule INTENTIONALLY overrides the `__media-grid`
2104 * defaults (which target square thumbnails on 130px columns) so
2105 * usage tiles match the canvas-flowed post tiles instead.
2106 */
2107 .os-my-wordpress__usage-grid {
2108 display: flex;
2109 flex-wrap: wrap;
2110 gap: var( --os-grid-gap-y, 16px ) var( --os-grid-gap-x, 20px );
2111 padding: var( --os-grid-padding, 16px );
2112 align-content: flex-start;
2113 }
2114
2115 /* Reset the `__media-tile` overrides for usage tiles so they
2116 * inherit the unmodified `.os-file-tile` chrome the
2117 * canvas-positioned post tiles use — except for `position: static`
2118 * so they participate in the flex layout. */
2119 .os-my-wordpress__tile--usage {
2120 width: var( --os-tile-w, 88px );
2121 align-items: center;
2122 padding: 8px 4px;
2123 gap: 6px;
2124 }
2125
2126 /* Right-pane summary bar sits ABOVE the inline media preview. */
2127 .os-my-wordpress__media-detail-summary-bar {
2128 padding: 12px 16px;
2129 border-bottom: 1px solid var( --os-ui-border, #dcdcde );
2130 }
2131
2132 .os-my-wordpress__media-detail-summary {
2133 margin: 0;
2134 color: var( --os-ui-fg-muted, #787c82 );
2135 font-size: 13px;
2136 }
2137
2138 .os-my-wordpress__media-detail-preview {
2139 /* Lets the embedded `__media-pane` keep its own padding without
2140 * fighting the summary bar's spacing. */
2141 display: block;
2142 }
2143
2144 /* ------------------------------------------------------------------ */
2145 /* Agents section (`agent` entity kind — src/my-wordpress/agents-renderer.ts) */
2146 /* ------------------------------------------------------------------ */
2147
2148 .dm-agents {
2149 display: flex;
2150 flex-direction: column;
2151 height: 100%;
2152 min-height: 0;
2153 padding: 12px;
2154 gap: 10px;
2155 overflow: hidden;
2156 box-sizing: border-box;
2157 }
2158
2159
2160 /* Section-level loading — same scale curve as the My WordPress
2161 preview loader and the window-loading overlay, instead of the bare
2162 48px component default. */
2163 .dm-agents__loading {
2164 display: flex;
2165 align-items: center;
2166 justify-content: center;
2167 height: 100%;
2168 min-height: 320px;
2169 }
2170
2171 .dm-agents__loading os-spinner {
2172 --os-ui-spinner-size: clamp( 96px, 14vw, 192px );
2173 }
2174
2175 .dm-agents__detail {
2176 flex: 1;
2177 min-width: 0;
2178 overflow-y: auto;
2179 display: flex;
2180 flex-direction: column;
2181 gap: 10px;
2182 }
2183
2184 /* `<os-empty-state>` owns the icon + copy and centres them within
2185 itself; the host is still a flex item that shrinks to its content,
2186 so without this it sits at the top of a pane that is mostly empty
2187 space. Auto margins on both axes absorb the slack — placement only,
2188 same as the comments window. Covers the framework-off state and
2189 "No agents yet" alike. */
2190 .dm-agents__detail > os-empty-state {
2191 margin: auto;
2192 }
2193
2194 .dm-agents__detail-head {
2195 display: flex;
2196 align-items: center;
2197 gap: 10px;
2198 }
2199
2200 .dm-agents__detail-avatar {
2201 width: 48px;
2202 height: 48px;
2203 border-radius: 50%;
2204 /* Placeholder behind the avatar image while it loads — follows the
2205 palette so it isn't a white disc on a dark station. */
2206 background: var( --os-ui-surface-elevated, #fff );
2207 flex: none;
2208 }
2209
2210 .dm-agents__detail-title {
2211 flex: 1;
2212 min-width: 0;
2213 }
2214
2215 .dm-agents__detail-title h3 {
2216 margin: 0;
2217 font-size: 16px;
2218 }
2219
2220 .dm-agents__detail-slug {
2221 font-size: 12px;
2222 opacity: 0.6;
2223 }
2224
2225 /* Verbs get their own row under the identity block: the set grows
2226 (profile, contributions, desktop, chat, delete) and squeezing them
2227 beside the name truncated it first. */
2228 .dm-agents__detail-actions {
2229 display: flex;
2230 flex-wrap: wrap;
2231 gap: 8px;
2232 }
2233
2234 .dm-agents__tabs {
2235 display: flex;
2236 gap: 4px;
2237 border-bottom: 1px solid var( --os-border, rgba( 0, 0, 0, 0.08 ) );
2238 }
2239
2240 .dm-agents__tab {
2241 appearance: none;
2242 background: none;
2243 border: none;
2244 border-bottom: 2px solid transparent;
2245 padding: 6px 10px;
2246 font: inherit;
2247 color: inherit;
2248 cursor: pointer;
2249 opacity: 0.7;
2250 }
2251
2252 .dm-agents__tab.is-active {
2253 border-bottom-color: var( --os-accent, #2271b1 );
2254 opacity: 1;
2255 font-weight: 600;
2256 }
2257
2258 .dm-agents__pane {
2259 display: flex;
2260 flex-direction: column;
2261 gap: 10px;
2262 padding-bottom: 12px;
2263 }
2264
2265 /* Pane-level loading: same big, centered WordPress mark the section
2266 loader uses, instead of a bare 48px spinner pinned to the top-left
2267 of the pane. */
2268 .dm-agents__pane--loading {
2269 align-items: center;
2270 justify-content: center;
2271 flex: 1;
2272 min-height: 260px;
2273 }
2274
2275 .dm-agents__pane--loading os-spinner {
2276 --os-ui-spinner-size: clamp( 96px, 12vw, 160px );
2277 }
2278
2279 .dm-agents__hint {
2280 margin: 0;
2281 font-size: 12px;
2282 opacity: 0.7;
2283 }
2284
2285 /* Role first: least privilege is the decision, so the control holds
2286 the mockup's width instead of stretching into a landing strip. */
2287 .dm-agents__role-select {
2288 max-width: 280px;
2289 }
2290
2291 /* The ability filter. Sized like the role select rather than the
2292 full pane: it sits above a list of rules, and a search box as wide
2293 as the rules reads as another one of them. */
2294 .dm-agents__ability-search {
2295 display: block;
2296 max-width: 320px;
2297 margin: 12px 0 4px;
2298 }
2299
2300 /* Each category is a native disclosure. `<details>` because the
2301 behaviour is exactly what it is for: keyboard, screen-reader
2302 semantics and the open/closed state all arrive for free, and the
2303 only thing left to write is the costume. */
2304 .dm-agents__ability-group {
2305 border-bottom: 1px solid var( --os-border, rgba( 0, 0, 0, 0.08 ) );
2306 }
2307
2308 .dm-agents__ability-group:last-of-type {
2309 border-bottom: 0;
2310 }
2311
2312 .dm-agents__category {
2313 margin: 18px 0 2px;
2314 font-family: var( --os-ui-font-mono, ui-monospace, monospace );
2315 font-size: 11px;
2316 font-weight: 500;
2317 text-transform: uppercase;
2318 letter-spacing: 0.08em;
2319 color: var( --os-ui-fg-muted, #646970 );
2320 }
2321
2322 /* A `<summary>` is a button, so it gets a button's affordances: a
2323 pointer, a hover, and a focus ring that is visible on the dark
2324 surface the section is drawn on. */
2325 summary.dm-agents__category {
2326 display: flex;
2327 align-items: center;
2328 gap: 8px;
2329 margin: 0;
2330 padding: 10px 0;
2331 cursor: pointer;
2332 list-style: none;
2333 user-select: none;
2334 }
2335
2336 summary.dm-agents__category::-webkit-details-marker {
2337 display: none;
2338 }
2339
2340 /* The marker is drawn here rather than left to the UA so it can be
2341 the same triangle in every browser and turn with the disclosure.
2342 Rotation only: a transform, so opening a group of forty rows
2343 costs no layout. */
2344 summary.dm-agents__category::before {
2345 content: '';
2346 flex: none;
2347 width: 0;
2348 height: 0;
2349 border-inline-start: 5px solid currentcolor;
2350 border-block: 4px solid transparent;
2351 transition: transform 120ms ease;
2352 }
2353
2354 .dm-agents__ability-group[ open ] > summary.dm-agents__category::before {
2355 transform: rotate( 90deg );
2356 }
2357
2358 @media ( prefers-reduced-motion: reduce ) {
2359 summary.dm-agents__category::before {
2360 transition: none;
2361 }
2362 }
2363
2364 summary.dm-agents__category:hover {
2365 color: var( --os-ui-fg, #1e1e1e );
2366 }
2367
2368 summary.dm-agents__category:focus-visible {
2369 outline: 2px solid var( --os-ui-accent, #3858e9 );
2370 outline-offset: 2px;
2371 border-radius: 4px;
2372 }
2373
2374 .dm-agents__category-name {
2375 flex: 1 1 auto;
2376 }
2377
2378 /* "3 of 12" when some are ticked, otherwise just the size of the
2379 group. The count is why a closed group is still informative:
2380 collapsing must not hide the fact that something in there is
2381 switched on. */
2382 .dm-agents__category-count {
2383 flex: none;
2384 font-variant-numeric: tabular-nums;
2385 opacity: 0.75;
2386 }
2387
2388 /* One ability per row, ruled like the mockup: tick and name on the
2389 left, badge on the right, the description indented under the name
2390 (the checkbox column is the control's own 26px). */
2391 .dm-agents__ability {
2392 display: grid;
2393 grid-template-columns: 1fr auto;
2394 gap: 2px 10px;
2395 align-items: start;
2396 padding: 11px 0;
2397 border-bottom: 1px solid var( --os-border, rgba( 0, 0, 0, 0.08 ) );
2398 }
2399
2400 .dm-agents__ability:last-of-type {
2401 border-bottom: 0;
2402 }
2403
2404 .dm-agents__ability-desc {
2405 grid-column: 1 / -1;
2406 margin: 0;
2407 padding-inline-start: 26px;
2408 font-size: 12.5px;
2409 color: var( --os-ui-fg-muted, #646970 );
2410 }
2411
2412 /* Access badges in the instrument voice. The palette's own warning
2413 tokens carry the pale-yellow "can modify"; all this changes is the
2414 costume: mono, uppercase, an outline instead of a wash. */
2415 .dm-agents__ability os-badge {
2416 --os-ui-badge-font: 500 10px/1.6 var( --os-ui-font-mono, ui-monospace, monospace );
2417 --os-ui-badge-dot-size: 0;
2418 --os-ui-badge-gap: 0;
2419 --os-ui-badge-padding: 3px 8px;
2420 --os-ui-badge-bg: transparent;
2421 --os-ui-badge-border: 1px solid var( --os-border, rgba( 0, 0, 0, 0.12 ) );
2422 text-transform: uppercase;
2423 letter-spacing: 0.06em;
2424 white-space: nowrap;
2425 margin-top: 2px;
2426 }
2427
2428 .dm-agents__ability os-badge[ tone='warning' ] {
2429 --os-ui-badge-border: 1px solid
2430 var( --os-ui-warning-border, rgba( 154, 103, 0, 0.4 ) );
2431 }
2432
2433 .dm-agents__trigger {
2434 border: 1px solid var( --os-border, rgba( 0, 0, 0, 0.08 ) );
2435 border-radius: 8px;
2436 padding: 8px 10px;
2437 display: flex;
2438 flex-direction: column;
2439 gap: 6px;
2440 }
2441
2442 .dm-agents__trigger-head {
2443 display: flex;
2444 align-items: center;
2445 gap: 8px;
2446 }
2447
2448 .dm-agents__trigger-desc {
2449 margin: 0;
2450 font-size: 12px;
2451 opacity: 0.75;
2452 }
2453
2454 .dm-agents__trigger-config {
2455 display: flex;
2456 flex-wrap: wrap;
2457 gap: 4px 14px;
2458 }
2459
2460 .dm-agents__actions {
2461 display: flex;
2462 gap: 8px;
2463 align-items: center;
2464 flex-wrap: wrap;
2465 margin-block-start: 12px;
2466 }
2467
2468 /* ---------------------------------------------------------------------
2469 The cast
2470 ------------------------------------------------------------------ */
2471
2472 /* One view at a time: the grid, an agent, or the wizard. A sidebar of
2473 faces next to a detail form would leave neither enough room, and the
2474 whole point of the grid is that the faces are big enough to tell
2475 apart. */
2476 .dm-agents__view {
2477 display: flex;
2478 flex-direction: column;
2479 flex: 1;
2480 min-height: 0;
2481 overflow-y: auto;
2482 gap: 12px;
2483 }
2484
2485 .dm-agents__cast-head {
2486 display: flex;
2487 align-items: baseline;
2488 gap: 8px;
2489 }
2490
2491 .dm-agents__cast-head h3 {
2492 margin: 0;
2493 }
2494
2495 .dm-agents__cast-count {
2496 font-family: var( --os-ui-font-mono, ui-monospace, monospace );
2497 font-size: 12px;
2498 color: var( --os-ui-fg-muted, #646970 );
2499 }
2500
2501 .dm-agents__cast {
2502 display: grid;
2503 gap: 16px;
2504 grid-template-columns: repeat( auto-fill, minmax( 288px, 1fr ) );
2505 align-content: start;
2506 }
2507
2508 /* The card reacts like the mockup's: a lift and a brighter edge.
2509 Transform and border-color only — both cheap, and the same pair the
2510 mockup transitions. Document styles win over the component's own
2511 :host rules, which is the sanctioned way to restyle a host. */
2512 .dm-agents__cast-card,
2513 .dm-agents__cast-new {
2514 --os-ui-card-padding: 20px 18px 16px;
2515 transition: border-color 0.18s ease, transform 0.18s ease;
2516 }
2517
2518 .dm-agents__cast-card:hover,
2519 .dm-agents__cast-card:focus-within {
2520 border-color: var( --os-ui-border-strong, #8c8f94 );
2521 transform: translateY( -2px );
2522 }
2523
2524 /* The dashed door, same as the mockup's crew-new card. */
2525 .dm-agents__cast-new {
2526 border-style: dashed;
2527 --os-ui-card-bg: var( --os-ui-surface-subtle, transparent );
2528 }
2529
2530 /* The crew breathes. Transform only, staggered so five faces do not
2531 bob in lockstep, and gone entirely under reduced motion. */
2532 @media ( prefers-reduced-motion: no-preference ) {
2533 .dm-agents__cast-card .dm-agents__cast-face {
2534 animation: dm-agents-bob 6s ease-in-out infinite;
2535 }
2536 .dm-agents__cast-card:nth-child( 2n ) .dm-agents__cast-face {
2537 animation-duration: 7.2s;
2538 animation-delay: -1.4s;
2539 }
2540 .dm-agents__cast-card:nth-child( 3n ) .dm-agents__cast-face {
2541 animation-duration: 6.6s;
2542 animation-delay: -2.8s;
2543 }
2544 }
2545
2546 @keyframes dm-agents-bob {
2547 0%,
2548 100% {
2549 transform: translateY( 0 );
2550 }
2551 50% {
2552 transform: translateY( -4px );
2553 }
2554 }
2555
2556 /* The column lives on a wrapper we own, not on the host: the slotted
2557 children sit inside the card's own container, so a flex rule on the
2558 host never reaches them and the role badges land wherever the
2559 description's line count leaves them. */
2560 .dm-agents__cast-inner {
2561 display: flex;
2562 flex-direction: column;
2563 align-items: center;
2564 text-align: center;
2565 gap: 2px;
2566 height: 100%;
2567 }
2568
2569 /* The faces are the loud part of this surface, and deliberately so:
2570 agent identity is the one place the station spends the mesh budget
2571 on something other than a hero CTA. A cast has to be legible at a
2572 glance, which is exactly what a flat accent cannot do. */
2573 .dm-agents__cast-face {
2574 width: 118px;
2575 height: 118px;
2576 margin: -10px 0 6px;
2577 flex: none;
2578 }
2579
2580 .dm-agents__cast-name {
2581 font-size: 17px;
2582 font-weight: 600;
2583 color: var( --os-ui-fg, #1d2327 );
2584 }
2585
2586 /* The voice line, in the instrument register: this is metadata about
2587 the agent, not prose from it. */
2588 .dm-agents__cast-vibes {
2589 font-family: var( --os-ui-font-mono, ui-monospace, monospace );
2590 font-size: 11.5px;
2591 color: var( --os-ui-accent-strong, #2271b1 );
2592 }
2593
2594 .dm-agents__cast-good {
2595 font-size: 13px;
2596 color: var( --os-ui-fg-muted, #646970 );
2597 margin-block: 4px 14px;
2598 flex: 1;
2599 }
2600
2601 /* The role pill in the instrument voice: mono, uppercase, no status
2602 dot — this is a fact about the agent, not a state to monitor. The
2603 custom properties are the component's own theming hooks; the
2604 inherited text properties flow into its shadow label. */
2605 .dm-agents__cast-card os-badge {
2606 --os-ui-badge-font: 500 11px/1.6 var( --os-ui-font-mono, ui-monospace, monospace );
2607 --os-ui-badge-dot-size: 0;
2608 --os-ui-badge-gap: 0;
2609 --os-ui-badge-padding: 3px 10px;
2610 text-transform: uppercase;
2611 letter-spacing: 0.08em;
2612 }
2613
2614 /* The framework-off bar: the section's status and its one action, on
2615 one line above the crew it would hire. It is deliberately not an
2616 `os-empty-state` — that block is centred, icon-led and paragraph-
2617 shaped, which is exactly the wall of text the cast replaced, and at
2618 five cards tall it would push the button that undoes the situation
2619 off the bottom of the window. `flex: none` because `.dm-agents__view`
2620 is a scrolling flex column and the bar must not absorb the slack. */
2621 .dm-agents__off-head {
2622 display: flex;
2623 align-items: center;
2624 justify-content: space-between;
2625 flex-wrap: wrap;
2626 flex: none;
2627 gap: 8px 16px;
2628 padding: 12px 14px;
2629 border-radius: 10px;
2630 background: var( --os-ui-surface-elevated, #fff );
2631 border: 1px solid var( --os-border, rgba( 0, 0, 0, 0.08 ) );
2632 }
2633
2634 .dm-agents__off-copy {
2635 min-width: 0;
2636 }
2637
2638 .dm-agents__off-copy h3 {
2639 margin: 0;
2640 }
2641
2642 .dm-agents__off-copy p {
2643 margin: 2px 0 0;
2644 font-size: 13px;
2645 color: var( --os-ui-fg-muted, #646970 );
2646 }
2647
2648 /* The cast the site would get, drawn while the framework is off.
2649 Dimmed as a whole rather than desaturated: the five hues ARE the
2650 argument this state is making, and a greyscale filter would flatten
2651 the crew into five identical blobs on the way to saying "not yet".
2652 Opacity says inactive and leaves the faces telling each other apart.
2653
2654 `pointer-events` is belt and braces — the cards are rendered without
2655 `interactive` and without a click handler, so there is nothing to
2656 press. It exists so a hover state cannot promise otherwise. */
2657 .dm-agents__cast--preview {
2658 opacity: 0.6;
2659 pointer-events: none;
2660 }
2661
2662 .dm-agents__cast-new .dm-agents__cast-plus {
2663 display: grid;
2664 place-items: center;
2665 width: 118px;
2666 height: 118px;
2667 margin: -10px 0 6px;
2668 border-radius: 50%;
2669 border: 1.5px dashed var( --os-ui-border-strong, #8c8f94 );
2670 font-size: 30px;
2671 font-weight: 300;
2672 line-height: 1;
2673 color: var( --os-ui-fg-muted, #646970 );
2674 }
2675
2676 /* ---------------------------------------------------------------------
2677 The wizard
2678 ------------------------------------------------------------------ */
2679
2680 .dm-agents__wizard {
2681 display: flex;
2682 flex-direction: column;
2683 gap: 10px;
2684 }
2685
2686 .dm-agents__wiz-head {
2687 display: flex;
2688 align-items: center;
2689 justify-content: space-between;
2690 gap: 8px 12px;
2691 flex-wrap: wrap;
2692 }
2693
2694 .dm-agents__wiz-head h3 {
2695 margin: 0;
2696 }
2697
2698 /* The trail wears the instrument voice: mono uppercase labels, 22px
2699 dots, a thin rule between stations. All of it goes through the
2700 component's own theming hooks; the host elements live in this light
2701 DOM, so the per-state overrides below can select them directly. */
2702 .dm-agents__trail {
2703 margin-block: 6px 14px;
2704 --os-ui-steps-gap: 10px;
2705 --os-ui-step-gap: 7px;
2706 --os-ui-step-chip-size: 22px;
2707 --os-ui-step-chip-font-size: 11px;
2708 --os-ui-step-chip-family: var( --os-ui-font-mono, ui-monospace, monospace );
2709 --os-ui-step-title-size: 11px;
2710 --os-ui-step-title-family: var( --os-ui-font-mono, ui-monospace, monospace );
2711 --os-ui-step-title-transform: uppercase;
2712 --os-ui-step-title-spacing: 0.08em;
2713 }
2714
2715 /* A station you have not reached is an outline, not a filled dot: the
2716 mesh chip is where you ARE, and it only reads that way if it is the
2717 only one lit. */
2718 .dm-agents__trail os-step:not( [ done ] ):not( [ current ] ) {
2719 --os-ui-step-chip-bg: transparent;
2720 --os-ui-step-chip-border: 1px solid var( --os-ui-border-strong, #8c8f94 );
2721 --os-ui-step-chip-fg: var( --os-ui-fg-muted, #646970 );
2722 }
2723
2724 /* Done is quiet: a filled tick on the elevated surface, muted like its
2725 label. It reads as history, not as a second highlight. */
2726 .dm-agents__trail os-step[ done ] {
2727 --os-ui-step-chip-done-bg: var( --os-ui-surface-elevated, #f0f0f1 );
2728 --os-ui-step-chip-fg: var( --os-ui-fg-muted, #646970 );
2729 }
2730
2731 .dm-agents__wiz-heading {
2732 margin: 12px 0 2px;
2733 font-size: 16px;
2734 font-weight: 600;
2735 color: var( --os-ui-fg, #1d2327 );
2736 }
2737
2738 /* The door. Five complete agents already ship with the plugin, and
2739 until now the create flow showed them to nobody. */
2740 .dm-agents__starters {
2741 display: grid;
2742 gap: 12px;
2743 grid-template-columns: repeat( auto-fill, minmax( 148px, 1fr ) );
2744 margin-block: 8px 16px;
2745 }
2746
2747 .dm-agents__starter {
2748 display: flex;
2749 flex-direction: column;
2750 align-items: center;
2751 text-align: center;
2752 gap: 3px;
2753 --os-ui-card-padding: 16px 14px;
2754 --os-ui-card-radius: 10px;
2755 --os-ui-card-bg: var( --os-ui-surface-subtle, transparent );
2756 transition: border-color 0.18s ease;
2757 }
2758
2759 .dm-agents__starter:hover,
2760 .dm-agents__starter:focus-within {
2761 border-color: var( --os-ui-border-strong, #8c8f94 );
2762 }
2763
2764 .dm-agents__starter-face {
2765 width: 76px;
2766 height: 76px;
2767 margin: -8px 0 4px;
2768 flex: none;
2769 }
2770
2771 .dm-agents__starter-name {
2772 font-size: 14px;
2773 font-weight: 500;
2774 color: var( --os-ui-fg, #1d2327 );
2775 }
2776
2777 .dm-agents__starter-vibes {
2778 font-family: var( --os-ui-font-mono, ui-monospace, monospace );
2779 font-size: 11.5px;
2780 color: var( --os-ui-fg-muted, #646970 );
2781 }
2782
2783 /* Meet: the portrait beside the fields, so the thing being named is
2784 visible while it is being named. */
2785 .dm-agents__meet {
2786 display: grid;
2787 gap: 20px;
2788 grid-template-columns: 220px 1fr;
2789 align-items: start;
2790 }
2791
2792 /* The character card. Sunken rather than subtle: the portrait is the
2793 hero of this step, and the mockup floats it on the darkest surface
2794 so the face's own glow does the framing. */
2795 .dm-agents__portrait {
2796 display: flex;
2797 flex-direction: column;
2798 align-items: center;
2799 gap: 10px;
2800 padding: 20px 14px 14px;
2801 border: 1px solid var( --os-border, rgba( 0, 0, 0, 0.08 ) );
2802 border-radius: var( --os-ui-card-radius, 10px );
2803 background: var( --os-ui-surface-sunken, #f6f7f7 );
2804 }
2805
2806 .dm-agents__portrait-face {
2807 width: 176px;
2808 height: 176px;
2809 max-width: 100%;
2810 margin-top: -12px;
2811 }
2812
2813 /* What this face is, in two words. */
2814 .dm-agents__portrait-chips {
2815 display: flex;
2816 gap: 6px;
2817 flex-wrap: wrap;
2818 justify-content: center;
2819 }
2820
2821 .dm-agents__faces {
2822 display: grid;
2823 grid-template-columns: repeat( 6, 1fr );
2824 gap: 2px;
2825 width: 100%;
2826 }
2827
2828 .dm-agents__face-pick {
2829 display: grid;
2830 place-items: center;
2831 padding: 2px;
2832 border: 1px solid transparent;
2833 border-radius: 8px;
2834 background: none;
2835 cursor: pointer;
2836 }
2837
2838 .dm-agents__face-pick img {
2839 width: 100%;
2840 height: auto;
2841 display: block;
2842 }
2843
2844 .dm-agents__face-pick:hover {
2845 border-color: var( --os-ui-border-strong, #8c8f94 );
2846 }
2847
2848 .dm-agents__face-pick.is-picked {
2849 border-color: var( --os-ui-accent, #2271b1 );
2850 }
2851
2852 .dm-agents__face-pick:focus-visible {
2853 outline: var( --os-ui-focus-ring, 2px solid #2271b1 );
2854 outline-offset: 1px;
2855 }
2856
2857 .dm-agents__meet-fields {
2858 display: flex;
2859 flex-direction: column;
2860 gap: 10px;
2861 min-width: 0;
2862 }
2863
2864 /* Launch: one card, the brand's card radius, floating on the darkest
2865 surface like the portrait it echoes. */
2866 .dm-agents__summary {
2867 display: grid;
2868 grid-template-columns: 106px 1fr;
2869 gap: 18px;
2870 align-items: start;
2871 --os-ui-card-padding: 18px;
2872 --os-ui-card-radius: 10px;
2873 --os-ui-card-bg: var( --os-ui-surface-sunken, #f6f7f7 );
2874 }
2875
2876 .dm-agents__summary-face {
2877 width: 106px;
2878 height: 106px;
2879 flex: none;
2880 }
2881
2882 .dm-agents__summary-text h4 {
2883 margin: 0 0 2px;
2884 font-size: 17px;
2885 }
2886
2887 .dm-agents__summary-vibes {
2888 font-family: var( --os-ui-font-mono, ui-monospace, monospace );
2889 font-size: 11.5px;
2890 color: var( --os-ui-accent-strong, #2271b1 );
2891 margin: 0 0 6px;
2892 }
2893
2894 .dm-agents__summary-desc {
2895 margin: 0;
2896 font-size: 13px;
2897 color: var( --os-ui-fg-muted, #646970 );
2898 }
2899
2900 .dm-agents__chips {
2901 display: flex;
2902 flex-wrap: wrap;
2903 gap: 6px;
2904 margin-block-start: 10px;
2905 }
2906
2907 /* What the agent will actually be told, under the chips. Clamped: a
2908 summary card quotes the instructions, it does not host them. */
2909 .dm-agents__summary-instr {
2910 margin: 10px 0 0;
2911 font-size: 13px;
2912 color: var( --os-ui-fg-muted, #646970 );
2913 display: -webkit-box;
2914 -webkit-line-clamp: 3;
2915 -webkit-box-orient: vertical;
2916 overflow: hidden;
2917 }
2918
2919 /* Identity chips — role, abilities, silhouette, hue — all wear the
2920 instrument voice: these are facts about the character, stamped
2921 rather than said. */
2922 .dm-agents__chips os-chip,
2923 .dm-agents__portrait-chips os-chip {
2924 --os-ui-chip-font-size: 10.5px;
2925 --os-ui-chip-font-weight: 500;
2926 --os-ui-chip-bg: transparent;
2927 --os-ui-chip-fg: var( --os-ui-fg-muted, #646970 );
2928 --os-ui-chip-border: 1px solid var( --os-border, rgba( 0, 0, 0, 0.12 ) );
2929 font-family: var( --os-ui-font-mono, ui-monospace, monospace );
2930 text-transform: uppercase;
2931 letter-spacing: 0.06em;
2932 }
2933
2934 .dm-agents__spacer {
2935 flex: 1;
2936 }
2937
2938 @media ( max-width: 640px ) {
2939 .dm-agents__meet,
2940 .dm-agents__summary {
2941 grid-template-columns: 1fr;
2942 }
2943 .dm-agents__portrait-face {
2944 width: 128px;
2945 height: 128px;
2946 }
2947 }
2948
2949 /* The way back to the grid. The window's own breadcrumb goes UP to the
2950 folder, which is a different journey: this one returns to the cast. */
2951 .dm-agents__back {
2952 align-self: flex-start;
2953 margin-block-end: 4px;
2954 }
2955