/** * My WordPress — window-local layout. * * Two-pane file-explorer for browsing WordPress entities. Mirrors the * Recycle Bin and Posts window CSS conventions (`os-*` * BEM-ish prefix, container-relative geometry, theming via the * os-variables CSS custom properties). * */ .desktop-mode-my-wordpress { display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; font-family: var( --os-font, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif ); color: var( --os-my-wordpress-fg, var( --os-ui-fg, #1d2327 ) ); /* Window background — separate from the wallpaper bg so the * folder window reads as a real OS window even when the desktop * gradient is dark. Themes / plugins can override * `--os-my-wordpress-bg` to repaint just this surface * without touching every other window. */ background: var( --os-my-wordpress-bg, var( --os-window-bg, #fff ) ); /* Tile color tokens — same recipe as the folder window. Both * chain through the palette so the surface follows the active * desktop theme, with the pre-brand WordPress-admin literal as * the floor if no stylesheet declares one. * * `--os-tile-fg-muted` used to be the bare literal below, which * outranked the palette's own value and painted every tile's * secondary line near-black — invisible on a dark station, while * the label right beside it followed the theme correctly. A * colour declared next to the thing it paints is out of reach of * the palette AND of every desktop theme. */ --os-tile-fg: var( --os-my-wordpress-fg, var( --os-ui-fg, #1d2327 ) ); --os-tile-fg-muted: var( --os-my-wordpress-fg-muted, var( --os-ui-fg-muted, rgba( 0, 0, 0, 0.55 ) ) ); --os-tile-hover-bg: var( --os-hover, rgba( 0, 0, 0, 0.06 ) ); /* Light-context label rendering — same recipe as the folder * window. Defined as tokens in `variables.css`; the single * `.os-file-tile__label` rule reads them for every * surface. */ --os-tile-label-shadow: none; --os-tile-label-weight: 500; --os-tile-label-smoothing: antialiased; --os-tile-label-color: var( --os-my-wordpress-fg, var( --os-ui-fg, #1d2327 ) ); } /* Pinned-icon visual cue — used by `os-icon--pinned` (the * generic flag added in 0.8.0). The icon-rail itself owns the * non-draggable invariant; this rule just removes the grab affordance * if the framework ever attaches one in a later phase. */ .os-icon--pinned { cursor: pointer; } /* Breadcrumb header chrome — owned by the shared * `.os-breadcrumbs` rules in `desktop-files.css`. No * per-surface duplication. */ /* ----- Body host ----- */ .os-my-wordpress__body { flex: 1 1 auto; min-height: 0; overflow: hidden; position: relative; display: flex; flex-direction: column; } /* List-view toolbar — sits between the breadcrumb header and the * split pane in every entity list view (Posts, Pages, Users, Media). * Holds the server-side search input today; reserved for future * filter / sort controls. Visual treatment mirrors the Content * Graph toolbar's search row so the two surfaces read as the same * control. */ .os-my-wordpress__list-toolbar { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var( --os-ui-border, #dcdcde ); /* Opaque and above the canvas: the tile canvas scrolls beneath * this row, and a transparent toolbar let tiles read through it. */ position: relative; z-index: 4; background: var( --os-ui-bg, #fff ); } .os-my-wordpress__list-toolbar-search { position: relative; flex: 1 1 auto; min-width: 0; max-width: 360px; } .os-my-wordpress__list-toolbar-search-input { width: 100%; padding: 6px 10px; border: 1px solid var( --os-ui-border, #d8dde4 ); border-radius: 6px; background: var( --os-ui-surface, #fff ); color: inherit; font-size: 13px; box-sizing: border-box; } .os-my-wordpress__list-toolbar-search-input:focus { outline: none; border-color: var( --os-ui-accent, #2c6be5 ); box-shadow: 0 0 0 3px var( --os-ui-accent-soft, rgba( 44, 107, 229, 0.18 ) ); } /* When the list-toolbar mounts, the split pane needs to claim the * remaining vertical space inside the flex column body — without * this rule it would size to its content (which, for an empty list, * is zero) and the tile canvas would collapse. */ .os-my-wordpress__body > .os-my-wordpress__split { flex: 1 1 auto; min-height: 0; } /* Search-in-progress dimming. The previous result set stays in place * (no skeleton flash, no empty intermediate page) but reads as * "stale" until the new page lands and the renderer atomically swaps * the tiles. `pointer-events: none` blocks the user from clicking * a tile that's about to disappear. */ .os-my-wordpress__tiles--searching, .os-my-wordpress__media-grid--searching { opacity: 0.45; pointer-events: none; transition: opacity 0.12s ease-out; } /* Reuse the file-system folder window's status-bar element * (`.os-folder-status-bar`) so segments render with the * exact same DOM the file-system uses — but retone it for the * white window background. The dark default (white-on-dark) reads * as invisible against `--os-window-bg`. */ .desktop-mode-my-wordpress > .os-folder-status-bar { border-top: 1px solid var( --os-ui-border, #dcdcde ); background: transparent; color: var( --os-ui-fg-muted, #50575e ); } .desktop-mode-my-wordpress > .os-folder-status-bar button.os-folder-status-bar__segment:hover { background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ) ); } /* ----- Tile canvas (root + entity list) ----- * * Both views use the SAME tile element (`.os-file-tile`) * the wallpaper uses, so visuals stay 1:1 with the desktop. The * canvas is `position: relative`; tiles are absolute-positioned and * draggable — same interaction pattern as the wallpaper, just * scoped to the window. Positions persist per-window in * `localStorage` (see `createTileLayout` in src/my-wordpress). */ .os-my-wordpress__canvas { position: relative; min-height: 100%; box-sizing: border-box; /* Note — DO NOT add `overflow: auto` here. Scroll is owned by * the surrounding pane (`__list` for entity views, `__body` for * the root view via `__grid` below). A second scroller here * would steal the wheel from the outer pane and break the * IntersectionObserver-based infinite scroll. */ } .os-my-wordpress__grid { height: 100%; overflow-y: scroll; } /* Override the wallpaper file-tile palette for in-window context. * On the wallpaper the file tile sits on a dark gradient and uses * white text + a 12% white hover — both of which would be invisible * on a white window background. Inside My WordPress we re-tone the * same element to read on light surfaces. The dimensions / tile * structure are inherited from `.os-file-tile`. */ .desktop-mode-my-wordpress .os-file-tile { /* Color + hover come from `--os-tile-*` — see the * scope-level variable overrides on `.desktop-mode-my-wordpress` * above. We only override structural / animation bits here. * * Drop the wallpaper's `transition: transform` — it hints at * GPU compositing in Chrome, and the moment the layer is * composited the label rasterizes against fractional device * pixels (sub-pixel blur on HiDPI). We don't animate transform * here, so the transition is dead code anyway. */ transition: background-color 0.15s ease; transform: none; } /* Selected state for every tile in My WordPress is rendered by * the canonical `.os-file-tile--selected` rule in * `desktop-files.css` — same accent border + tinted background * the folder windows + wallpaper tiles use. No section-specific * override here — visual parity across the whole shell. */ /* Large tiles — sections that opt in with `tileSize: 'large'`. * * A section whose rows carry a photograph (a shop's products) reads * as a catalogue, not a file list: the picture is what's being * scanned, and at 48px it's a thumbnail of a thumbnail. Roughly * doubling the icon well also gives a corner ribbon room to be a * corner flash instead of covering the subject. * * The cell pitch lives in `TILE_METRICS_LARGE` (src/my-wordpress/ * index.ts) and the two MUST move together — a wider tile in a cell * that didn't grow overlaps its neighbour. */ /* * Image-led sections re-point the tile tokens for their whole * subtree rather than overriding the tile's width directly. The base * `.os-file-tile` rule then sizes itself from them, and so does * everything derived from them (the label's max-width, the loading * skeletons) — one declaration instead of a chain of overrides that * have to be kept in step. */ .os-my-wordpress__tiles--large { --os-tile-w: var( --os-tile-w-large, 132px ); --os-tile-h: var( --os-tile-h-large, 160px ); } .os-my-wordpress__tiles--large .os-file-tile__visual, .os-my-wordpress__tiles--large .os-file-tile__icon { width: 104px; height: 104px; font-size: 64px; } .os-my-wordpress__tiles--large .os-file-tile__preview { width: 104px; height: 104px; border-radius: 8px; } /* Three lines of title at this width instead of two — a product name * is long and the extra width earns the room. Matches the taller * cell in `TILE_METRICS_LARGE`. */ .os-my-wordpress__tiles--large .os-file-tile__label { -webkit-line-clamp: 3; } /* Featured-image tiles. Entries that have a featured image render it * in the icon well instead of the section dashicon — decisive for * image-led types like WooCommerce products. The base * `.os-file-tile__preview` rule in `desktop-files.css` * already sizes and crops it; all that's added here is a hairline * border so a light-on-light photo still reads as a distinct object * against the wallpaper, plus a neutral well behind it so a * transparent PNG or a slow-loading image doesn't flash the * wallpaper through. */ .os-my-wordpress__tile--entry .os-file-tile__preview { background: var( --os-media-tile-bg, rgba( 0, 0, 0, 0.05 ) ); /* The hairline was a flat 12%-alpha black — right on the pre-brand * white canvas, and nothing at all on a dark one, where a black * line over a near-black surface leaves the photo edgeless. It * follows the border token now, so it inverts with the theme. */ box-shadow: inset 0 0 0 1px var( --os-ui-border, rgba( 0, 0, 0, 0.12 ) ); } /* Banded list views. A section can split its tiles into labelled * bands (WooCommerce Orders groups by status, needs-attention first) * instead of one flat canvas. The root container stops being a * positioned canvas and becomes a plain stack; each band owns its * own canvas below its heading. */ .os-my-wordpress__tiles--banded { position: static; min-height: 0; } .os-my-wordpress__band + .os-my-wordpress__band { margin-block-start: 8px; } /* Bands are laid out in order before the first page arrives so none * appears mid-scroll, but one that hasn't received a row yet stays * out of the way rather than showing a heading over nothing. */ .os-my-wordpress__band--empty { display: none; } .os-my-wordpress__band-title { display: flex; align-items: center; gap: 8px; position: sticky; /* Keeps the band label visible while scrolling a long band — * the whole point of banding is knowing what you're looking at. */ top: 0; /* Above the absolutely-positioned tiles of every band, including * the ones that come later in the DOM. */ z-index: 3; margin: 0; padding: 6px 12px; font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var( --os-ui-fg-muted, #787c82 ); /* Near-opaque on purpose. Tiles scrolling underneath are behind * the bar, but at 85% they still read as ghostly blurred shapes * through it, which looks like a rendering fault rather than * frosted glass. The blur + saturate keeps the frosted feel while * the alpha stops the content beneath resolving into shapes. */ background: color-mix( in srgb, var( --os-ui-bg, #fff ) 94%, transparent ); backdrop-filter: blur( 14px ) saturate( 180% ); border-block-end: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) ); } /* Browsers without `color-mix()` fall back to the flat surface — * still opaque, just without the translucency. */ @supports not ( background: color-mix( in srgb, red 50%, transparent ) ) { .os-my-wordpress__band-title { background: var( --os-ui-bg, #fff ); } } /* Attention-carrying bands (WooCommerce's "Needs attention", * "Problems") tint their bar so the eye lands there first instead of * having to read every heading. */ .os-my-wordpress__band-title--warn { color: var( --os-ui-warning, #8a6116 ); background: color-mix( in srgb, var( --os-ui-warning, #8a6116 ) 12%, var( --os-ui-bg, #fff ) ); border-block-end-color: color-mix( in srgb, var( --os-ui-warning, #8a6116 ) 35%, transparent ); } .os-my-wordpress__band-title--danger { color: var( --os-ui-danger, #b32d2e ); background: color-mix( in srgb, var( --os-ui-danger, #b32d2e ) 12%, var( --os-ui-bg, #fff ) ); border-block-end-color: color-mix( in srgb, var( --os-ui-danger, #b32d2e ) 35%, transparent ); } .os-my-wordpress__band-count { padding: 0 6px; border-radius: 999px; font-size: 11px; line-height: 1.7; background: color-mix( in srgb, currentColor 16%, transparent ); color: inherit; } /* Lock state — another user is currently editing this post. We dim * the tile a touch and overlay a small dashicon-lock badge on the * top-right of the icon. The whole tile stays clickable; the lock * is just a visual heads-up so the user knows the takeover dialog * is coming if they double-click. */ .os-my-wordpress__tile--locked { opacity: 0.85; } .os-my-wordpress__tile-lock { position: absolute; top: 4px; inset-inline-end: 14px; width: 18px; height: 18px; font-size: 12px; line-height: 18px; text-align: center; border-radius: 50%; background: var( --os-danger, var( --os-ui-danger, #b32d2e ) ); color: var( --os-ui-fg-on-accent, #fff ); box-shadow: 0 0 0 1.5px rgba( 0, 0, 0, 0.4 ), 0 1px 3px rgba( 0, 0, 0, 0.35 ); pointer-events: none; } /* Hover-card lock banner — shown above the excerpt when present. The * wash was a fixed 8%-alpha red, which on a dark card was a smear too * faint to read as a banner at all; it follows the badge-danger token * now, so it lands at the alpha the active theme picked. */ .os-my-wordpress__tooltip-lock { display: flex; align-items: center; gap: 6px; margin: 0 0 8px; padding: 6px 8px; border-radius: 4px; background: var( --os-my-wordpress-card-lock-bg, rgba( 179, 45, 46, 0.08 ) ); color: var( --os-danger, var( --os-ui-danger, #b32d2e ) ); font-size: 12px; font-weight: 600; } .os-my-wordpress__tooltip-lock .dashicons { font-size: 14px; width: 14px; height: 14px; } /* Plain two-line clamp without `-webkit-line-clamp`. The webkit * variant promotes the label to its own composited layer, which on * HiDPI screens snaps text to fractional device pixels and renders * blurry. `max-height: 2 × line-height` + `overflow: hidden` looks * the same and stays sharp. */ .os-my-wordpress__tile .os-file-tile__label { font-size: 12px; line-height: 1.3; max-height: calc( 1.3em * 2 ); text-align: center; overflow: hidden; max-width: 100%; word-break: break-word; } /* ----- Entity list: two-pane split ----- */ .os-my-wordpress__split { display: grid; grid-template-columns: minmax( 240px, 60% ) minmax( 0, 1fr ); height: 100%; min-height: 0; } .os-my-wordpress__list { overflow-y: scroll; border-inline-end: 1px solid var( --os-ui-border, #dcdcde ); min-width: 0; } /* Always-visible scrollbar styling for the icon canvases. macOS * defaults to overlay scrollbars that hide until a wheel/scroll * event — fine for documents but actively confusing on a tile * canvas where the user can't tell whether more icons live below * or to the right. We force the scrollbar to render at all times * via `::-webkit-scrollbar` (works in WebKit / Blink / Edge), and * the explicit `scrollbar-width: thin` covers Firefox. */ .os-my-wordpress__list, .os-my-wordpress__grid { scrollbar-width: thin; scrollbar-color: rgba( 0, 0, 0, 0.25 ) transparent; } .os-my-wordpress__list::-webkit-scrollbar, .os-my-wordpress__grid::-webkit-scrollbar { width: 12px; height: 12px; } .os-my-wordpress__list::-webkit-scrollbar-thumb, .os-my-wordpress__grid::-webkit-scrollbar-thumb { background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.25 ) ); border-radius: 6px; border: 3px solid transparent; background-clip: padding-box; } .os-my-wordpress__list::-webkit-scrollbar-thumb:hover, .os-my-wordpress__grid::-webkit-scrollbar-thumb:hover { background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.4 ) ); background-clip: padding-box; } .os-my-wordpress__list::-webkit-scrollbar-track, .os-my-wordpress__grid::-webkit-scrollbar-track { background: transparent; } .os-my-wordpress__sentinel { height: 1px; } /* Tile-grid loading skeleton — placeholder tiles that mimic the * icon + label shape of what's about to load. We dropped the * spinning WordPress mark here because it read as an alien * affordance inside the file-explorer surface; a skeleton signals * "tiles are landing in these slots" instead of "the system is * busy doing something opaque." * * Each placeholder is parented directly to the positioned canvas * (`__canvas--positioned`) and `position: absolute` at the * (left, top) the next real tile will occupy — see * `layout.peekNextCells()` and `showLoadingSkeleton()` in * `src/my-wordpress/index.ts`. Per-tile shimmer delay and label * width are inlined so a row of placeholders reads as a cascade, * not a uniform pulse. * * Tile dimensions mirror the real `.os-file-tile` width * so a placeholder sits cleanly in the cell its successor will * inherit. */ .os-my-wordpress__skeleton-tile { position: absolute; display: flex; flex-direction: column; align-items: center; gap: 6px; /* Same box as the real tile it stands in for — a skeleton that * doesn't match the grid makes the list jump when rows land. */ width: var( --os-tile-w, 88px ); height: var( --os-tile-h, 104px ); padding: 8px 4px; box-sizing: border-box; pointer-events: none; } .os-my-wordpress__skeleton-icon, .os-my-wordpress__skeleton-label { background: linear-gradient( 90deg, var( --os-skeleton-low, var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) ) ) 0%, var( --os-skeleton-high, var( --os-ui-hover, rgba( 0, 0, 0, 0.13 ) ) ) 50%, var( --os-skeleton-low, var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) ) ) 100% ); background-size: 200% 100%; animation: os-my-wordpress-skeleton-shimmer 1.4s ease-in-out var( --os-skeleton-delay, 0s ) infinite; } .os-my-wordpress__skeleton-icon { width: 44px; height: 44px; border-radius: 10px; } .os-my-wordpress__skeleton-label { width: 72%; height: 10px; border-radius: 4px; } @keyframes os-my-wordpress-skeleton-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } } @media ( prefers-reduced-motion: reduce ) { .os-my-wordpress__skeleton-icon, .os-my-wordpress__skeleton-label { animation: none; } } /* ----- Right pane: preview ----- */ .os-my-wordpress__preview { overflow: auto; min-width: 0; background: transparent; } .os-my-wordpress__preview-empty { display: flex; flex-direction: column; gap: 6px; align-items: center; justify-content: center; height: 100%; color: var( --os-ui-fg-muted, #787c82 ); font-size: 13px; padding: 24px; text-align: center; } /* Type / status breakdown under a multi-selection's count. */ .os-my-wordpress__preview-selection-breakdown { font-size: 12px; opacity: 0.75; } /* One labelled control in the bulk-edit modal. Label above field, * because the fields are wide (a category tree, a tag row) and a * two-column grid would put a one-word label beside a control four * times its height. */ .os-my-wordpress__bulk-row { display: flex; flex-direction: column; gap: 6px; margin-block-end: 16px; } .os-my-wordpress__bulk-label { font-size: 12px; font-weight: 600; color: var( --os-ui-fg-muted, #787c82 ); } .os-my-wordpress__preview-loading { display: flex; align-items: center; justify-content: center; height: 100%; min-height: 320px; padding: 32px; box-sizing: border-box; } /* Preview-pane loader — same scale curve as the tile-grid first * spinner and the window-loading overlay. The JS used to hard-code * `size="128"`, which set an inline `--os-ui-spinner-size` that * overrode any CSS knob; the size attribute is now omitted so this * rule is the single source of truth for both the My WordPress * preview and the wallpaper file-preview pane (which reuses this * class deliberately). */ .os-my-wordpress__preview-loading os-spinner { --os-ui-spinner-size: clamp( 96px, 14vw, 192px ); } .os-my-wordpress__article { padding: 24px 32px; max-width: 720px; margin: 0 auto; } .os-my-wordpress__article-title { font-size: 22px; margin: 0 0 8px; line-height: 1.3; } .os-my-wordpress__article-meta { margin: 0 0 16px; color: var( --os-ui-fg-muted, #50575e ); font-size: 12px; } .os-my-wordpress__article-hero { width: 100%; height: auto; border-radius: 6px; margin-bottom: 16px; display: block; } .os-my-wordpress__article-content { font-size: 14px; line-height: 1.6; } .os-my-wordpress__article-content img { max-width: 100%; height: auto; } .os-my-wordpress__article-footer { margin-top: 24px; padding-top: 16px; border-top: 1px solid var( --os-ui-border, #dcdcde ); display: flex; justify-content: flex-end; align-items: center; gap: 8px; flex-wrap: wrap; } /* ----- Empty / error states ----- */ .os-my-wordpress__empty, .os-my-wordpress__error { padding: 32px 24px; text-align: center; color: var( --os-ui-fg-muted, #787c82 ); font-size: 13px; } .os-my-wordpress__error { color: var( --os-danger, var( --os-ui-danger, #b32d2e ) ); } /* ----- Hover card (floats over body) ----- * * The card is summoned from a tile and belongs to the window that * tile is in, so it follows the active desktop theme rather than the * shell's tooltip chip. Every colour chains * `--os-my-wordpress-card-*` first — derived in `variables.css` from * the window family, so a theme that repaints the window repaints the * card with it — then the chip tokens, then the pre-brand literal. * * It is appended to `document.body`, not to the window, because it is * `position: fixed` and a window with `overflow: hidden` would clip * it. Theme tokens still reach it: a desktop theme declares on * `body.os-desktop-theme-`, an ancestor of both. What does NOT * reach it is anything scoped to `.desktop-mode-my-wordpress`, which * is why the card family is declared at palette level. */ .os-my-wordpress__tooltip { position: fixed; z-index: 100000; max-width: 320px; background: var( --os-my-wordpress-card-bg, var( --os-tooltip-bg, var( --os-surface, var( --os-ui-surface, #fff ) ) ) ); color: var( --os-my-wordpress-card-fg, var( --os-tooltip-fg, var( --os-fg, #1d2327 ) ) ); /* The card lands on the same colour as the window under it — the * border and the shadow are the only reason it reads as a separate * object, so neither is decoration. */ border: 1px solid var( --os-my-wordpress-card-border, #dcdcde ); border-radius: 6px; padding: 12px; box-shadow: var( --os-my-wordpress-card-shadow, 0 6px 24px rgba( 0, 0, 0, 0.18 ) ); font-size: 12px; pointer-events: none; } .os-my-wordpress__tooltip-title { font-weight: 600; margin-bottom: 6px; font-size: 13px; } /* Same neutral well the entry and media tiles give their thumbnails: * a transparent PNG or a still-loading image would otherwise show the * card straight through, and a photo whose edge matches the card * would have no boundary. */ .os-my-wordpress__tooltip-thumb { display: block; max-width: 100%; height: auto; border-radius: 4px; margin: 6px 0; background: var( --os-my-wordpress-card-thumb-bg, rgba( 0, 0, 0, 0.05 ) ); box-shadow: inset 0 0 0 1px var( --os-my-wordpress-card-border, #dcdcde ); } .os-my-wordpress__tooltip-excerpt { margin: 0; color: var( --os-my-wordpress-card-fg-muted, #50575e ); line-height: 1.4; } /* ----- Context menu host ----- */ .os-my-wordpress__menu { position: fixed; z-index: 100001; } /* --------------------------------------------------------------- * User dossier — right-pane preview when a person is selected. * Header (avatar + name + roles + links), bio, stat cards row, * 12-month activity sparkline, milestones, recent posts, top * categories. Designed to fit comfortably on a 360px-wide pane * but to stretch nicely up to ~720px before the line-length stops * helping readability. * --------------------------------------------------------------- */ .os-my-wordpress__user { max-width: 720px; } .os-my-wordpress__user-header { display: flex; gap: 16px; align-items: center; margin-bottom: 16px; } .os-my-wordpress__user-avatar { width: 96px; height: 96px; border-radius: 50%; flex-shrink: 0; box-shadow: 0 2px 8px rgba( 0, 0, 0, 0.12 ); } .os-my-wordpress__user-headline { flex: 1 1 auto; min-width: 0; } .os-my-wordpress__user .os-my-wordpress__article-title { margin: 0 0 4px; } .os-my-wordpress__user-roles { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; } .os-my-wordpress__user-role { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; background: rgba( 34, 113, 177, 0.12 ); color: var( --wp-admin-theme-color, #2271b1 ); } .os-my-wordpress__user-links { display: flex; gap: 12px; font-size: 12px; } .os-my-wordpress__user-links a { color: var( --os-link, var( --os-ui-accent, #2271b1 ) ); text-decoration: none; } .os-my-wordpress__user-links a:hover { text-decoration: underline; } .os-my-wordpress__user-bio { font-size: 14px; line-height: 1.5; color: var( --os-fg, #1d2327 ); margin: 0 0 16px; padding-left: 12px; border-left: 3px solid var( --os-ui-border, #dcdcde ); } /* ----- Stat cards row ----- */ .os-my-wordpress__user-stats { display: grid; grid-template-columns: repeat( auto-fit, minmax( 120px, 1fr ) ); gap: 8px; margin-bottom: 20px; } .os-my-wordpress__user-stat { display: flex; flex-direction: column; gap: 2px; padding: 12px 14px; border-radius: 8px; background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) ) ); border: 1px solid var( --os-ui-border, #dcdcde ); } .os-my-wordpress__user-stat-value { font-size: 26px; font-weight: 700; line-height: 1.1; color: var( --wp-admin-theme-color, #2271b1 ); font-variant-numeric: tabular-nums; } .os-my-wordpress__user-stat-label { font-size: 12px; color: var( --os-ui-fg-muted, #50575e ); text-transform: uppercase; letter-spacing: 0.04em; } .os-my-wordpress__user-stat-caption { font-size: 11px; color: var( --os-ui-fg-muted, #787c82 ); } /* ----- 12-month activity sparkline ----- */ .os-my-wordpress__user-section { margin-bottom: 20px; } .os-my-wordpress__user-section h3 { font-size: 13px; font-weight: 600; color: var( --os-ui-fg-muted, #50575e ); text-transform: uppercase; letter-spacing: 0.04em; margin: 0 0 8px; } .os-my-wordpress__user-spark-chart { display: grid; grid-template-columns: repeat( 12, 1fr ); gap: 4px; height: 80px; align-items: end; padding: 6px 0; } .os-my-wordpress__user-spark-col { display: flex; flex-direction: column; align-items: stretch; justify-content: flex-end; gap: 4px; height: 100%; min-height: 0; } .os-my-wordpress__user-spark-bar { background: linear-gradient( 180deg, var( --wp-admin-theme-color, #2271b1 ) 0%, rgba( 34, 113, 177, 0.55 ) 100% ); border-radius: 3px 3px 0 0; min-height: 2px; transition: height 0.3s ease; } .os-my-wordpress__user-spark-bar--empty { background: var( --os-ui-border, #dcdcde ); min-height: 2px; height: 2px !important; } .os-my-wordpress__user-spark-label { font-size: 10px; text-align: center; color: var( --os-ui-fg-muted, #787c82 ); } /* ----- Milestones ----- */ .os-my-wordpress__user-milestones { display: grid; grid-template-columns: repeat( auto-fit, minmax( 140px, 1fr ) ); gap: 12px; margin: 0 0 20px; } .os-my-wordpress__user-milestones dt { font-size: 11px; color: var( --os-ui-fg-muted, #50575e ); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 2px; } .os-my-wordpress__user-milestones dd { font-size: 14px; font-weight: 600; margin: 0; } /* ----- Recent posts list ----- */ .os-my-wordpress__user-recent { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; } .os-my-wordpress__user-recent li { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border-radius: 6px; background: var( --os-ui-hover, rgba( 0, 0, 0, 0.02 ) ); } .os-my-wordpress__user-recent a { font-weight: 600; color: var( --os-link, var( --os-ui-accent, #2271b1 ) ); text-decoration: none; } .os-my-wordpress__user-recent a:hover { text-decoration: underline; } .os-my-wordpress__user-recent-meta { font-size: 11px; color: var( --os-ui-fg-muted, #787c82 ); } /* ----- Top categories chips ----- */ .os-my-wordpress__user-chips { display: flex; flex-wrap: wrap; gap: 6px; } .os-my-wordpress__user-chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px; font-size: 12px; background: var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) ); border: 1px solid var( --os-ui-border, #dcdcde ); } .os-my-wordpress__user-chip--tag { background: rgba( 124, 58, 237, 0.08 ); border-color: rgba( 124, 58, 237, 0.25 ); } .os-my-wordpress__user-chip-count { font-weight: 700; color: var( --wp-admin-theme-color, #2271b1 ); font-variant-numeric: tabular-nums; } /* --------------------------------------------------------------- * Term dossier — same skeleton as the user dossier (header, stat * cards, sparkline, milestones, recent list, chips) plus a few * term-specific bits below: a colored taxonomy icon medallion in * the header, role badge variants for category vs tag, and a * "Top contributors" grid that pairs an author avatar with a * post count. * --------------------------------------------------------------- */ .os-my-wordpress__term-header { display: flex; align-items: center; gap: 16px; margin-bottom: 16px; } .os-my-wordpress__term-icon { width: 64px; height: 64px; border-radius: 12px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; color: var( --os-ui-fg-on-accent, #fff ); } .os-my-wordpress__term-icon--category { background: linear-gradient( 135deg, var( --os-ui-accent, #2271b1 ) 0%, var( --os-ui-accent-strong, #135e96 ) 100% ); box-shadow: 0 4px 12px rgba( 34, 113, 177, 0.32 ); } .os-my-wordpress__term-icon--tag { background: linear-gradient( 135deg, #7c3aed 0%, #5b21b6 100% ); box-shadow: 0 4px 12px rgba( 124, 58, 237, 0.32 ); } .os-my-wordpress__user-role--category { background: rgba( 34, 113, 177, 0.12 ); color: var( --os-ui-accent-strong, #135e96 ); } .os-my-wordpress__user-role--tag { background: rgba( 124, 58, 237, 0.12 ); color: #6d28d9; } /* Top contributors row — paired avatar + name + post count. */ .os-my-wordpress__term-authors { display: grid; grid-template-columns: repeat( auto-fit, minmax( 180px, 1fr ) ); gap: 8px; } .os-my-wordpress__term-author { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 8px; background: var( --os-ui-hover, rgba( 0, 0, 0, 0.03 ) ); border: 1px solid var( --os-ui-border, #dcdcde ); } .os-my-wordpress__term-author-avatar { width: 36px; height: 36px; border-radius: 50%; flex-shrink: 0; } .os-my-wordpress__term-author-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; } .os-my-wordpress__term-author-name { font-weight: 600; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .os-my-wordpress__term-author-count { font-size: 11px; color: var( --os-ui-fg-muted, #50575e ); } /* --------------------------------------------------------------- * Comment dossier — header (avatar + name + status / date / count * badges + author archive / website / moderate links), optional * "in reply to" quote, the comment body, parent-post card, * direct-reply thread, and a moderation strip (IP + user agent) * for users with moderate_comments. * --------------------------------------------------------------- */ .os-my-wordpress__comment-status--approved { background: rgba( 34, 113, 177, 0.12 ); color: var( --os-ui-accent-strong, #135e96 ); } .os-my-wordpress__comment-status--pending { background: rgba( 217, 119, 6, 0.16 ); color: #b45309; } .os-my-wordpress__comment-status--spam { background: rgba( 179, 45, 46, 0.14 ); color: var( --os-ui-danger-hover, #b32d2e ); } .os-my-wordpress__comment-status--trash { background: var( --os-ui-hover, rgba( 0, 0, 0, 0.08 ) ); color: var( --os-ui-fg-muted, #50575e ); } .os-my-wordpress__comment-date-badge { background: transparent; color: var( --os-ui-fg-muted, #50575e ); border: 1px solid var( --os-ui-border, #dcdcde ); text-transform: none; letter-spacing: normal; } /* "In reply to ..." quote shown above the comment body when this * is a thread reply. */ .os-my-wordpress__comment-quote { margin: 0 0 16px; padding: 10px 14px; border-left: 3px solid var( --wp-admin-theme-color, #2271b1 ); background: rgba( 34, 113, 177, 0.06 ); border-radius: 0 6px 6px 0; font-size: 13px; } .os-my-wordpress__comment-quote-lead { font-size: 11px; font-weight: 600; color: var( --os-ui-fg-muted, #50575e ); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px; } .os-my-wordpress__comment-quote p { margin: 0; color: var( --os-fg, #1d2327 ); } /* Comment body — same style as article-content but with a soft * card background so it reads as the focal element. */ .os-my-wordpress__comment-body { padding: 14px 16px; background: var( --os-ui-hover, rgba( 0, 0, 0, 0.03 ) ); border-radius: 8px; border: 1px solid var( --os-ui-border, #dcdcde ); } /* Parent-post card under the comment body. */ .os-my-wordpress__comment-post { display: flex; flex-direction: column; gap: 4px; padding: 10px 14px; border-radius: 8px; background: var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) ); border: 1px solid var( --os-ui-border, #dcdcde ); } .os-my-wordpress__comment-post-title { font-weight: 600; color: var( --os-link, var( --os-ui-accent, #2271b1 ) ); text-decoration: none; } .os-my-wordpress__comment-post-title:hover { text-decoration: underline; } .os-my-wordpress__comment-post-meta { font-size: 11px; color: var( --os-ui-fg-muted, #787c82 ); } /* Replies list. */ .os-my-wordpress__comment-replies { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; } .os-my-wordpress__comment-reply { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border-radius: 8px; background: var( --os-ui-hover, rgba( 0, 0, 0, 0.02 ) ); border: 1px solid var( --os-ui-border, #dcdcde ); } .os-my-wordpress__comment-reply-avatar { width: 32px; height: 32px; border-radius: 50%; flex-shrink: 0; } .os-my-wordpress__comment-reply-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; } .os-my-wordpress__comment-reply-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; } .os-my-wordpress__comment-reply-name { font-weight: 600; font-size: 13px; } .os-my-wordpress__comment-reply-when { font-size: 11px; color: var( --os-ui-fg-muted, #787c82 ); } .os-my-wordpress__comment-reply-excerpt { margin: 0; font-size: 13px; color: var( --os-fg, #1d2327 ); line-height: 1.45; } /* =================================================================== * * USERS — list tiles + activity footprint surface. * * The Users folder reuses the same canvas + 96×92 tile grid the * post tiles use; the user-tile content (avatar circle + label + * small sub-line) fits inside the cell. The "View activity * footprint" surface is a full-width body view (no split pane) * with hero / stats / heatmap / rhythm / timeline. * * Since 0.20.0. * =================================================================== */ /* User tile — replaces the dashicon glyph with a real avatar */ .os-my-wordpress__tile--user { display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 4px; padding: 8px 4px 6px; box-sizing: border-box; } .os-my-wordpress__user-tile-avatar { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; border-radius: 50%; overflow: hidden; background: linear-gradient( 135deg, rgba( 34, 113, 177, 0.18 ), rgba( 34, 113, 177, 0.04 ) ); box-shadow: inset 0 0 0 1px rgba( 0, 0, 0, 0.08 ), 0 2px 6px rgba( 0, 0, 0, 0.08 ); flex: 0 0 auto; } .os-my-wordpress__user-tile-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; } .os-my-wordpress__user-tile-initials { font-weight: 600; font-size: 14px; letter-spacing: 0.02em; color: var( --wp-admin-theme-color, #2271b1 ); text-transform: uppercase; } /* Single-line label for user tiles — we already have a generic * label clamp, but the user variant gets only ONE line so the * sub-line ("Editor · 12 posts") can fit beneath it. */ .os-my-wordpress__tile--user .os-file-tile__label { font-size: 11px; font-weight: 500; line-height: 1.25; max-height: 1.25em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 100%; padding: 0 4px; box-sizing: border-box; } .os-my-wordpress__user-tile-sub { font-size: 10px; line-height: 1.2; color: var( --os-tile-fg-muted, rgba( 0, 0, 0, 0.55 ) ); max-height: 1.2em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 100%; text-align: center; padding: 0 4px; box-sizing: border-box; } /* Selected/hover user tiles inherit the same chrome the post tiles * use — see `.os-my-wordpress__tile--selected` above. */ /* ----- Activity footprint — full-width body surface ----- * * Opens via the right-click context menu on a user tile (or the * dossier's "View activity footprint" button). Replaces the split * list/preview with a wide canvas so each section gets enough room * to read. */ .os-my-wordpress__footprint { display: flex; flex-direction: column; gap: 20px; padding: 24px 28px 32px; box-sizing: border-box; overflow-y: auto; height: 100%; /* Scroll container spans the full window width so wheel + touch * scroll work no matter where the pointer is — including in the * empty margins to the left / right of the content. The 1080px * cap is applied to each immediate child instead (below). */ align-items: center; } .os-my-wordpress__footprint > * { width: 100%; max-width: 1080px; } .os-my-wordpress__footprint-section { border: 1px solid var( --os-ui-border, #dcdcde ); border-radius: 12px; padding: 18px 20px; background: var( --os-my-wordpress-surface, var( --os-surface, var( --os-ui-surface, rgba( 255, 255, 255, 0.6 ) ) ) ); box-shadow: 0 1px 2px rgba( 0, 0, 0, 0.04 ); /* The parent `.__footprint` is a flex column with `overflow-y: * auto`. Flex items default to `flex-shrink: 1`, so each section * would compress along the column axis when the combined natural * height exceeds the container — clipping the calendar heatmap * (the calendar section also sets `overflow-x: auto`, which * doesn't surface a vertical scrollbar). Lock the intrinsic * height so the parent scroller, not the children, handles the * overflow. */ flex: 0 0 auto; } .os-my-wordpress__footprint-section h3 { margin: 0 0 12px; font-size: 13px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var( --os-ui-fg-muted, #50575e ); } /* ----- Hero ----- */ .os-my-wordpress__footprint-hero { display: flex; gap: 20px; align-items: center; padding: 24px; border-radius: 16px; background: linear-gradient( 135deg, rgba( 34, 113, 177, 0.10 ) 0%, rgba( 34, 113, 177, 0.02 ) 60%, transparent 100% ); border: 1px solid var( --os-ui-border, #dcdcde ); } .os-my-wordpress__footprint-avatar { width: 112px; height: 112px; border-radius: 50%; overflow: hidden; flex: 0 0 auto; box-shadow: inset 0 0 0 2px rgba( 255, 255, 255, 0.8 ), 0 6px 24px rgba( 0, 0, 0, 0.14 ); background: linear-gradient( 135deg, rgba( 34, 113, 177, 0.18 ), rgba( 34, 113, 177, 0.04 ) ); display: inline-flex; align-items: center; justify-content: center; } .os-my-wordpress__footprint-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; } .os-my-wordpress__footprint-avatar .os-my-wordpress__user-tile-initials { font-size: 36px; } .os-my-wordpress__footprint-headline { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 8px; } .os-my-wordpress__footprint-title { margin: 0; font-size: 28px; line-height: 1.15; font-weight: 700; color: var( --os-fg, #1d2327 ); } .os-my-wordpress__footprint-meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; } .os-my-wordpress__footprint-since { background: var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ); color: var( --os-ui-fg-muted, #50575e ); } /* ----- Headline stats row (4 cards) ----- */ .os-my-wordpress__footprint-stats-row { display: grid; grid-template-columns: repeat( auto-fit, minmax( 160px, 1fr ) ); gap: 12px; padding: 16px 18px; } .os-my-wordpress__footprint-stats-row .os-my-wordpress__user-stat { background: var( --os-surface, var( --os-ui-surface, rgba( 255, 255, 255, 0.9 ) ) ); box-shadow: 0 1px 4px rgba( 0, 0, 0, 0.04 ); } /* ----- Calendar heatmap (GitHub-style) ----- */ .os-my-wordpress__footprint-calendar-section { overflow-x: auto; } .os-my-wordpress__footprint-calendar { display: flex; flex-direction: column; gap: 8px; /* Size to the grid itself so the legend (justify-content: flex-end * inside this same column) anchors to the grid's right edge instead * of the section's. Centered within the wider section by * `margin-inline: auto`. */ width: max-content; max-width: 100%; margin-inline: auto; } .os-my-wordpress__footprint-grid { display: grid; /* Row 1 holds month labels (auto height); rows 2–8 are the 7 * weekdays at fixed 11px. Col 1 holds weekday labels (auto width); * subsequent cols are 11px-wide week tracks, set per-cell via * `grid-column` so the JS can place items explicitly. */ grid-template-rows: auto repeat( 7, 11px ); grid-template-columns: auto; grid-auto-columns: 11px; column-gap: 3px; row-gap: 3px; width: max-content; } .os-my-wordpress__footprint-month, .os-my-wordpress__footprint-weekday { font-size: 10px; line-height: 11px; color: var( --os-ui-fg-muted, #50575e ); white-space: nowrap; } .os-my-wordpress__footprint-weekday { /* Sit flush to the right of the label gutter so each label reads * adjacent to its row. The 6px breathing room is in addition to * the grid's column-gap. */ padding-inline-end: 6px; align-self: center; justify-self: end; } .os-my-wordpress__footprint-month { padding-bottom: 2px; } .os-my-wordpress__footprint-cell { width: 11px; height: 11px; border-radius: 2px; background: var( --os-ui-border, #ebedf0 ); display: inline-block; transition: transform 0.12s ease; } .os-my-wordpress__footprint-cell--pad { background: transparent; } .os-my-wordpress__footprint-cell--l0 { background: var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ); } .os-my-wordpress__footprint-cell--l1 { background: rgba( 34, 113, 177, 0.25 ); } .os-my-wordpress__footprint-cell--l2 { background: rgba( 34, 113, 177, 0.5 ); } .os-my-wordpress__footprint-cell--l3 { background: rgba( 34, 113, 177, 0.75 ); } .os-my-wordpress__footprint-cell--l4 { background: var( --wp-admin-theme-color, #2271b1 ); } .os-my-wordpress__footprint-cell:hover { transform: scale( 1.4 ); z-index: 1; } .os-my-wordpress__footprint-legend { display: flex; gap: 4px; align-items: center; justify-content: flex-end; font-size: 11px; color: var( --os-ui-fg-muted, #50575e ); } .os-my-wordpress__footprint-legend-label { font-size: 11px; color: var( --os-ui-fg-muted, #50575e ); padding: 0 4px; } /* ----- Rhythm (weekday + hour bar charts) ----- */ .os-my-wordpress__footprint-rhythm-grid { display: grid; grid-template-columns: minmax( 0, 1fr ) minmax( 0, 1.7fr ); gap: 24px; } @container ( max-width: 720px ) { .os-my-wordpress__footprint-rhythm-grid { grid-template-columns: 1fr; } } .os-my-wordpress__footprint-chart-caption { font-size: 12px; color: var( --os-ui-fg-muted, #50575e ); margin-bottom: 6px; } .os-my-wordpress__footprint-bars { display: flex; align-items: end; gap: 4px; height: 100px; padding-bottom: 18px; position: relative; } .os-my-wordpress__footprint-bar-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: stretch; justify-content: flex-end; gap: 4px; height: 100%; position: relative; } .os-my-wordpress__footprint-bar { background: linear-gradient( 180deg, var( --wp-admin-theme-color, #2271b1 ) 0%, rgba( 34, 113, 177, 0.55 ) 100% ); border-radius: 3px 3px 0 0; min-height: 2px; transition: opacity 0.2s ease; } .os-my-wordpress__footprint-bar--empty { background: var( --os-ui-border, #dcdcde ); height: 2px !important; min-height: 2px; } .os-my-wordpress__footprint-bar-label { position: absolute; bottom: -16px; left: 0; right: 0; text-align: center; font-size: 10px; color: var( --os-ui-fg-muted, #787c82 ); } /* ----- Most-prolific-month callout ----- */ .os-my-wordpress__footprint-callout { display: flex; flex-direction: column; gap: 4px; align-items: center; text-align: center; padding: 20px 24px; background: linear-gradient( 135deg, rgba( 34, 113, 177, 0.10 ), rgba( 34, 113, 177, 0.02 ) ); } .os-my-wordpress__footprint-callout-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var( --os-ui-fg-muted, #50575e ); font-weight: 600; } .os-my-wordpress__footprint-callout-value { margin: 2px 0; font-size: 24px; font-weight: 700; color: var( --os-fg, #1d2327 ); } .os-my-wordpress__footprint-callout-detail { margin: 0; font-size: 13px; color: var( --os-ui-fg-muted, #50575e ); max-width: 480px; } /* ----- Recent-activity timeline ----- */ .os-my-wordpress__footprint-timeline { list-style: none; margin: 0; padding: 0; position: relative; display: flex; flex-direction: column; gap: 12px; } .os-my-wordpress__footprint-timeline::before { content: ''; position: absolute; inset-inline-start: 13px; top: 4px; bottom: 4px; width: 2px; background: var( --os-ui-border, #dcdcde ); border-radius: 1px; } .os-my-wordpress__footprint-event { display: flex; gap: 12px; align-items: flex-start; position: relative; padding-inline-start: 36px; min-height: 28px; } .os-my-wordpress__footprint-dot { position: absolute; inset-inline-start: 0; top: 0; width: 28px; height: 28px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; background: var( --os-surface, var( --os-ui-surface, #fff ) ); border: 2px solid var( --wp-admin-theme-color, #2271b1 ); color: var( --wp-admin-theme-color, #2271b1 ); } .os-my-wordpress__footprint-event--comment .os-my-wordpress__footprint-dot { border-color: var( --os-ui-fg-muted, #787c82 ); color: var( --os-ui-fg-muted, #787c82 ); } .os-my-wordpress__footprint-dot .dashicons { font-size: 14px; width: 14px; height: 14px; line-height: 1; } .os-my-wordpress__footprint-event-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; } .os-my-wordpress__footprint-event-title { font-weight: 600; font-size: 13px; color: var( --os-link, var( --os-ui-accent, #2271b1 ) ); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } a.os-my-wordpress__footprint-event-title:hover { text-decoration: underline; } .os-my-wordpress__footprint-event-meta { font-size: 11px; color: var( --os-ui-fg-muted, #787c82 ); } /* ----- Footer actions ----- */ .os-my-wordpress__footprint-footer { display: flex; justify-content: flex-end; align-items: center; gap: 8px; flex-wrap: wrap; background: transparent; border-color: transparent; box-shadow: none; padding: 0; } /* ============================================================ * * Status ribbon — diagonal corner banner for non-published tiles * (since 0.21.0) * * Classic 45° corner ribbon — same affordance as a stamp on a * document. Applied to every tile whose post status isn't * `publish` across every My WordPress section (Posts, Pages, * Media drill-in, plugin kinds). * * Implementation: * 1. Tiles with a status modifier get `overflow: hidden` so the * ribbon's overshoot clips to the tile bounds. * 2. Ribbon is a wide strip (`width: 110px`) anchored to the * top-right corner, rotated +45° so it forms a diagonal * banner across that corner. The strip extends past the * tile edges; `overflow: hidden` on the parent crops it * into a clean triangle. * * The status tiles need a positioning context. `.os- * file-tile` is `position: absolute` (canvas layout) on the * Posts/Pages browse views — that satisfies the positioning- * context requirement. For the Media drill-in usage tiles we * swap from `static` to `relative` via the per-status rule * below so the ribbon stays anchored to its own tile. * ============================================================ */ /* Status ribbon — the diagonal corner banner — is now drawn by * the `` web component slotted inside ``. * The component's own shadow-DOM styles own placement, rotation, * background, lettering, and the WP admin-theme tint. The only * surface-specific rule left here is dimming the icon on non- * published tiles so the unpublished signal carries even when * the ribbon is hidden via the OS-settings toggle. */ .os-my-wordpress__tile--draft, .os-my-wordpress__tile--pending, .os-my-wordpress__tile--private, .os-my-wordpress__tile--future { overflow: hidden; } os-tile[ status='draft' ] .os-file-tile__icon, os-tile[ status='pending' ] .os-file-tile__icon, os-tile[ status='private' ] .os-file-tile__icon, os-tile[ status='future' ] .os-file-tile__icon { opacity: 0.7; } /* ============================================================ * * Media section (since 0.21.0) * ============================================================ */ .os-my-wordpress__media-grid { display: grid; grid-template-columns: repeat( auto-fill, minmax( 120px, 1fr ) ); /* A flow grid rather than the absolute canvas, but the air * between two icons should not depend on which window you are * looking at — so the gaps and the gutter come from the same * tokens the canvas pitch is built from. */ gap: var( --os-grid-gap-y, 16px ) var( --os-grid-gap-x, 20px ); padding: var( --os-grid-padding, 16px ); overflow-y: auto; align-content: start; } .os-my-wordpress__media-tile { /* Override the base `.os-file-tile` (position: absolute, * width: 88px) so media tiles flow inside the CSS Grid instead of * stacking at (0,0) like the absolute-positioned post/user tiles. * * `relative` (not `static`) so the tile remains a containing * block for absolutely-positioned descendants — the `::after` * shortcut-arrow badge on attachment tiles, and the * `` post-status corner ribbon — both of which * would otherwise escape to the nearest positioned ancestor * (the window, the desktop, the viewport) and render way off * the tile. */ position: relative; width: auto; /* …and out of the fixed tile height for the same reason: a media * tile is a square thumbnail plus a caption, sized by the grid * column it lands in, not by the icon-canvas cell. */ height: auto; display: flex; flex-direction: column; align-items: stretch; gap: 6px; padding: 8px; background: transparent; border: 1px solid transparent; border-radius: 8px; cursor: pointer; text-align: center; min-width: 0; } .os-my-wordpress__media-tile:hover { background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) ) ); } /* Media tiles inherit the canonical `.os-file-tile--selected` * rule from `desktop-files.css` — accent inset border + tinted * background — same as every other tile in the shell. */ /* Override the canonical 48×48 visual/preview sizing — media tiles * want the visual to fill the full grid-cell width as a square * thumbnail (or icon-fallback square). Photos browser look, not * folder-icon look. Applies to BOTH the `` (real thumbnail) * and the dashicon fallback (non-image MIME), so the two tile * shapes are visually consistent in the grid. */ .os-my-wordpress__media-tile .os-file-tile__visual { display: flex; align-items: center; justify-content: center; width: 100%; height: auto; aspect-ratio: 1 / 1; overflow: hidden; border-radius: 6px; background: var( --os-media-tile-bg, rgba( 0, 0, 0, 0.05 ) ); /* Reset the base 32px font + 48px size since we're sizing the * inner img/icon explicitly below. */ font-size: 0; line-height: 1; } .os-my-wordpress__media-tile .os-file-tile__preview { width: 100%; height: 100%; object-fit: cover; border-radius: 0; } .os-my-wordpress__media-tile .os-file-tile__icon { /* Centered dashicon fallback — proportional to the larger * thumbnail well so it doesn't look like a postage stamp. */ width: auto; height: auto; font-size: 36px; color: var( --os-ui-fg-muted, #787c82 ); } .os-my-wordpress__media-tile .os-file-tile__label { font-size: 12px; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } /* ----- Media preview pane ----- */ .os-my-wordpress__media-pane { display: flex; flex-direction: column; gap: 16px; padding: 16px; } .os-my-wordpress__media-title { font-size: 16px; font-weight: 600; margin: 0; } .os-my-wordpress__media-visual { display: flex; align-items: center; justify-content: center; background: var( --os-media-visual-bg, var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) ) ); border-radius: 8px; min-height: 200px; max-height: 480px; overflow: hidden; } .os-my-wordpress__media-image, .os-my-wordpress__media-video { max-width: 100%; max-height: 480px; object-fit: contain; } .os-my-wordpress__media-fallback-icon { font-size: 96px; width: 96px; height: 96px; color: var( --os-ui-fg-muted, #787c82 ); } .os-my-wordpress__media-doc-link { margin-inline-start: 16px; align-self: center; } .os-my-wordpress__media-audio-stack { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 24px; } .os-my-wordpress__media-meta { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; font-size: 13px; margin: 0; } .os-my-wordpress__media-meta-term { color: var( --os-ui-fg-muted, #787c82 ); margin: 0; } .os-my-wordpress__media-meta-value { color: var( --os-ui-fg, #1d2327 ); word-break: break-word; margin: 0; } .os-my-wordpress__media-actions { display: flex; flex-wrap: wrap; gap: 8px; padding-top: 8px; border-top: 1px solid var( --os-ui-border, #dcdcde ); } /* ----- Media drill-in ----- */ /* Drill-in: referencing-posts list. Visually identical to the * Posts / Pages tiles — same `os-file-tile` chrome (48px * dashicon + 88px-wide two-line label). We can't reuse the * absolute-positioning canvas layout from `renderEntityList` here * (those internals don't export), so this surface uses a flex-wrap * of fixed-width tiles to reproduce the look. * * NOTE: this rule INTENTIONALLY overrides the `__media-grid` * defaults (which target square thumbnails on 130px columns) so * usage tiles match the canvas-flowed post tiles instead. */ .os-my-wordpress__usage-grid { display: flex; flex-wrap: wrap; gap: var( --os-grid-gap-y, 16px ) var( --os-grid-gap-x, 20px ); padding: var( --os-grid-padding, 16px ); align-content: flex-start; } /* Reset the `__media-tile` overrides for usage tiles so they * inherit the unmodified `.os-file-tile` chrome the * canvas-positioned post tiles use — except for `position: static` * so they participate in the flex layout. */ .os-my-wordpress__tile--usage { width: var( --os-tile-w, 88px ); align-items: center; padding: 8px 4px; gap: 6px; } /* Right-pane summary bar sits ABOVE the inline media preview. */ .os-my-wordpress__media-detail-summary-bar { padding: 12px 16px; border-bottom: 1px solid var( --os-ui-border, #dcdcde ); } .os-my-wordpress__media-detail-summary { margin: 0; color: var( --os-ui-fg-muted, #787c82 ); font-size: 13px; } .os-my-wordpress__media-detail-preview { /* Lets the embedded `__media-pane` keep its own padding without * fighting the summary bar's spacing. */ display: block; } /* ------------------------------------------------------------------ */ /* Agents section (`agent` entity kind — src/my-wordpress/agents-renderer.ts) */ /* ------------------------------------------------------------------ */ .dm-agents { display: flex; flex-direction: column; height: 100%; min-height: 0; padding: 12px; gap: 10px; overflow: hidden; box-sizing: border-box; } /* Section-level loading — same scale curve as the My WordPress preview loader and the window-loading overlay, instead of the bare 48px component default. */ .dm-agents__loading { display: flex; align-items: center; justify-content: center; height: 100%; min-height: 320px; } .dm-agents__loading os-spinner { --os-ui-spinner-size: clamp( 96px, 14vw, 192px ); } .dm-agents__detail { flex: 1; min-width: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; } /* `` owns the icon + copy and centres them within itself; the host is still a flex item that shrinks to its content, so without this it sits at the top of a pane that is mostly empty space. Auto margins on both axes absorb the slack — placement only, same as the comments window. Covers the framework-off state and "No agents yet" alike. */ .dm-agents__detail > os-empty-state { margin: auto; } .dm-agents__detail-head { display: flex; align-items: center; gap: 10px; } .dm-agents__detail-avatar { width: 48px; height: 48px; border-radius: 50%; /* Placeholder behind the avatar image while it loads — follows the palette so it isn't a white disc on a dark station. */ background: var( --os-ui-surface-elevated, #fff ); flex: none; } .dm-agents__detail-title { flex: 1; min-width: 0; } .dm-agents__detail-title h3 { margin: 0; font-size: 16px; } .dm-agents__detail-slug { font-size: 12px; opacity: 0.6; } /* Verbs get their own row under the identity block: the set grows (profile, contributions, desktop, chat, delete) and squeezing them beside the name truncated it first. */ .dm-agents__detail-actions { display: flex; flex-wrap: wrap; gap: 8px; } .dm-agents__tabs { display: flex; gap: 4px; border-bottom: 1px solid var( --os-border, rgba( 0, 0, 0, 0.08 ) ); } .dm-agents__tab { appearance: none; background: none; border: none; border-bottom: 2px solid transparent; padding: 6px 10px; font: inherit; color: inherit; cursor: pointer; opacity: 0.7; } .dm-agents__tab.is-active { border-bottom-color: var( --os-accent, #2271b1 ); opacity: 1; font-weight: 600; } .dm-agents__pane { display: flex; flex-direction: column; gap: 10px; padding-bottom: 12px; } /* Pane-level loading: same big, centered WordPress mark the section loader uses, instead of a bare 48px spinner pinned to the top-left of the pane. */ .dm-agents__pane--loading { align-items: center; justify-content: center; flex: 1; min-height: 260px; } .dm-agents__pane--loading os-spinner { --os-ui-spinner-size: clamp( 96px, 12vw, 160px ); } .dm-agents__hint { margin: 0; font-size: 12px; opacity: 0.7; } /* Role first: least privilege is the decision, so the control holds the mockup's width instead of stretching into a landing strip. */ .dm-agents__role-select { max-width: 280px; } /* The ability filter. Sized like the role select rather than the full pane: it sits above a list of rules, and a search box as wide as the rules reads as another one of them. */ .dm-agents__ability-search { display: block; max-width: 320px; margin: 12px 0 4px; } /* Each category is a native disclosure. `
` because the behaviour is exactly what it is for: keyboard, screen-reader semantics and the open/closed state all arrive for free, and the only thing left to write is the costume. */ .dm-agents__ability-group { border-bottom: 1px solid var( --os-border, rgba( 0, 0, 0, 0.08 ) ); } .dm-agents__ability-group:last-of-type { border-bottom: 0; } .dm-agents__category { margin: 18px 0 2px; font-family: var( --os-ui-font-mono, ui-monospace, monospace ); font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.08em; color: var( --os-ui-fg-muted, #646970 ); } /* A `` is a button, so it gets a button's affordances: a pointer, a hover, and a focus ring that is visible on the dark surface the section is drawn on. */ summary.dm-agents__category { display: flex; align-items: center; gap: 8px; margin: 0; padding: 10px 0; cursor: pointer; list-style: none; user-select: none; } summary.dm-agents__category::-webkit-details-marker { display: none; } /* The marker is drawn here rather than left to the UA so it can be the same triangle in every browser and turn with the disclosure. Rotation only: a transform, so opening a group of forty rows costs no layout. */ summary.dm-agents__category::before { content: ''; flex: none; width: 0; height: 0; border-inline-start: 5px solid currentcolor; border-block: 4px solid transparent; transition: transform 120ms ease; } .dm-agents__ability-group[ open ] > summary.dm-agents__category::before { transform: rotate( 90deg ); } @media ( prefers-reduced-motion: reduce ) { summary.dm-agents__category::before { transition: none; } } summary.dm-agents__category:hover { color: var( --os-ui-fg, #1e1e1e ); } summary.dm-agents__category:focus-visible { outline: 2px solid var( --os-ui-accent, #3858e9 ); outline-offset: 2px; border-radius: 4px; } .dm-agents__category-name { flex: 1 1 auto; } /* "3 of 12" when some are ticked, otherwise just the size of the group. The count is why a closed group is still informative: collapsing must not hide the fact that something in there is switched on. */ .dm-agents__category-count { flex: none; font-variant-numeric: tabular-nums; opacity: 0.75; } /* One ability per row, ruled like the mockup: tick and name on the left, badge on the right, the description indented under the name (the checkbox column is the control's own 26px). */ .dm-agents__ability { display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; align-items: start; padding: 11px 0; border-bottom: 1px solid var( --os-border, rgba( 0, 0, 0, 0.08 ) ); } .dm-agents__ability:last-of-type { border-bottom: 0; } .dm-agents__ability-desc { grid-column: 1 / -1; margin: 0; padding-inline-start: 26px; font-size: 12.5px; color: var( --os-ui-fg-muted, #646970 ); } /* Access badges in the instrument voice. The palette's own warning tokens carry the pale-yellow "can modify"; all this changes is the costume: mono, uppercase, an outline instead of a wash. */ .dm-agents__ability os-badge { --os-ui-badge-font: 500 10px/1.6 var( --os-ui-font-mono, ui-monospace, monospace ); --os-ui-badge-dot-size: 0; --os-ui-badge-gap: 0; --os-ui-badge-padding: 3px 8px; --os-ui-badge-bg: transparent; --os-ui-badge-border: 1px solid var( --os-border, rgba( 0, 0, 0, 0.12 ) ); text-transform: uppercase; letter-spacing: 0.06em; white-space: nowrap; margin-top: 2px; } .dm-agents__ability os-badge[ tone='warning' ] { --os-ui-badge-border: 1px solid var( --os-ui-warning-border, rgba( 154, 103, 0, 0.4 ) ); } .dm-agents__trigger { border: 1px solid var( --os-border, rgba( 0, 0, 0, 0.08 ) ); border-radius: 8px; padding: 8px 10px; display: flex; flex-direction: column; gap: 6px; } .dm-agents__trigger-head { display: flex; align-items: center; gap: 8px; } .dm-agents__trigger-desc { margin: 0; font-size: 12px; opacity: 0.75; } .dm-agents__trigger-config { display: flex; flex-wrap: wrap; gap: 4px 14px; } .dm-agents__actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-block-start: 12px; } /* --------------------------------------------------------------------- The cast ------------------------------------------------------------------ */ /* One view at a time: the grid, an agent, or the wizard. A sidebar of faces next to a detail form would leave neither enough room, and the whole point of the grid is that the faces are big enough to tell apart. */ .dm-agents__view { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: auto; gap: 12px; } .dm-agents__cast-head { display: flex; align-items: baseline; gap: 8px; } .dm-agents__cast-head h3 { margin: 0; } .dm-agents__cast-count { font-family: var( --os-ui-font-mono, ui-monospace, monospace ); font-size: 12px; color: var( --os-ui-fg-muted, #646970 ); } .dm-agents__cast { display: grid; gap: 16px; grid-template-columns: repeat( auto-fill, minmax( 288px, 1fr ) ); align-content: start; } /* The card reacts like the mockup's: a lift and a brighter edge. Transform and border-color only — both cheap, and the same pair the mockup transitions. Document styles win over the component's own :host rules, which is the sanctioned way to restyle a host. */ .dm-agents__cast-card, .dm-agents__cast-new { --os-ui-card-padding: 20px 18px 16px; transition: border-color 0.18s ease, transform 0.18s ease; } .dm-agents__cast-card:hover, .dm-agents__cast-card:focus-within { border-color: var( --os-ui-border-strong, #8c8f94 ); transform: translateY( -2px ); } /* The dashed door, same as the mockup's crew-new card. */ .dm-agents__cast-new { border-style: dashed; --os-ui-card-bg: var( --os-ui-surface-subtle, transparent ); } /* The crew breathes. Transform only, staggered so five faces do not bob in lockstep, and gone entirely under reduced motion. */ @media ( prefers-reduced-motion: no-preference ) { .dm-agents__cast-card .dm-agents__cast-face { animation: dm-agents-bob 6s ease-in-out infinite; } .dm-agents__cast-card:nth-child( 2n ) .dm-agents__cast-face { animation-duration: 7.2s; animation-delay: -1.4s; } .dm-agents__cast-card:nth-child( 3n ) .dm-agents__cast-face { animation-duration: 6.6s; animation-delay: -2.8s; } } @keyframes dm-agents-bob { 0%, 100% { transform: translateY( 0 ); } 50% { transform: translateY( -4px ); } } /* The column lives on a wrapper we own, not on the host: the slotted children sit inside the card's own container, so a flex rule on the host never reaches them and the role badges land wherever the description's line count leaves them. */ .dm-agents__cast-inner { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 2px; height: 100%; } /* The faces are the loud part of this surface, and deliberately so: agent identity is the one place the station spends the mesh budget on something other than a hero CTA. A cast has to be legible at a glance, which is exactly what a flat accent cannot do. */ .dm-agents__cast-face { width: 118px; height: 118px; margin: -10px 0 6px; flex: none; } .dm-agents__cast-name { font-size: 17px; font-weight: 600; color: var( --os-ui-fg, #1d2327 ); } /* The voice line, in the instrument register: this is metadata about the agent, not prose from it. */ .dm-agents__cast-vibes { font-family: var( --os-ui-font-mono, ui-monospace, monospace ); font-size: 11.5px; color: var( --os-ui-accent-strong, #2271b1 ); } .dm-agents__cast-good { font-size: 13px; color: var( --os-ui-fg-muted, #646970 ); margin-block: 4px 14px; flex: 1; } /* The role pill in the instrument voice: mono, uppercase, no status dot — this is a fact about the agent, not a state to monitor. The custom properties are the component's own theming hooks; the inherited text properties flow into its shadow label. */ .dm-agents__cast-card os-badge { --os-ui-badge-font: 500 11px/1.6 var( --os-ui-font-mono, ui-monospace, monospace ); --os-ui-badge-dot-size: 0; --os-ui-badge-gap: 0; --os-ui-badge-padding: 3px 10px; text-transform: uppercase; letter-spacing: 0.08em; } /* The framework-off bar: the section's status and its one action, on one line above the crew it would hire. It is deliberately not an `os-empty-state` — that block is centred, icon-led and paragraph- shaped, which is exactly the wall of text the cast replaced, and at five cards tall it would push the button that undoes the situation off the bottom of the window. `flex: none` because `.dm-agents__view` is a scrolling flex column and the bar must not absorb the slack. */ .dm-agents__off-head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; flex: none; gap: 8px 16px; padding: 12px 14px; border-radius: 10px; background: var( --os-ui-surface-elevated, #fff ); border: 1px solid var( --os-border, rgba( 0, 0, 0, 0.08 ) ); } .dm-agents__off-copy { min-width: 0; } .dm-agents__off-copy h3 { margin: 0; } .dm-agents__off-copy p { margin: 2px 0 0; font-size: 13px; color: var( --os-ui-fg-muted, #646970 ); } /* The cast the site would get, drawn while the framework is off. Dimmed as a whole rather than desaturated: the five hues ARE the argument this state is making, and a greyscale filter would flatten the crew into five identical blobs on the way to saying "not yet". Opacity says inactive and leaves the faces telling each other apart. `pointer-events` is belt and braces — the cards are rendered without `interactive` and without a click handler, so there is nothing to press. It exists so a hover state cannot promise otherwise. */ .dm-agents__cast--preview { opacity: 0.6; pointer-events: none; } .dm-agents__cast-new .dm-agents__cast-plus { display: grid; place-items: center; width: 118px; height: 118px; margin: -10px 0 6px; border-radius: 50%; border: 1.5px dashed var( --os-ui-border-strong, #8c8f94 ); font-size: 30px; font-weight: 300; line-height: 1; color: var( --os-ui-fg-muted, #646970 ); } /* --------------------------------------------------------------------- The wizard ------------------------------------------------------------------ */ .dm-agents__wizard { display: flex; flex-direction: column; gap: 10px; } .dm-agents__wiz-head { display: flex; align-items: center; justify-content: space-between; gap: 8px 12px; flex-wrap: wrap; } .dm-agents__wiz-head h3 { margin: 0; } /* The trail wears the instrument voice: mono uppercase labels, 22px dots, a thin rule between stations. All of it goes through the component's own theming hooks; the host elements live in this light DOM, so the per-state overrides below can select them directly. */ .dm-agents__trail { margin-block: 6px 14px; --os-ui-steps-gap: 10px; --os-ui-step-gap: 7px; --os-ui-step-chip-size: 22px; --os-ui-step-chip-font-size: 11px; --os-ui-step-chip-family: var( --os-ui-font-mono, ui-monospace, monospace ); --os-ui-step-title-size: 11px; --os-ui-step-title-family: var( --os-ui-font-mono, ui-monospace, monospace ); --os-ui-step-title-transform: uppercase; --os-ui-step-title-spacing: 0.08em; } /* A station you have not reached is an outline, not a filled dot: the mesh chip is where you ARE, and it only reads that way if it is the only one lit. */ .dm-agents__trail os-step:not( [ done ] ):not( [ current ] ) { --os-ui-step-chip-bg: transparent; --os-ui-step-chip-border: 1px solid var( --os-ui-border-strong, #8c8f94 ); --os-ui-step-chip-fg: var( --os-ui-fg-muted, #646970 ); } /* Done is quiet: a filled tick on the elevated surface, muted like its label. It reads as history, not as a second highlight. */ .dm-agents__trail os-step[ done ] { --os-ui-step-chip-done-bg: var( --os-ui-surface-elevated, #f0f0f1 ); --os-ui-step-chip-fg: var( --os-ui-fg-muted, #646970 ); } .dm-agents__wiz-heading { margin: 12px 0 2px; font-size: 16px; font-weight: 600; color: var( --os-ui-fg, #1d2327 ); } /* The door. Five complete agents already ship with the plugin, and until now the create flow showed them to nobody. */ .dm-agents__starters { display: grid; gap: 12px; grid-template-columns: repeat( auto-fill, minmax( 148px, 1fr ) ); margin-block: 8px 16px; } .dm-agents__starter { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 3px; --os-ui-card-padding: 16px 14px; --os-ui-card-radius: 10px; --os-ui-card-bg: var( --os-ui-surface-subtle, transparent ); transition: border-color 0.18s ease; } .dm-agents__starter:hover, .dm-agents__starter:focus-within { border-color: var( --os-ui-border-strong, #8c8f94 ); } .dm-agents__starter-face { width: 76px; height: 76px; margin: -8px 0 4px; flex: none; } .dm-agents__starter-name { font-size: 14px; font-weight: 500; color: var( --os-ui-fg, #1d2327 ); } .dm-agents__starter-vibes { font-family: var( --os-ui-font-mono, ui-monospace, monospace ); font-size: 11.5px; color: var( --os-ui-fg-muted, #646970 ); } /* Meet: the portrait beside the fields, so the thing being named is visible while it is being named. */ .dm-agents__meet { display: grid; gap: 20px; grid-template-columns: 220px 1fr; align-items: start; } /* The character card. Sunken rather than subtle: the portrait is the hero of this step, and the mockup floats it on the darkest surface so the face's own glow does the framing. */ .dm-agents__portrait { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 20px 14px 14px; border: 1px solid var( --os-border, rgba( 0, 0, 0, 0.08 ) ); border-radius: var( --os-ui-card-radius, 10px ); background: var( --os-ui-surface-sunken, #f6f7f7 ); } .dm-agents__portrait-face { width: 176px; height: 176px; max-width: 100%; margin-top: -12px; } /* What this face is, in two words. */ .dm-agents__portrait-chips { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; } .dm-agents__faces { display: grid; grid-template-columns: repeat( 6, 1fr ); gap: 2px; width: 100%; } .dm-agents__face-pick { display: grid; place-items: center; padding: 2px; border: 1px solid transparent; border-radius: 8px; background: none; cursor: pointer; } .dm-agents__face-pick img { width: 100%; height: auto; display: block; } .dm-agents__face-pick:hover { border-color: var( --os-ui-border-strong, #8c8f94 ); } .dm-agents__face-pick.is-picked { border-color: var( --os-ui-accent, #2271b1 ); } .dm-agents__face-pick:focus-visible { outline: var( --os-ui-focus-ring, 2px solid #2271b1 ); outline-offset: 1px; } .dm-agents__meet-fields { display: flex; flex-direction: column; gap: 10px; min-width: 0; } /* Launch: one card, the brand's card radius, floating on the darkest surface like the portrait it echoes. */ .dm-agents__summary { display: grid; grid-template-columns: 106px 1fr; gap: 18px; align-items: start; --os-ui-card-padding: 18px; --os-ui-card-radius: 10px; --os-ui-card-bg: var( --os-ui-surface-sunken, #f6f7f7 ); } .dm-agents__summary-face { width: 106px; height: 106px; flex: none; } .dm-agents__summary-text h4 { margin: 0 0 2px; font-size: 17px; } .dm-agents__summary-vibes { font-family: var( --os-ui-font-mono, ui-monospace, monospace ); font-size: 11.5px; color: var( --os-ui-accent-strong, #2271b1 ); margin: 0 0 6px; } .dm-agents__summary-desc { margin: 0; font-size: 13px; color: var( --os-ui-fg-muted, #646970 ); } .dm-agents__chips { display: flex; flex-wrap: wrap; gap: 6px; margin-block-start: 10px; } /* What the agent will actually be told, under the chips. Clamped: a summary card quotes the instructions, it does not host them. */ .dm-agents__summary-instr { margin: 10px 0 0; font-size: 13px; color: var( --os-ui-fg-muted, #646970 ); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } /* Identity chips — role, abilities, silhouette, hue — all wear the instrument voice: these are facts about the character, stamped rather than said. */ .dm-agents__chips os-chip, .dm-agents__portrait-chips os-chip { --os-ui-chip-font-size: 10.5px; --os-ui-chip-font-weight: 500; --os-ui-chip-bg: transparent; --os-ui-chip-fg: var( --os-ui-fg-muted, #646970 ); --os-ui-chip-border: 1px solid var( --os-border, rgba( 0, 0, 0, 0.12 ) ); font-family: var( --os-ui-font-mono, ui-monospace, monospace ); text-transform: uppercase; letter-spacing: 0.06em; } .dm-agents__spacer { flex: 1; } @media ( max-width: 640px ) { .dm-agents__meet, .dm-agents__summary { grid-template-columns: 1fr; } .dm-agents__portrait-face { width: 128px; height: 128px; } } /* The way back to the grid. The window's own breadcrumb goes UP to the folder, which is a different journey: this one returns to the cast. */ .dm-agents__back { align-self: flex-start; margin-block-end: 4px; }