PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.1
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.1
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.1, at assets/css/chromeless.css

1,055 lines 32.0 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: 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: 32px;
519 margin: 0 0 16px;
520 padding: 20px 24px;
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: 8px 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: 12px 16px;
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: 24px;
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-rows: minmax(0, auto) auto;
810 grid-template-areas:
811 "screenshot info"
812 "screenshot actions";
813 column-gap: 28px;
814 row-gap: 14px;
815 align-items: start;
816 position: relative;
817 top: auto;
818 right: auto;
819 bottom: auto;
820 left: auto;
821 min-height: 0;
822 padding: 20px;
823 background: #fff;
824 border: 1px solid #c3c4c7;
825 border-radius: 12px;
826 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
827 overflow: visible;
828 z-index: 10;
829 }
830
831 .os-chromeless.themes-php
832 .themes.single-theme
833 .theme-overlay.active
834 .theme-about {
835 display: contents;
836 }
837
838 .os-chromeless.themes-php
839 .themes.single-theme
840 .theme-overlay.active
841 .theme-screenshots {
842 grid-area: screenshot;
843 float: none;
844 width: 100%;
845 max-width: none;
846 margin: 0;
847 }
848
849 .os-chromeless.themes-php
850 .themes.single-theme
851 .theme-overlay.active
852 .screenshot {
853 background: #f0f0f1;
854 border: 0;
855 border-radius: 8px;
856 box-shadow: 0 0 0 1px #dcdcde;
857 overflow: hidden;
858 }
859
860 .os-chromeless.themes-php
861 .themes.single-theme
862 .theme-overlay.active
863 .theme-info {
864 grid-area: info;
865 float: none;
866 width: auto;
867 max-height: 300px;
868 padding: 2px 10px 2px 0;
869 overflow: auto;
870 scrollbar-gutter: stable;
871 }
872
873 .os-chromeless.themes-php
874 .themes.single-theme
875 .theme-overlay.active
876 .current-label {
877 margin: 0 0 10px;
878 padding: 4px 9px;
879 background: #e7f5ed;
880 border: 1px solid #9ad7b5;
881 border-radius: 999px;
882 color: #0a5c36;
883 font-size: 10px;
884 font-weight: 700;
885 letter-spacing: 0.05em;
886 line-height: 1.4;
887 text-transform: uppercase;
888 }
889
890 .os-chromeless.themes-php
891 .themes.single-theme
892 .theme-overlay.active
893 .theme-name {
894 margin: 0;
895 color: #1d2327;
896 font-size: clamp(25px, 3vw, 34px);
897 font-weight: 650;
898 letter-spacing: -0.03em;
899 line-height: 1.12;
900 }
901
902 .os-chromeless.themes-php
903 .themes.single-theme
904 .theme-overlay.active
905 .theme-version {
906 margin-left: 8px;
907 color: #646970;
908 font-size: 11px;
909 font-weight: 500;
910 letter-spacing: 0;
911 }
912
913 .os-chromeless.themes-php
914 .themes.single-theme
915 .theme-overlay.active
916 .theme-author {
917 margin: 8px 0 14px;
918 color: #646970;
919 font-size: 13px;
920 }
921
922 .os-chromeless.themes-php
923 .themes.single-theme
924 .theme-overlay.active
925 .theme-autoupdate {
926 margin: 0 0 14px;
927 padding: 9px 10px;
928 background: #f6f7f7;
929 border: 1px solid #dcdcde;
930 border-radius: 6px;
931 font-size: 12px;
932 }
933
934 .os-chromeless.themes-php
935 .themes.single-theme
936 .theme-overlay.active
937 .theme-description {
938 margin: 0;
939 color: #3c434a;
940 font-size: 13px;
941 line-height: 1.55;
942 }
943
944 .os-chromeless.themes-php
945 .themes.single-theme
946 .theme-overlay.active
947 .theme-tags,
948 .os-chromeless.themes-php
949 .themes.single-theme
950 .theme-overlay.active
951 .parent-theme {
952 margin-top: 16px;
953 padding-top: 12px;
954 border-top-width: 1px;
955 color: #646970;
956 font-size: 11px;
957 line-height: 1.5;
958 }
959
960 .os-chromeless.themes-php
961 .themes.single-theme
962 .theme-overlay.active
963 .theme-actions {
964 display: flex;
965 grid-area: actions;
966 align-items: center;
967 justify-content: flex-start;
968 position: static;
969 padding: 14px 0 0;
970 background: transparent;
971 border-top: 1px solid #dcdcde;
972 text-align: left;
973 }
974
975 .os-chromeless.themes-php
976 .themes.single-theme
977 .theme-overlay.active
978 .theme-actions
979 > div {
980 display: flex;
981 flex-wrap: wrap;
982 gap: 7px;
983 }
984
985 .os-chromeless.themes-php
986 .themes.single-theme
987 .theme-overlay.active
988 .theme-actions
989 .button {
990 margin: 0;
991 }
992
993 @media (max-width: 760px) {
994 .os-chromeless.themes-php #wpbody-content {
995 padding: 12px;
996 }
997
998 .os-chromeless.themes-php .openstation-themes-intro {
999 align-items: flex-start;
1000 gap: 16px;
1001 padding: 16px;
1002 }
1003
1004 .os-chromeless.themes-php .openstation-themes-intro__copy > p:last-child {
1005 display: none;
1006 }
1007
1008 .os-chromeless.themes-php
1009 .themes.single-theme
1010 .theme-overlay.active
1011 .theme-wrap {
1012 grid-template-columns: minmax(0, 1fr);
1013 grid-template-areas:
1014 "screenshot"
1015 "info"
1016 "actions";
1017 padding: 16px;
1018 }
1019
1020 .os-chromeless.themes-php
1021 .themes.single-theme
1022 .theme-overlay.active
1023 .theme-info {
1024 max-height: none;
1025 padding-right: 0;
1026 overflow: visible;
1027 }
1028
1029 .os-chromeless.themes-php .theme-overlay.active .theme-wrap {
1030 top: 10px;
1031 right: 10px;
1032 bottom: 10px;
1033 left: 10px;
1034 }
1035 }
1036
1037 @media (max-width: 480px) {
1038 .os-chromeless.themes-php .openstation-themes-intro__count {
1039 display: none;
1040 }
1041
1042 .os-chromeless.themes-php .openstation-themes-intro h1 {
1043 font-size: 21px;
1044 }
1045 }
1046
1047 @media (prefers-reduced-motion: reduce) {
1048 .os-chromeless.themes-php
1049 .theme-browser
1050 .themes:not(.single-theme)
1051 > .theme {
1052 transition: none;
1053 }
1054 }
1055