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

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