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

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

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