PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.2
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.2
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
desktop-mode / assets / css / chromeless.css

chromeless.css in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.2, at assets/css/chromeless.css

1,069 lines 32.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * OpenStation — Chromeless Overrides.
3 *
4 * CSS adjustments for legacy admin pages rendered inside OpenStation
5 * iframes (chromeless mode). All rules are scoped to .os-chromeless
6 * so they never affect the classic admin or the desktop shell.
7 *
8 * Plugin and theme developers: to add your own chromeless overrides,
9 * enqueue a stylesheet on the 'openstation_chromeless_styles' action.
10 * Your CSS just needs to target .os-chromeless as the body class.
11 *
12 * Example:
13 * add_action( 'openstation_chromeless_styles', function() {
14 * wp_enqueue_style( 'my-plugin-chromeless', plugin_dir_url( __FILE__ ) . 'chromeless.css' );
15 * } );
16 *
17 * @since 0.1.0
18 */
19
20 /* ---------------------------------------------------------------
21 * Hide the classic admin chrome elements. These would otherwise
22 * reserve space (sidebar gutter, footer, admin bar) around the
23 * chromeless page content.
24 * --------------------------------------------------------------- */
25 .os-chromeless #adminmenuwrap,
26 .os-chromeless #adminmenuback,
27 .os-chromeless #wpfooter,
28 .os-chromeless #wpadminbar,
29 .os-chromeless .wp-responsive-toggle,
30 .os-chromeless #collapse-menu {
31 display: none !important;
32 }
33
34 /* ---------------------------------------------------------------
35 * The iframe paints its own canvas.
36 *
37 * An admin page leaves whole regions unpainted — the gutter around
38 * `#wpbody-content`, the strip behind `.subsubsub` and `.tablenav`,
39 * everything below short content. In a normal admin tab the browser
40 * canvas is white underneath, so nobody notices. Inside an iframe the
41 * canvas is TRANSPARENT, and what shows through is the window element
42 * behind it — `--os-window-bg`.
43 *
44 * That was invisible while the window body was white and became a
45 * black band the moment the station's palette made it Obsidian. It is
46 * a real coupling either way: the look of a wp-admin page must not
47 * depend on the colour of the frame around it.
48 *
49 * So the chromeless document paints itself, in the colour the browser
50 * canvas would have been. Deliberately a literal and deliberately NOT
51 * a token: nothing about the shell's palette — or any desktop theme —
52 * should reach a real admin page. **An admin page in a window renders
53 * exactly as it does outside one.**
54 * --------------------------------------------------------------- */
55 body.os-chromeless {
56 background: #fff;
57 }
58
59 /*
60 * _wp_admin_html_begin() adds the `wp-toolbar` class to <html> whenever
61 * is_admin_bar_showing() is true — which, in admin, is unconditional.
62 * The class carries a `padding-top: var(--wp-admin--admin-bar--height)`
63 * that would leave a 32px (or 46px) dead gap at the top of the iframe.
64 * Zero it out inside chromeless iframes.
65 *
66 * We also rebind `--wp-admin--admin-bar--height` (and the derived
67 * `--wp-admin--admin-bar--position-offset` from block-library) to 0px
68 * inside chromeless so plugins that position UI relative to the admin
69 * bar resolve their math against the iframe's actual chrome state.
70 *
71 * WooCommerce's activity-panel wrapper, the block editor's sticky
72 * header, and several others use `top: var(--wp-admin--admin-bar--height)`
73 * to clear the bar. Without this override they reserve a 32px (or 46px
74 * on small screens) gap that no longer exists, producing visible jumps
75 * on first paint and dead space at the top of the content area.
76 */
77 html.wp-toolbar:has( body.os-chromeless ) {
78 padding-top: 0 !important;
79 --wp-admin--admin-bar--height: 0px;
80 --wp-admin--admin-bar--position-offset: 0px;
81 }
82
83 /* Remove the sidebar gutter on #wpcontent and let the body fill the frame. */
84 .os-chromeless #wpcontent {
85 margin-inline-start: 0 !important;
86 padding-inline-start: 0 !important;
87 }
88
89 /*
90 * WooCommerce sidebar-reservation override.
91 *
92 * `.woocommerce-layout__header` is a `position: fixed` bar WC
93 * mounts on every wc-admin and wc-embedded page. Its width is
94 * compiled from the SCSS source (header/style.scss) as a literal:
95 *
96 * position: fixed;
97 * top: 32px; // $adminbar-height
98 * width: calc(100% - 160px); // reserves classic sidebar
99 * z-index: 1001;
100 *
101 * The 160px subtraction is the classic admin menu width — WC bakes
102 * it in at build time so the header doesn't overlap the sidebar
103 * in standard admin. Inside chromeless we hide the sidebar, but
104 * the header keeps the reservation — so the header ends 160px
105 * short of the iframe right edge, and `.woocommerce-layout__activity-panel-wrapper`
106 * (`position: absolute; right: 0; top: 100%; transform: translateX(100%)`,
107 * containing block = the fixed header) translates past the
108 * header's right edge, NOT past the iframe's right edge. The
109 * resulting visible strip is exactly 160px wide — the size of the
110 * sidebar gap WC reserved for nothing.
111 *
112 * Reclaim the reservation: pin the header to full iframe width
113 * inside chromeless. The activity panel then translates past the
114 * iframe edge as WC's design intended, the gray strip disappears,
115 * and the header's content uses the full window width. No
116 * transform / overflow / visibility tricks needed.
117 */
118 .os-chromeless .woocommerce-layout__header {
119 width: 100% !important;
120 }
121
122 /* ---------------------------------------------------------------
123 * Screen Meta (Screen Options / Help panels)
124 * The toggle buttons (#screen-meta-links) are hidden because
125 * the parent desktop shell adds its own buttons to the window
126 * title bar. The panels themselves stay visible and functional —
127 * they're toggled via postMessage from the parent.
128 *
129 * Collapse all margins on the hidden links and the panel container
130 * so they don't leave a gap at the top of the iframe content.
131 * --------------------------------------------------------------- */
132 .os-chromeless #screen-meta-links {
133 display: none;
134 margin: 0;
135 }
136
137 .os-chromeless #screen-meta {
138 margin: 0;
139 border: none;
140 }
141
142 /* ---------------------------------------------------------------
143 * Wrap container
144 * Remove the default left margin that accounts for the sidebar
145 * which doesn't exist in chromeless mode.
146 * --------------------------------------------------------------- */
147 .os-chromeless .wrap {
148 margin: 0;
149 }
150
151 /* ---------------------------------------------------------------
152 * Page title & header
153 * The window title bar already shows the page title, so the
154 * in-page <h1> + header separator (.wp-header-end) are redundant
155 * inside chromeless iframes. Hide them so the window content
156 * starts flush.
157 *
158 * `.page-title-action` (the "Add New" / "Add Order" button next to
159 * the H1) stays VISIBLE by default — it's the only entry point to
160 * the add-new flow on many plugin pages (WooCommerce Orders, custom
161 * CPTs, plugin settings pages, etc.). A blanket hide would break
162 * every third-party plugin page that has no submenu equivalent, so
163 * the button is only removed where the window's own tab strip
164 * demonstrably leads to the same place — see
165 * `includes/render/chromeless-title-actions.php`, which emits one
166 * `[href="…"]` rule per submenu tab URL of the current screen.
167 *
168 * Sites that want to hide it somewhere that rule deliberately
169 * doesn't reach can add their own via the
170 * `openstation_chromeless_styles` action — e.g. WooCommerce's "Add
171 * order", which we keep because it points at `&action=new` and the
172 * Orders tab doesn't:
173 *
174 * body.woocommerce_page_wc-orders .wrap > .page-title-action {
175 * display: none;
176 * }
177 *
178 * --------------------------------------------------------------- */
179 .os-chromeless .wrap > h1,
180 .os-chromeless .wrap > h1.wp-heading-inline,
181 .os-chromeless #wpbody-content > .wrap > h1,
182 .os-chromeless .wrap > .wp-header-end {
183 display: none;
184 }
185
186 /*
187 * The H1 above it is hidden, so the button would float at the very
188 * top of the iframe area without breathing room — give it a small
189 * margin so it lands cleanly.
190 *
191 * `block` + `fit-content` instead of Core's `inline-block`: with the
192 * H1 hidden, an inline button ends up on the same line as the
193 * floated `.subsubsub` filter row and reads as one more filter link
194 * ("All (6) | Published (4) | Trash (2) Add Post"). Its own row is
195 * also what the screen already looks like when an admin notice is
196 * showing. `clear` keeps it below anything floated before it.
197 *
198 * Themes.php's native "Add Theme" button lives in the submenu tab
199 * strip via `openstation_inject_appearance_tabs`
200 * (includes/themes-tabs.php), so its in-page page-title-action is
201 * redundant on that one screen. The generic href-matching hide in
202 * `includes/render/chromeless-title-actions.php` only matches exact
203 * URLs, and the injected tab points at
204 * `theme-install.php?browse=popular` while the button points at
205 * plain `theme-install.php`. Hence the per-page rule below.
206 */
207 .os-chromeless .wrap > .page-title-action {
208 display: block;
209 width: fit-content;
210 margin-top: 12px;
211 margin-bottom: 12px;
212 clear: both;
213 }
214
215 .os-chromeless.themes-php .wrap > .page-title-action {
216 display: none;
217 }
218
219 /* ---------------------------------------------------------------
220 * WooCommerce "embed page" header overlay — page-scoped.
221 *
222 * Background: WC renders TWO layouts simultaneously on "connected"
223 * pages like `wc-orders` — the PHP-rendered legacy `.wrap` (with
224 * the h1 + the "Add order" `.page-title-action`) AND a React-mounted
225 * `EmbedHeader` (`client/admin/client/header/embed.tsx`) that
226 * `position: fixed`-overlays the page from the top. References:
227 * - `src/Internal/Admin/Loader.php::embed_page_header`
228 * - `includes/react-admin/connect-existing-pages.php` (registers
229 * wc-orders as a connected page when HPOS is on)
230 * - `client/admin/client/header/shared.tsx::useUpdateBodyMargin`
231 * (the hook that pushes `#wpbody.style.marginTop` to make
232 * room for the fixed header)
233 *
234 * The conflict, narrowly:
235 * 1. The legacy `.wrap > .page-title-action` ("Add order") at
236 * the top of the document body — primary add-new affordance.
237 * 2. The React `EmbedHeader` overlays the top of the iframe with
238 * a redundant `<h1>Orders</h1>` and an Activity Panel toggle.
239 *
240 * The fixed React header OBSCURES the legacy `.page-title-action`
241 * button when we reset `#wpbody`'s margin-top.
242 *
243 * Scope: ONLY the pages where the dual rendering is genuinely a
244 * problem — `wc-orders` (Orders list, the user-reported failure
245 * mode) and `wc-orders--shop_order` (the trash view variant). Other
246 * WC-admin pages (Analytics, Marketing, Customers, Coupons,
247 * Products, Reports, …) DON'T render a competing legacy `.wrap`
248 * with a `.page-title-action` button; their primary content IS the
249 * React app. On those pages the Activity Panel + EmbedHeader are
250 * the only header affordance the user has, so we keep them visible.
251 *
252 * If new HPOS-style connected pages emerge where the same dual
253 * layout creates the same conflict, add their per-page body class
254 * (`.woocommerce_page_<slug>`) to the selector — don't broaden the
255 * rule to `.woocommerce-admin-page` (that would silently strip the
256 * Activity Panel from every WC screen).
257 *
258 * @since 0.8.9
259 */
260 .os-chromeless.woocommerce_page_wc-orders .woocommerce-layout__header,
261 .os-chromeless.woocommerce_page_wc-orders--shop_order .woocommerce-layout__header {
262 display: none !important;
263 }
264 .os-chromeless.woocommerce_page_wc-orders #wpbody,
265 .os-chromeless.woocommerce_page_wc-orders--shop_order #wpbody {
266 margin-top: 0 !important;
267 }
268
269 /* ---------------------------------------------------------------
270 * Revisions screen — page-scoped.
271 *
272 * "← Go to editor" is a back button for a screen the user navigated
273 * into. Here they didn't: the editor is open in its own window
274 * behind this one, and closing this window is the way back.
275 *
276 * The revision tooltip is positioned upward from the bottom of
277 * `.revisions-control-frame` and clears the top of the viewport only
278 * thanks to the screen H1, which chromeless hides. An iframe can't
279 * overflow its box, so the space has to be given back: 48px covers
280 * both slider modes.
281 *
282 * The padding goes on `.revisions`, not on the frame — the frame is
283 * the positioned ancestor for the compare-mode checkbox and the
284 * tooltip, so padding it would leave both behind at the old top edge.
285 * --------------------------------------------------------------- */
286 .os-chromeless.revision-php .wrap > h1.long-header + a {
287 display: none;
288 }
289
290 .os-chromeless.revision-php .revisions {
291 padding-top: 48px;
292 }
293
294 /* ---------------------------------------------------------------
295 * Dashboard welcome panel
296 * The default 16px top margin pushes the panel down and creates a
297 * visible gap at the top of the iframe. Collapse it in chromeless
298 * mode so the panel sits flush with the top of the window body.
299 * --------------------------------------------------------------- */
300 .os-chromeless #welcome-panel,
301 .os-chromeless .welcome-panel {
302 margin-top: 0.5em;
303 }
304
305 /* ---------------------------------------------------------------
306 * Footer
307 * The classic footer is not rendered in chromeless mode,
308 * but some pages have bottom padding assuming it exists.
309 * --------------------------------------------------------------- */
310 .os-chromeless #wpbody-content {
311 margin: 0;
312 padding: 0 8px 8px;
313 float: none;
314 width: auto;
315 }
316
317 /* ---------------------------------------------------------------
318 * Block Editor — hide Gutenberg chrome that would break the window.
319 *
320 * The fullscreen-mode close button (the "W" logo top-left of the editor)
321 * is an <a href="/wp-admin/edit.php"> that navigates the iframe to a
322 * non-chromeless URL — which re-renders the entire classic admin
323 * inside our desktop window. The link interceptor in the chromeless
324 * bridge catches it too, but hiding the button removes the visual
325 * affordance so users never try to click "back to dashboard" inside
326 * what looks like a self-contained window.
327 *
328 * The site editor's equivalent navigation affordances (site hub toggle,
329 * "back to dashboard" link) get the same treatment.
330 *
331 * The welcome guide is intentionally NOT hidden anymore. Earlier
332 * iterations both CSS-hid the dialog AND flipped `core/edit-post:
333 * welcomeGuide` to `false` at the data layer on every chromeless
334 * mount — but Gutenberg already persists the user's "Get started"
335 * dismissal to user meta the moment they close it, so the override
336 * was permanently stealing the one-time orientation tour from every
337 * user who never got to see it. Modal's focus trap is Tab-only and
338 * doesn't fight the shell. Let it run. (See git history for the
339 * removed `openstation_chromeless_editor_preferences` override.)
340 * --------------------------------------------------------------- */
341 .os-chromeless .edit-post-fullscreen-mode-close,
342 .os-chromeless .edit-post-fullscreen-mode-close__view-mode-toggle,
343 .os-chromeless .edit-site-navigation-link,
344 .os-chromeless .edit-site-site-hub,
345 .os-chromeless .edit-site-site-hub__toggle {
346 display: none !important;
347 }
348
349 /* ---------------------------------------------------------------
350 * Block Editor & Site Editor — full-bleed layouts.
351 * Gutenberg owns its entire viewport (its own header bar, side
352 * panels, etc.). Any padding around #wpbody-content crops the
353 * editor and breaks its layout, so reset to zero on those pages
354 * and let the editor render edge-to-edge.
355 * --------------------------------------------------------------- */
356 .os-chromeless.block-editor-page #wpbody-content,
357 .os-chromeless.site-editor-php #wpbody-content,
358 .os-chromeless.is-fullscreen-mode #wpbody-content {
359 padding: 0;
360 }
361
362 /* ---------------------------------------------------------------
363 * "Boot" SPA pages (Font Library, Options → Connectors).
364 *
365 * These screens mount a React app into `.boot-layout-container` and
366 * paint `#wpwrap` a dark `#1e1e1e` via inline <style>. Our 8px
367 * right/bottom padding on `#wpbody-content` lets that dark color
368 * bleed through as two black gaps on the right and bottom edges of
369 * the window. Zero the padding for any page hosting a boot layout
370 * — detection is purely structural (`:has()`), so a plugin that
371 * adopts the same container class inherits the fix automatically.
372 * --------------------------------------------------------------- */
373 .os-chromeless #wpbody-content:has( .boot-layout-container ) {
374 padding: 0;
375 }
376
377 .os-chromeless:has( .boot-layout-container ) #wpwrap {
378 /* Neutralize the inline dark background so any stray overflow
379 * matches the window body instead of flashing black. */
380 background: transparent;
381 }
382
383 /*
384 * `boot-layout__surfaces` is the white card the React app draws its
385 * content onto. In classic admin it sits on the dark #wpwrap with
386 * rounded corners and a margin that makes it read as a "card." Inside
387 * a desktop window the surface IS the content — rounded corners and
388 * side margins leave ugly bleed at the edges. Collapse it to a plain
389 * edge-to-edge surface.
390 */
391 .os-chromeless .boot-layout__surfaces,
392 .os-chromeless .boot-layout__stage,
393 .os-chromeless .boot-layout__canvas {
394 border-radius: 0 !important;
395 margin: 0 !important;
396 }
397
398 /*
399 * The boot screens render their own H1 (`.boot-navigation-screen__title`,
400 * e.g., "Fonts") at the top of the surface. The desktop window already
401 * shows the page title in its title bar, so the in-page title is
402 * redundant. Visually hide it but keep it in the DOM for screen readers
403 * and for the boot app's own focus management.
404 */
405 .os-chromeless .boot-navigation-screen__title,
406 .os-chromeless .boot-navigation-screen__title-icon {
407 position: absolute;
408 width: 1px;
409 height: 1px;
410 padding: 0;
411 margin: -1px;
412 overflow: hidden;
413 clip: rect( 0, 0, 0, 0 );
414 white-space: nowrap;
415 border: 0;
416 }
417
418 /* ---------------------------------------------------------------
419 * Snackbars
420 *
421 * `components-snackbar-list` (Gutenberg `@wordpress/components`) and
422 * `boot-notices__snackbar` (Font Library / Connectors) position
423 * themselves `fixed` at the viewport bottom. Because a chromeless
424 * iframe IS its own viewport, those anchors already land inside the
425 * window — but our 8px bottom padding on `#wpbody-content` (applied
426 * to non-boot pages) plus some plugin-level `bottom: 40px` offsets
427 * meant for the classic admin footer can push them out of sight.
428 *
429 * Pin them flush to the bottom of the iframe and make sure nothing
430 * in the window chrome can occlude them.
431 *
432 * Triggering a snackbar to verify (OpenStation enabled):
433 * - Open Posts → Add New, save/publish → "Post published" toast.
434 * - Open Media, upload a file → "Uploaded" toast.
435 * - Open Appearance → Fonts, install a Google Font → toast.
436 * --------------------------------------------------------------- */
437 .os-chromeless .components-snackbar-list,
438 .os-chromeless .boot-notices__snackbar {
439 bottom: 16px !important;
440 z-index: 100000;
441 }
442
443 /*
444 * Force the Gutenberg interface skeleton to fill the iframe regardless
445 * of fullscreen state. When fullscreenMode is off, Gutenberg hardcodes
446 * `top: 32px` (admin bar reservation) and `left: 160px` (sidebar
447 * reservation) on `.interface-interface-skeleton` — both chromes we've
448 * already hidden, so those offsets become dead gaps at the top and
449 * left of the window. Zero them out unconditionally.
450 *
451 * The default is fullscreen (inset: 0), but users who turned fullscreen
452 * off in classic admin carry that preference into chromeless via the
453 * shared persistence layer. This rule keeps chromeless rendering
454 * edge-to-edge either way.
455 */
456 .os-chromeless .interface-interface-skeleton {
457 top: 0 !important;
458 left: 0 !important;
459 right: 0 !important;
460 bottom: 0 !important;
461 }
462
463 /* ---------------------------------------------------------------
464 * Notices
465 * Give notices a bit of top margin since there's no admin bar above.
466 * --------------------------------------------------------------- */
467 .os-chromeless .notice:first-child {
468 margin-top: 4px;
469 }
470
471 /* ---------------------------------------------------------------
472 * Update nag
473 * Core's global update / maintenance nags are detached server-side
474 * inside chromeless iframes (see
475 * `openstation_chromeless_suppress_update_nags()`) and re-surfaced
476 * once by the shell as a single notice.
477 * --------------------------------------------------------------- */
478 .os-chromeless .update-nag {
479 display: none !important;
480 }
481
482 /* ---------------------------------------------------------------
483 * Suppress WordPress's native command palette inside chromeless
484 * iframes. The chromeless bridge intercepts Cmd+K and forwards to
485 * the desktop shell, but if an in-page component triggers the
486 * palette via another path (programmatic dispatch, a menu item)
487 * the dialog would still render. Hide it so the shell's palette
488 * stays the only surface users ever see.
489 * --------------------------------------------------------------- */
490 .os-chromeless .commands-command-menu__container,
491 .os-chromeless .commands-command-menu {
492 display: none !important;
493 }
494
495 /* ---------------------------------------------------------------
496 * Appearance → Themes workspace
497 *
498 * Core's one-theme state renders the Theme Details dialog inline and
499 * removes its header. Inside a short desktop window that leaves a large,
500 * unexplained panel whose actions fall below the fold. Treat it as an
501 * active-site identity card instead: screenshot first, useful context at
502 * the side, and actions kept in the card's visible edge.
503 *
504 * With multiple installed themes the native cards become a deliberate
505 * library grid and Core's details view remains a dialog. All selectors are
506 * scoped to the chromeless Themes screen; the classic wp-admin page keeps
507 * Core's own presentation and the shell's palette cannot leak in here.
508 * --------------------------------------------------------------- */
509 .os-chromeless.themes-php #wpbody-content {
510 padding: 10px 16px;
511 background: #f6f7f7;
512 }
513
514 .os-chromeless.themes-php .openstation-themes-intro {
515 display: flex;
516 align-items: center;
517 justify-content: space-between;
518 gap: 24px;
519 margin: 0 0 8px;
520 padding: 14px 18px;
521 background: #fff;
522 border: 1px solid #dcdcde;
523 border-radius: 12px;
524 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
525 box-sizing: border-box;
526 }
527
528 .os-chromeless.themes-php .openstation-themes-intro__copy {
529 max-width: 680px;
530 }
531
532 .os-chromeless.themes-php .openstation-themes-intro__eyebrow {
533 margin: 0 0 5px;
534 color: var(--wp-admin-theme-color, #2271b1);
535 font-size: 11px;
536 font-weight: 700;
537 letter-spacing: 0.08em;
538 line-height: 1.4;
539 text-transform: uppercase;
540 }
541
542 .os-chromeless.themes-php .openstation-themes-intro h1 {
543 margin: 0;
544 color: #1d2327;
545 font-size: clamp(22px, 2.8vw, 30px);
546 font-weight: 650;
547 letter-spacing: -0.025em;
548 line-height: 1.12;
549 }
550
551 .os-chromeless.themes-php .openstation-themes-intro__copy > p:last-child {
552 max-width: 650px;
553 margin: 6px 0 0;
554 color: #50575e;
555 font-size: 13px;
556 line-height: 1.45;
557 }
558
559 .os-chromeless.themes-php .openstation-themes-intro__count {
560 display: grid;
561 flex: 0 0 auto;
562 min-width: 104px;
563 margin: 0;
564 padding: 10px 14px;
565 background: #f6f7f7;
566 border: 1px solid #dcdcde;
567 border-radius: 9px;
568 box-sizing: border-box;
569 text-align: center;
570 }
571
572 .os-chromeless.themes-php .openstation-themes-intro__count strong {
573 color: #1d2327;
574 font-size: 22px;
575 font-weight: 650;
576 letter-spacing: -0.03em;
577 line-height: 1;
578 }
579
580 .os-chromeless.themes-php .openstation-themes-intro__count span {
581 margin-top: 5px;
582 color: #646970;
583 font-size: 10px;
584 font-weight: 600;
585 letter-spacing: 0.04em;
586 line-height: 1.25;
587 text-transform: uppercase;
588 }
589
590 .os-chromeless.themes-php .wrap {
591 margin: 0;
592 }
593
594 .os-chromeless.themes-php .search-form {
595 display: flex;
596 margin: 0 0 16px;
597 padding: 0;
598 }
599
600 .os-chromeless.themes-php .wp-filter-search {
601 width: min(320px, 100%);
602 min-height: 36px;
603 padding-inline: 12px;
604 background: #fff;
605 border-color: #8c8f94;
606 border-radius: 7px;
607 }
608
609 /* Searching a library of one is noise, so the field goes away in Core's
610 * single-theme state. The general sibling combinator rather than `+`:
611 * themes.php emits `wp_admin_notice()` output between the search form and
612 * `.theme-browser` on `?activated`, `?deleted`, `?broken` and
613 * `?delete-active-child` — exactly the loads that follow activating or
614 * deleting a theme — which breaks strict adjacency. */
615 .os-chromeless.themes-php
616 .search-form:has(~ .theme-browser .themes.single-theme) {
617 display: none;
618 }
619
620 /* Installed-theme library. The Add Theme destination already has its own
621 * persistent window tab, so Core's duplicate dashed card is unnecessary. */
622 .os-chromeless.themes-php .theme-browser .themes:not(.single-theme) {
623 display: grid;
624 grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
625 gap: 18px;
626 clear: both;
627 }
628
629 .os-chromeless.themes-php .theme-browser .themes:not(.single-theme) > .theme {
630 float: none;
631 width: auto;
632 margin: 0;
633 background: #fff;
634 border: 1px solid #c3c4c7;
635 border-radius: 10px;
636 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
637 overflow: hidden;
638 transition:
639 border-color 120ms ease,
640 box-shadow 120ms ease,
641 transform 120ms ease;
642 }
643
644 .os-chromeless.themes-php
645 .theme-browser
646 .themes:not(.single-theme)
647 > .theme:hover,
648 .os-chromeless.themes-php
649 .theme-browser
650 .themes:not(.single-theme)
651 > .theme.focus {
652 border-color: #8c8f94;
653 box-shadow: 0 7px 20px rgba(0, 0, 0, 0.1);
654 transform: translateY(-2px);
655 }
656
657 .os-chromeless.themes-php
658 .theme-browser
659 .themes:not(.single-theme)
660 > .theme.active {
661 border-color: var(--wp-admin-theme-color, #2271b1);
662 box-shadow:
663 0 0 0 1px var(--wp-admin-theme-color, #2271b1),
664 0 7px 20px rgba(0, 0, 0, 0.08);
665 }
666
667 .os-chromeless.themes-php
668 .theme-browser
669 .themes:not(.single-theme)
670 > .add-new-theme {
671 display: none;
672 }
673
674 .os-chromeless.themes-php
675 .theme-browser
676 .themes:not(.single-theme)
677 .theme-screenshot {
678 background: #f0f0f1;
679 }
680
681 .os-chromeless.themes-php
682 .theme-browser
683 .themes:not(.single-theme)
684 .theme-id-container {
685 display: flex;
686 align-items: center;
687 justify-content: space-between;
688 min-height: 58px;
689 background: #fff;
690 }
691
692 .os-chromeless.themes-php .theme-browser .themes:not(.single-theme) .theme-name,
693 .os-chromeless.themes-php
694 .theme-browser
695 .themes:not(.single-theme)
696 .theme.active
697 .theme-name {
698 flex: 1 1 auto;
699 height: auto;
700 min-width: 0;
701 margin: 0;
702 padding: 14px 12px 14px 14px;
703 background: #fff;
704 box-shadow: none;
705 color: #1d2327;
706 font-size: 14px;
707 font-weight: 600;
708 line-height: 1.3;
709 }
710
711 .os-chromeless.themes-php
712 .theme-browser
713 .themes:not(.single-theme)
714 .theme.active
715 .theme-name
716 span {
717 display: block;
718 margin-bottom: 2px;
719 color: var(--wp-admin-theme-color, #2271b1);
720 font-size: 10px;
721 font-weight: 700;
722 letter-spacing: 0.06em;
723 line-height: 1.2;
724 text-transform: uppercase;
725 }
726
727 .os-chromeless.themes-php
728 .theme-browser
729 .themes:not(.single-theme)
730 .theme-actions,
731 .os-chromeless.themes-php
732 .theme-browser
733 .themes:not(.single-theme)
734 .theme.active
735 .theme-actions {
736 display: flex;
737 align-items: center;
738 flex: 0 0 auto;
739 position: static;
740 opacity: 1;
741 padding: 10px 12px 10px 0;
742 background: #fff;
743 border: 0;
744 box-shadow: none;
745 transform: none;
746 }
747
748 /* Multiple-theme details remain a modal, but fit the iframe rather than
749 * reserving the classic admin sidebar's 160px gutter. */
750 .os-chromeless.themes-php .theme-overlay.active .theme-backdrop {
751 position: fixed;
752 left: 0;
753 background: rgba(29, 35, 39, 0.62);
754 backdrop-filter: blur(2px);
755 }
756
757 .os-chromeless.themes-php .theme-overlay.active .theme-wrap {
758 top: 18px;
759 right: 18px;
760 bottom: 18px;
761 left: 18px;
762 border: 1px solid #c3c4c7;
763 border-radius: 12px;
764 box-shadow: 0 20px 55px rgba(0, 0, 0, 0.28);
765 overflow: hidden;
766 }
767
768 .os-chromeless.themes-php .theme-overlay.active .theme-header {
769 background: #fff;
770 }
771
772 .os-chromeless.themes-php .theme-overlay.active .theme-about {
773 padding: 24px;
774 }
775
776 .os-chromeless.themes-php .theme-overlay.active .theme-actions {
777 justify-content: flex-end;
778 padding: 10px 16px 5px;
779 }
780
781 /* Single-theme mode is a workspace card, not a stranded modal. */
782 .os-chromeless.themes-php .themes.single-theme {
783 display: block;
784 margin: 0;
785 }
786
787 .os-chromeless.themes-php .themes.single-theme .theme-overlay.active {
788 display: block;
789 position: static;
790 }
791
792 .os-chromeless.themes-php
793 .themes.single-theme
794 .theme-overlay.active
795 .theme-backdrop,
796 .os-chromeless.themes-php
797 .themes.single-theme
798 .theme-overlay.active
799 .theme-header {
800 display: none;
801 }
802
803 .os-chromeless.themes-php
804 .themes.single-theme
805 .theme-overlay.active
806 .theme-wrap {
807 display: grid;
808 grid-template-columns: minmax(320px, 0.95fr) minmax(300px, 1.05fr);
809 grid-template-areas: "screenshot details";
810 column-gap: 24px;
811 align-items: start;
812 position: relative;
813 top: auto;
814 right: auto;
815 bottom: auto;
816 left: auto;
817 min-height: 0;
818 padding: 12px 16px;
819 background: #fff;
820 border: 1px solid #c3c4c7;
821 border-radius: 12px;
822 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
823 overflow: visible;
824 z-index: 10;
825 }
826
827 .os-chromeless.themes-php
828 .themes.single-theme
829 .theme-overlay.active
830 .theme-about {
831 display: contents;
832 }
833
834 .os-chromeless.themes-php
835 .themes.single-theme
836 .theme-overlay.active
837 .theme-screenshots {
838 grid-area: screenshot;
839 float: none;
840 width: 100%;
841 max-width: none;
842 margin: 0;
843 }
844
845 .os-chromeless.themes-php
846 .themes.single-theme
847 .theme-overlay.active
848 .screenshot {
849 background: #f0f0f1;
850 border: 0;
851 border-radius: 8px;
852 box-shadow: 0 0 0 1px #dcdcde;
853 overflow: hidden;
854 }
855
856 .os-chromeless.themes-php
857 .themes.single-theme
858 .theme-overlay.active
859 .theme-info {
860 grid-area: details;
861 align-self: start;
862 float: none;
863 width: auto;
864 max-height: 240px;
865 padding: 2px 10px 2px 0;
866 overflow: auto;
867 scrollbar-gutter: stable;
868 }
869
870 .os-chromeless.themes-php
871 .themes.single-theme
872 .theme-overlay.active
873 .current-label {
874 margin: 0 0 10px;
875 padding: 4px 9px;
876 background: #e7f5ed;
877 border: 1px solid #9ad7b5;
878 border-radius: 999px;
879 color: #0a5c36;
880 font-size: 10px;
881 font-weight: 700;
882 letter-spacing: 0.05em;
883 line-height: 1.4;
884 text-transform: uppercase;
885 }
886
887 .os-chromeless.themes-php
888 .themes.single-theme
889 .theme-overlay.active
890 .theme-name {
891 margin: 0;
892 color: #1d2327;
893 font-size: clamp(25px, 3vw, 34px);
894 font-weight: 650;
895 letter-spacing: -0.03em;
896 line-height: 1.12;
897 }
898
899 .os-chromeless.themes-php
900 .themes.single-theme
901 .theme-overlay.active
902 .theme-version {
903 margin-left: 8px;
904 color: #646970;
905 font-size: 11px;
906 font-weight: 500;
907 letter-spacing: 0;
908 }
909
910 .os-chromeless.themes-php
911 .themes.single-theme
912 .theme-overlay.active
913 .theme-author {
914 margin: 8px 0 14px;
915 color: #646970;
916 font-size: 13px;
917 }
918
919 .os-chromeless.themes-php
920 .themes.single-theme
921 .theme-overlay.active
922 .theme-autoupdate {
923 margin: 0 0 14px;
924 padding: 9px 10px;
925 background: #f6f7f7;
926 border: 1px solid #dcdcde;
927 border-radius: 6px;
928 font-size: 12px;
929 }
930
931 .os-chromeless.themes-php
932 .themes.single-theme
933 .theme-overlay.active
934 .theme-description {
935 margin: 0;
936 color: #3c434a;
937 font-size: 13px;
938 line-height: 1.55;
939 }
940
941 .os-chromeless.themes-php
942 .themes.single-theme
943 .theme-overlay.active
944 .theme-tags,
945 .os-chromeless.themes-php
946 .themes.single-theme
947 .theme-overlay.active
948 .parent-theme {
949 margin-top: 16px;
950 padding-top: 12px;
951 border-top-width: 1px;
952 color: #646970;
953 font-size: 11px;
954 line-height: 1.5;
955 }
956
957 .os-chromeless.themes-php
958 .themes.single-theme
959 .theme-overlay.active
960 .theme-actions {
961 display: flex;
962 grid-area: details;
963 align-items: center;
964 align-self: end;
965 justify-content: flex-start;
966 position: static;
967 padding: 14px 0 0;
968 background: transparent;
969 border-top: 1px solid #dcdcde;
970 text-align: left;
971 }
972
973 .os-chromeless.themes-php
974 .themes.single-theme
975 .theme-overlay.active
976 .theme-actions
977 .active-theme {
978 display: flex;
979 flex-wrap: wrap;
980 gap: 7px;
981 }
982
983 .os-chromeless.themes-php
984 .themes.single-theme
985 .theme-overlay.active
986 .theme-actions
987 .inactive-theme {
988 display: none;
989 }
990
991 .os-chromeless.themes-php
992 .themes.single-theme
993 .theme-overlay.active
994 .theme-actions
995 .button {
996 margin: 0;
997 }
998
999 @media (max-width: 760px) {
1000 .os-chromeless.themes-php #wpbody-content {
1001 padding: 12px;
1002 }
1003
1004 .os-chromeless.themes-php .openstation-themes-intro {
1005 align-items: flex-start;
1006 gap: 16px;
1007 padding: 16px;
1008 }
1009
1010 .os-chromeless.themes-php .openstation-themes-intro__copy > p:last-child {
1011 display: none;
1012 }
1013
1014 .os-chromeless.themes-php
1015 .themes.single-theme
1016 .theme-overlay.active
1017 .theme-wrap {
1018 grid-template-columns: minmax(0, 1fr);
1019 grid-template-areas:
1020 "screenshot"
1021 "info"
1022 "actions";
1023 padding: 16px;
1024 }
1025
1026 .os-chromeless.themes-php
1027 .themes.single-theme
1028 .theme-overlay.active
1029 .theme-info {
1030 grid-area: info;
1031 max-height: none;
1032 padding-right: 0;
1033 overflow: visible;
1034 }
1035
1036 .os-chromeless.themes-php
1037 .themes.single-theme
1038 .theme-overlay.active
1039 .theme-actions {
1040 grid-area: actions;
1041 }
1042
1043 .os-chromeless.themes-php .theme-overlay.active .theme-wrap {
1044 top: 10px;
1045 right: 10px;
1046 bottom: 10px;
1047 left: 10px;
1048 }
1049 }
1050
1051 @media (max-width: 480px) {
1052 .os-chromeless.themes-php .openstation-themes-intro__count {
1053 display: none;
1054 }
1055
1056 .os-chromeless.themes-php .openstation-themes-intro h1 {
1057 font-size: 21px;
1058 }
1059 }
1060
1061 @media (prefers-reduced-motion: reduce) {
1062 .os-chromeless.themes-php
1063 .theme-browser
1064 .themes:not(.single-theme)
1065 > .theme {
1066 transition: none;
1067 }
1068 }
1069