PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.11
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.11
1.1.11 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 All 35 releases
← All changes | assets/css/chromeless.css +966 -86 0.8.8 → 1.1.11 View file →
@@ -1,17 +1,17 @@
1 1 /**
2 - * Desktop Mode — Chromeless Overrides.
2 + * OpenStation — Chromeless Overrides.
3 3 *
4 - * CSS adjustments for legacy admin pages rendered inside desktop mode
5 - * iframes (chromeless mode). All rules are scoped to .desktop-mode-chromeless
4 + * CSS adjustments for legacy admin pages rendered inside OpenStation
5 + * iframes (chromeless mode). All rules are scoped to .os-chromeless
6 6 * so they never affect the classic admin or the desktop shell.
7 7 *
8 8 * Plugin and theme developers: to add your own chromeless overrides,
9 - * enqueue a stylesheet on the 'desktop_mode_chromeless_styles' action.
10 - * Your CSS just needs to target .desktop-mode-chromeless as the body class.
9 + * enqueue a stylesheet on the 'openstation_chromeless_styles' action.
10 + * Your CSS just needs to target .os-chromeless as the body class.
11 11 *
12 12 * Example:
13 - * add_action( 'desktop_mode_chromeless_styles', function() {
13 + * add_action( 'openstation_chromeless_styles', function() {
14 14 * wp_enqueue_style( 'my-plugin-chromeless', plugin_dir_url( __FILE__ ) . 'chromeless.css' );
15 15 * } );
16 16 *
17 17 * @since 0.1.0
@@ -21,17 +21,42 @@
21 21 * Hide the classic admin chrome elements. These would otherwise
22 22 * reserve space (sidebar gutter, footer, admin bar) around the
23 23 * chromeless page content.
24 24 * --------------------------------------------------------------- */
25 -.desktop-mode-chromeless #adminmenuwrap,
26 -.desktop-mode-chromeless #adminmenuback,
27 -.desktop-mode-chromeless #wpfooter,
28 -.desktop-mode-chromeless #wpadminbar,
29 -.desktop-mode-chromeless .wp-responsive-toggle,
30 -.desktop-mode-chromeless #collapse-menu {
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 31 display: none !important;
32 32 }
33 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 +
34 59 /*
35 60 * _wp_admin_html_begin() adds the `wp-toolbar` class to <html> whenever
36 61 * is_admin_bar_showing() is true — which, in admin, is unconditional.
37 62 * The class carries a `padding-top: var(--wp-admin--admin-bar--height)`
@@ -48,9 +73,9 @@
48 73 * to clear the bar. Without this override they reserve a 32px (or 46px
49 74 * on small screens) gap that no longer exists, producing visible jumps
50 75 * on first paint and dead space at the top of the content area.
51 76 */
52 -html.wp-toolbar:has( body.desktop-mode-chromeless ) {
77 +html.wp-toolbar:has( body.os-chromeless ) {
53 78 padding-top: 0 !important;
54 79 --wp-admin--admin-bar--height: 0px;
55 80 --wp-admin--admin-bar--position-offset: 0px;
56 81 }
@@ -55,9 +80,9 @@
55 80 --wp-admin--admin-bar--position-offset: 0px;
56 81 }
57 82
58 83 /* Remove the sidebar gutter on #wpcontent and let the body fill the frame. */
59 -.desktop-mode-chromeless #wpcontent {
84 +.os-chromeless #wpcontent {
60 85 margin-inline-start: 0 !important;
61 86 padding-inline-start: 0 !important;
62 87 }
63 88
@@ -89,12 +114,40 @@
89 114 * iframe edge as WC's design intended, the gray strip disappears,
90 115 * and the header's content uses the full window width. No
91 116 * transform / overflow / visibility tricks needed.
92 117 */
93 -.desktop-mode-chromeless .woocommerce-layout__header {
118 +.os-chromeless .woocommerce-layout__header {
94 119 width: 100% !important;
95 120 }
96 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 +
97 150 /* ---------------------------------------------------------------
98 151 * Screen Meta (Screen Options / Help panels)
99 152 * The toggle buttons (#screen-meta-links) are hidden because
100 153 * the parent desktop shell adds its own buttons to the window
@@ -103,14 +156,14 @@
103 156 *
104 157 * Collapse all margins on the hidden links and the panel container
105 158 * so they don't leave a gap at the top of the iframe content.
106 159 * --------------------------------------------------------------- */
107 -.desktop-mode-chromeless #screen-meta-links {
160 +.os-chromeless #screen-meta-links {
108 161 display: none;
109 162 margin: 0;
110 163 }
111 164
112 -.desktop-mode-chromeless #screen-meta {
165 +.os-chromeless #screen-meta {
113 166 margin: 0;
114 167 border: none;
115 168 }
116 169
@@ -118,69 +171,189 @@
118 171 * Wrap container
119 172 * Remove the default left margin that accounts for the sidebar
120 173 * which doesn't exist in chromeless mode.
121 174 * --------------------------------------------------------------- */
122 -.desktop-mode-chromeless .wrap {
175 +.os-chromeless .wrap {
123 176 margin: 0;
124 177 }
125 178
126 179 /* ---------------------------------------------------------------
127 180 * Page title & header
128 - * The window title bar already shows the page title, and the
129 - * submenu tab strip below it already exposes actions like
130 - * "Add New". Hide the in-page <h1>, the adjacent action button
131 - * (.page-title-action), and the header separator (.wp-header-end)
132 - * so the window content starts flush without a redundant 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.
133 185 *
134 - * Plugins that need their action button to remain visible can
135 - * override these rules by enqueueing CSS on the
136 - * 'desktop_mode_chromeless_styles' action.
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 + *
137 206 * --------------------------------------------------------------- */
138 -.desktop-mode-chromeless .wrap > h1,
139 -.desktop-mode-chromeless .wrap > h1.wp-heading-inline,
140 -.desktop-mode-chromeless #wpbody-content > .wrap > h1,
141 -.desktop-mode-chromeless .wrap > .page-title-action,
142 -.desktop-mode-chromeless .wrap > .wp-header-end {
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 {
143 211 display: none;
144 212 }
145 213
146 214 /*
147 - * Exceptions: keep .page-title-action visible where it's the only
148 - * entry point to a sub-flow:
149 - * - plugin-install.php → "Upload Plugin"
150 - * - theme-install.php → "Upload Theme"
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.
151 218 *
152 - * themes.php's native "Add Theme" page-title-action used to live
153 - * here, but the iframe scrolled past it on load (the focus-target
154 - * heuristic on the visible theme grid stole the scroll position),
155 - * which made the only entry point to theme-install.php disappear.
156 - * The Appearance window now ships an "Add Theme" tab in the
157 - * submenu strip (see desktop_mode_inject_appearance_tabs in
158 - * includes/themes-tabs.php), so the in-page button is redundant
159 - * and stays hidden.
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.
160 225 *
161 - * The theme-install.php "Upload Theme" exception is necessary
162 - * because the upload form is its own sub-flow with no equivalent
163 - * affordance elsewhere in the chromeless shell — without this
164 - * rule, users who want to install a .zip theme have no entry
165 - * point.
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.
166 254 *
167 - * See issue #38.
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.
168 263 */
169 -.desktop-mode-chromeless.plugin-install-php .wrap > .page-title-action,
170 -.desktop-mode-chromeless.theme-install-php .wrap > .page-title-action {
171 - display: inline-block;
264 +.os-chromeless .wrap .page-title-action {
265 + top: 0;
266 +}
267 +
268 +.os-chromeless .wrap :has( > .page-title-action ) {
172 269 margin-top: 12px;
270 + margin-bottom: 12px;
173 271 }
174 272
175 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 +/* ---------------------------------------------------------------
176 349 * Dashboard welcome panel
177 350 * The default 16px top margin pushes the panel down and creates a
178 351 * visible gap at the top of the iframe. Collapse it in chromeless
179 352 * mode so the panel sits flush with the top of the window body.
180 353 * --------------------------------------------------------------- */
181 -.desktop-mode-chromeless #welcome-panel,
182 -.desktop-mode-chromeless .welcome-panel {
354 +.os-chromeless #welcome-panel,
355 +.os-chromeless .welcome-panel {
183 356 margin-top: 0.5em;
184 357 }
185 358
186 359 /* ---------------------------------------------------------------
@@ -187,9 +360,9 @@
187 360 * Footer
188 361 * The classic footer is not rendered in chromeless mode,
189 362 * but some pages have bottom padding assuming it exists.
190 363 * --------------------------------------------------------------- */
191 -.desktop-mode-chromeless #wpbody-content {
364 +.os-chromeless #wpbody-content {
192 365 margin: 0;
193 366 padding: 0 8px 8px;
194 367 float: none;
195 368 width: auto;
@@ -195,8 +368,85 @@
195 368 width: auto;
196 369 }
197 370
198 371 /* ---------------------------------------------------------------
372 + * Metabox screens — the one-column breakpoint is 160px too early.
373 + *
374 + * Core collapses every `#poststuff` two-column screen (the classic
375 + * post editor, any CPT editor, a WooCommerce order) to one column
376 + * at `max-width: 850px`. That number is a VIEWPORT width, and it is
377 + * sized for a viewport that still has the 160px admin menu in it:
378 + * at 851px core leaves roughly 650px of usable content — less than
379 + * the 763px the two columns actually need (`#post-body-content`'s
380 + * 463px floor plus the 300px sidebar reservation), which is why
381 + * core's own two-column layout scrolls sideways just above the
382 + * breakpoint.
383 + *
384 + * A chromeless iframe hides the admin menu, so the same 851px gives
385 + * 835px of content — comfortably two columns — and core stacks it
386 + * anyway. The sidebar then lands BELOW the editor, and since the
387 + * classic editor grows to fit its text, a long post pushes Publish,
388 + * Categories, Tags and Featured image thousands of pixels down the
389 + * page. The user's word for that is "vanished".
390 + *
391 + * Same shape as the WooCommerce header fix at the top of this file:
392 + * the page reserved space for a sidebar we removed, so reclaim the
393 + * reservation. Two columns stay until the content really stops
394 + * fitting: 763px of content, plus the 16px `#wpbody-content` gutter
395 + * above, plus up to 17px for a classic vertical scrollbar — which a
396 + * media query does NOT subtract from the width it matches on, the
397 + * same reason `100vw` overflows a scrolling page. That is 796px.
398 + * Below it, core's one-column layout is correct and stays.
399 + *
400 + * The reservation is written flow-relative rather than as core's
401 + * `margin-right` / `float: right`, because core ships the mirrored
402 + * values in a separate `edit-rtl.css` and this file has no RTL
403 + * build — one logical declaration lands on the correct side in
404 + * both directions. `float: right` stays ahead of `float:
405 + * inline-end` as the fallback.
406 + *
407 + * Attachments keep core's own, wider, 1200px breakpoint: the media
408 + * editor's sidebar carries the whole attachment form and needs the
409 + * room. `.post-type-attachment` is excluded rather than re-stacked
410 + * so that rule keeps working untouched.
411 + *
412 + * Verifying: open a post with a few thousand words in a window
413 + * ~800px wide. Publish/Categories/Tags sit beside the editor, not
414 + * under it, and the page has no horizontal scrollbar.
415 + * --------------------------------------------------------------- */
416 +@media only screen and ( min-width: 796px ) and ( max-width: 850px ) {
417 + .os-chromeless:not( .post-type-attachment ) #wpbody-content #poststuff #post-body.columns-2 {
418 + margin-inline-end: 300px;
419 + }
420 +
421 + .os-chromeless:not( .post-type-attachment ) #wpbody-content #post-body.columns-2 #postbox-container-1 {
422 + float: right;
423 + float: inline-end;
424 + margin-inline-end: -300px;
425 + width: 280px;
426 + }
427 +
428 + .os-chromeless:not( .post-type-attachment ) #poststuff #post-body.columns-2 #side-sortables {
429 + min-height: 250px;
430 + width: 280px;
431 + }
432 +
433 + /*
434 + * Core hides the Layout radios with the two-column layout. They
435 + * are the way back to one column, so they come back with it.
436 + *
437 + * `edit.css` is part of the `wp-admin` bundle and loads on every
438 + * screen, so that hide reaches the Dashboard's own 1-4 column
439 + * radios too. Only un-hide them where this block actually gave a
440 + * layout back.
441 + */
442 + .os-chromeless:not( .post-type-attachment ):has( #post-body.columns-2 ) .screen-layout,
443 + .os-chromeless:not( .post-type-attachment ):has( #post-body.columns-2 ) .columns-prefs {
444 + display: block;
445 + }
446 +}
447 +
448 +/* ---------------------------------------------------------------
199 449 * Block Editor — hide Gutenberg chrome that would break the window.
200 450 *
201 451 * The fullscreen-mode close button (the "W" logo top-left of the editor)
202 452 * is an <a href="/wp-admin/edit.php"> that navigates the iframe to a
@@ -216,15 +466,15 @@
216 466 * dismissal to user meta the moment they close it, so the override
217 467 * was permanently stealing the one-time orientation tour from every
218 468 * user who never got to see it. Modal's focus trap is Tab-only and
219 469 * doesn't fight the shell. Let it run. (See git history for the
220 - * removed `desktop_mode_chromeless_editor_preferences` override.)
470 + * removed `openstation_chromeless_editor_preferences` override.)
221 471 * --------------------------------------------------------------- */
222 -.desktop-mode-chromeless .edit-post-fullscreen-mode-close,
223 -.desktop-mode-chromeless .edit-post-fullscreen-mode-close__view-mode-toggle,
224 -.desktop-mode-chromeless .edit-site-navigation-link,
225 -.desktop-mode-chromeless .edit-site-site-hub,
226 -.desktop-mode-chromeless .edit-site-site-hub__toggle {
472 +.os-chromeless .edit-post-fullscreen-mode-close,
473 +.os-chromeless .edit-post-fullscreen-mode-close__view-mode-toggle,
474 +.os-chromeless .edit-site-navigation-link,
475 +.os-chromeless .edit-site-site-hub,
476 +.os-chromeless .edit-site-site-hub__toggle {
227 477 display: none !important;
228 478 }
229 479
230 480 /* ---------------------------------------------------------------
@@ -233,11 +483,11 @@
233 483 * panels, etc.). Any padding around #wpbody-content crops the
234 484 * editor and breaks its layout, so reset to zero on those pages
235 485 * and let the editor render edge-to-edge.
236 486 * --------------------------------------------------------------- */
237 -.desktop-mode-chromeless.block-editor-page #wpbody-content,
238 -.desktop-mode-chromeless.site-editor-php #wpbody-content,
239 -.desktop-mode-chromeless.is-fullscreen-mode #wpbody-content {
487 +.os-chromeless.block-editor-page #wpbody-content,
488 +.os-chromeless.site-editor-php #wpbody-content,
489 +.os-chromeless.is-fullscreen-mode #wpbody-content {
240 490 padding: 0;
241 491 }
242 492
243 493 /* ---------------------------------------------------------------
@@ -250,13 +500,13 @@
250 500 * the window. Zero the padding for any page hosting a boot layout
251 501 * — detection is purely structural (`:has()`), so a plugin that
252 502 * adopts the same container class inherits the fix automatically.
253 503 * --------------------------------------------------------------- */
254 -.desktop-mode-chromeless #wpbody-content:has( .boot-layout-container ) {
504 +.os-chromeless #wpbody-content:has( .boot-layout-container ) {
255 505 padding: 0;
256 506 }
257 507
258 -.desktop-mode-chromeless:has( .boot-layout-container ) #wpwrap {
508 +.os-chromeless:has( .boot-layout-container ) #wpwrap {
259 509 /* Neutralize the inline dark background so any stray overflow
260 510 * matches the window body instead of flashing black. */
261 511 background: transparent;
262 512 }
@@ -261,23 +511,57 @@
261 511 background: transparent;
262 512 }
263 513
264 514 /*
265 - * `boot-layout__surfaces` is the white card the React app draws its
266 - * content onto. In classic admin it sits on the dark #wpwrap with
515 + * The surfaces wrapper is the white card the React app draws its
516 + * content onto. In classic admin it sits on the dark layout with
267 517 * rounded corners and a margin that makes it read as a "card." Inside
268 - * a desktop window the surface IS the content — rounded corners and
269 - * side margins leave ugly bleed at the edges. Collapse it to a plain
518 + * a desktop window the surface IS the content, so that frame is just
519 + * bleed at the right and bottom edges. Collapse it to a plain
270 520 * edge-to-edge surface.
521 + *
522 + * Core builds these class names with CSS modules, so only the
523 + * `__<local-name>` suffix is stable; the hash in front of it changes
524 + * with every Core build. Match on the suffix, scoped to the boot
525 + * container. The bare `boot-layout__*` names are the pre-CSS-modules
526 + * spelling, kept for Core builds that still ship it.
527 + *
528 + * The container is not a tight enough scope on its own. The page a
529 + * boot screen routes to mounts INSIDE the layout, and a plugin page
530 + * built with CSS modules gives its own elements the same
531 + * `<hash>__<name>` shape: Jetpack's Stats v2 dashboard lays its
532 + * widgets out in a `…__layout` grid, and the `min-height: 100vh` meant
533 + * for Core's layout element stretched that grid to the window and
534 + * spread the dashboard's rows across it, with the widgets pushed
535 + * out of sight. Everything a page renders lives inside admin-ui's
536 + * navigable region, so exclude that subtree: these rules only ever
537 + * touch Core's own frame around the page, never the page.
271 538 */
272 -.desktop-mode-chromeless .boot-layout__surfaces,
273 -.desktop-mode-chromeless .boot-layout__stage,
274 -.desktop-mode-chromeless .boot-layout__canvas {
539 +.os-chromeless .boot-layout-container [class*='__surfaces']:not( .admin-ui-navigable-region * ),
540 +.os-chromeless .boot-layout-container [class*='__stage']:not( .admin-ui-navigable-region * ),
541 +.os-chromeless .boot-layout-container [class*='__canvas']:not( .admin-ui-navigable-region * ),
542 +.os-chromeless .boot-layout__surfaces,
543 +.os-chromeless .boot-layout__stage,
544 +.os-chromeless .boot-layout__canvas {
275 545 border-radius: 0 !important;
276 546 margin: 0 !important;
277 547 }
278 548
279 549 /*
550 + * The layout element is absolutely positioned inside a zero-height
551 + * `#wpbody`, so `top: 0; bottom: 0` resolve to nothing and its
552 + * `min-height: calc( 100vh - <admin bar> )` is what it actually gets.
553 + * A chromeless iframe has no admin bar, so that reservation shows up
554 + * as a dark band along the bottom of the window. Take the full
555 + * viewport back. Same navigable-region guard as above: the page's
556 + * own `…__layout` elements keep their height.
557 + */
558 +.os-chromeless .boot-layout-container [class*='__layout']:not( .admin-ui-navigable-region * ),
559 +.os-chromeless .boot-layout {
560 + min-height: 100vh !important;
561 +}
562 +
563 +/*
280 564 * The boot screens render their own H1 (`.boot-navigation-screen__title`,
281 565 * e.g., "Fonts") at the top of the surface. The desktop window already
282 566 * shows the page title in its title bar, so the in-page title is
283 567 * redundant. Visually hide it but keep it in the DOM for screen readers
@@ -282,10 +566,10 @@
282 566 * shows the page title in its title bar, so the in-page title is
283 567 * redundant. Visually hide it but keep it in the DOM for screen readers
284 568 * and for the boot app's own focus management.
285 569 */
286 -.desktop-mode-chromeless .boot-navigation-screen__title,
287 -.desktop-mode-chromeless .boot-navigation-screen__title-icon {
570 +.os-chromeless .boot-navigation-screen__title,
571 +.os-chromeless .boot-navigation-screen__title-icon {
288 572 position: absolute;
289 573 width: 1px;
290 574 height: 1px;
291 575 padding: 0;
@@ -309,15 +593,15 @@
309 593 *
310 594 * Pin them flush to the bottom of the iframe and make sure nothing
311 595 * in the window chrome can occlude them.
312 596 *
313 - * Triggering a snackbar to verify (desktop mode enabled):
597 + * Triggering a snackbar to verify (OpenStation enabled):
314 598 * - Open Posts → Add New, save/publish → "Post published" toast.
315 599 * - Open Media, upload a file → "Uploaded" toast.
316 600 * - Open Appearance → Fonts, install a Google Font → toast.
317 601 * --------------------------------------------------------------- */
318 -.desktop-mode-chromeless .components-snackbar-list,
319 -.desktop-mode-chromeless .boot-notices__snackbar {
602 +.os-chromeless .components-snackbar-list,
603 +.os-chromeless .boot-notices__snackbar {
320 604 bottom: 16px !important;
321 605 z-index: 100000;
322 606 }
323 607
@@ -333,9 +617,9 @@
333 617 * off in classic admin carry that preference into chromeless via the
334 618 * shared persistence layer. This rule keeps chromeless rendering
335 619 * edge-to-edge either way.
336 620 */
337 -.desktop-mode-chromeless .interface-interface-skeleton {
621 +.os-chromeless .interface-interface-skeleton {
338 622 top: 0 !important;
339 623 left: 0 !important;
340 624 right: 0 !important;
341 625 bottom: 0 !important;
@@ -344,19 +628,21 @@
344 628 /* ---------------------------------------------------------------
345 629 * Notices
346 630 * Give notices a bit of top margin since there's no admin bar above.
347 631 * --------------------------------------------------------------- */
348 -.desktop-mode-chromeless .notice:first-child {
632 +.os-chromeless .notice:first-child {
349 633 margin-top: 4px;
350 634 }
351 635
352 636 /* ---------------------------------------------------------------
353 637 * Update nag
354 - * Red top border signals a pending WordPress update; keep it so
355 - * the nag reads as urgent even without the surrounding admin chrome.
638 + * Core's global update / maintenance nags are detached server-side
639 + * inside chromeless iframes (see
640 + * `openstation_chromeless_suppress_update_nags()`) and re-surfaced
641 + * once by the shell as a single notice.
356 642 * --------------------------------------------------------------- */
357 -.desktop-mode-chromeless .update-nag {
358 - border-top: 1px solid #d63638;
643 +.os-chromeless .update-nag {
644 + display: none !important;
359 645 }
360 646
361 647 /* ---------------------------------------------------------------
362 648 * Suppress WordPress's native command palette inside chromeless
@@ -365,8 +651,602 @@
365 651 * palette via another path (programmatic dispatch, a menu item)
366 652 * the dialog would still render. Hide it so the shell's palette
367 653 * stays the only surface users ever see.
368 654 * --------------------------------------------------------------- */
369 -.desktop-mode-chromeless .commands-command-menu__container,
370 -.desktop-mode-chromeless .commands-command-menu {
655 +.os-chromeless .commands-command-menu__container,
656 +.os-chromeless .commands-command-menu {
371 657 display: none !important;
658 +}
659 +
660 +/* ---------------------------------------------------------------
661 + * A file drag over a native upload box.
662 + *
663 + * The chromeless bridge hands an OS-file drop to Core's own
664 + * `<input type="file">` when it lands on one, or anywhere on the
665 + * `form.wp-upload-form` box around it (Upload Plugin, Upload
666 + * Theme), and stamps `data-os-file-drop-active` on that box while
667 + * the drag is over it. Outside the shell the box gives no sign it
668 + * will take a drop; inside, the shell used to take the file
669 + * instead, so people learned it would not. Say so while it can.
670 + *
671 + * Core's own admin colour, read the way common.css reads it, not
672 + * the station's: this is a wp-admin control on a wp-admin page.
673 + * --------------------------------------------------------------- */
674 +.os-chromeless [data-os-file-drop-active] {
675 + outline: 2px dashed var( --wp-admin-theme-color, #3858e9 );
676 + outline-offset: -2px;
677 +}
678 +
679 +/* ---------------------------------------------------------------
680 + * Appearance → Themes workspace
681 + *
682 + * Core's one-theme state renders the Theme Details dialog inline and
683 + * removes its header. Inside a short desktop window that leaves a large,
684 + * unexplained panel whose actions fall below the fold. Treat it as an
685 + * active-site identity card instead: screenshot first, useful context at
686 + * the side, and actions kept in the card's visible edge.
687 + *
688 + * With multiple installed themes the native cards become a deliberate
689 + * library grid and Core's details view remains a dialog. All selectors are
690 + * scoped to the chromeless Themes screen; the classic wp-admin page keeps
691 + * Core's own presentation and the shell's palette cannot leak in here.
692 + * --------------------------------------------------------------- */
693 +.os-chromeless.themes-php #wpbody-content {
694 + padding: 10px 16px;
695 + background: #f6f7f7;
696 +}
697 +
698 +.os-chromeless.themes-php .openstation-themes-intro {
699 + display: flex;
700 + align-items: center;
701 + justify-content: space-between;
702 + gap: 24px;
703 + margin: 0 0 8px;
704 + padding: 14px 18px;
705 + background: #fff;
706 + border: 1px solid #dcdcde;
707 + border-radius: 12px;
708 + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
709 + box-sizing: border-box;
710 +}
711 +
712 +.os-chromeless.themes-php .openstation-themes-intro__copy {
713 + max-width: 680px;
714 +}
715 +
716 +.os-chromeless.themes-php .openstation-themes-intro__eyebrow {
717 + margin: 0 0 5px;
718 + color: var(--wp-admin-theme-color, #2271b1);
719 + font-size: 11px;
720 + font-weight: 700;
721 + letter-spacing: 0.08em;
722 + line-height: 1.4;
723 + text-transform: uppercase;
724 +}
725 +
726 +.os-chromeless.themes-php .openstation-themes-intro h1 {
727 + margin: 0;
728 + color: #1d2327;
729 + font-size: clamp(22px, 2.8vw, 30px);
730 + font-weight: 650;
731 + letter-spacing: -0.025em;
732 + line-height: 1.12;
733 +}
734 +
735 +.os-chromeless.themes-php .openstation-themes-intro__copy > p:last-child {
736 + max-width: 650px;
737 + margin: 6px 0 0;
738 + color: #50575e;
739 + font-size: 13px;
740 + line-height: 1.45;
741 +}
742 +
743 +.os-chromeless.themes-php .openstation-themes-intro__count {
744 + display: grid;
745 + flex: 0 0 auto;
746 + min-width: 104px;
747 + margin: 0;
748 + padding: 10px 14px;
749 + background: #f6f7f7;
750 + border: 1px solid #dcdcde;
751 + border-radius: 9px;
752 + box-sizing: border-box;
753 + text-align: center;
754 +}
755 +
756 +.os-chromeless.themes-php .openstation-themes-intro__count strong {
757 + color: #1d2327;
758 + font-size: 22px;
759 + font-weight: 650;
760 + letter-spacing: -0.03em;
761 + line-height: 1;
762 +}
763 +
764 +.os-chromeless.themes-php .openstation-themes-intro__count span {
765 + margin-top: 5px;
766 + color: #646970;
767 + font-size: 10px;
768 + font-weight: 600;
769 + letter-spacing: 0.04em;
770 + line-height: 1.25;
771 + text-transform: uppercase;
772 +}
773 +
774 +.os-chromeless.themes-php .wrap {
775 + margin: 0;
776 +}
777 +
778 +.os-chromeless.themes-php .search-form {
779 + display: flex;
780 + margin: 0 0 16px;
781 + padding: 0;
782 +}
783 +
784 +.os-chromeless.themes-php .wp-filter-search {
785 + width: min(320px, 100%);
786 + min-height: 36px;
787 + padding-inline: 12px;
788 + background: #fff;
789 + border-color: #8c8f94;
790 + border-radius: 7px;
791 +}
792 +
793 +/* Searching a library of one is noise, so the field goes away in Core's
794 + * single-theme state. The general sibling combinator rather than `+`:
795 + * themes.php emits `wp_admin_notice()` output between the search form and
796 + * `.theme-browser` on `?activated`, `?deleted`, `?broken` and
797 + * `?delete-active-child` — exactly the loads that follow activating or
798 + * deleting a theme — which breaks strict adjacency. */
799 +.os-chromeless.themes-php
800 + .search-form:has(~ .theme-browser .themes.single-theme) {
801 + display: none;
802 +}
803 +
804 +/* Installed-theme library. The Add Theme destination already has its own
805 + * persistent window tab, so Core's duplicate dashed card is unnecessary. */
806 +.os-chromeless.themes-php .theme-browser .themes:not(.single-theme) {
807 + display: grid;
808 + grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
809 + gap: 18px;
810 + clear: both;
811 +}
812 +
813 +.os-chromeless.themes-php .theme-browser .themes:not(.single-theme) > .theme {
814 + float: none;
815 + width: auto;
816 + margin: 0;
817 + background: #fff;
818 + border: 1px solid #c3c4c7;
819 + border-radius: 10px;
820 + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
821 + overflow: hidden;
822 + transition:
823 + border-color 120ms ease,
824 + box-shadow 120ms ease,
825 + transform 120ms ease;
826 +}
827 +
828 +.os-chromeless.themes-php
829 + .theme-browser
830 + .themes:not(.single-theme)
831 + > .theme:hover,
832 +.os-chromeless.themes-php
833 + .theme-browser
834 + .themes:not(.single-theme)
835 + > .theme.focus {
836 + border-color: #8c8f94;
837 + box-shadow: 0 7px 20px rgba(0, 0, 0, 0.1);
838 + transform: translateY(-2px);
839 +}
840 +
841 +.os-chromeless.themes-php
842 + .theme-browser
843 + .themes:not(.single-theme)
844 + > .theme.active {
845 + border-color: var(--wp-admin-theme-color, #2271b1);
846 + box-shadow:
847 + 0 0 0 1px var(--wp-admin-theme-color, #2271b1),
848 + 0 7px 20px rgba(0, 0, 0, 0.08);
849 +}
850 +
851 +.os-chromeless.themes-php
852 + .theme-browser
853 + .themes:not(.single-theme)
854 + > .add-new-theme {
855 + display: none;
856 +}
857 +
858 +.os-chromeless.themes-php
859 + .theme-browser
860 + .themes:not(.single-theme)
861 + .theme-screenshot {
862 + background: #f0f0f1;
863 +}
864 +
865 +.os-chromeless.themes-php
866 + .theme-browser
867 + .themes:not(.single-theme)
868 + .theme-id-container {
869 + display: flex;
870 + align-items: center;
871 + justify-content: space-between;
872 + min-height: 58px;
873 + background: #fff;
874 +}
875 +
876 +.os-chromeless.themes-php .theme-browser .themes:not(.single-theme) .theme-name,
877 +.os-chromeless.themes-php
878 + .theme-browser
879 + .themes:not(.single-theme)
880 + .theme.active
881 + .theme-name {
882 + flex: 1 1 auto;
883 + height: auto;
884 + min-width: 0;
885 + margin: 0;
886 + padding: 14px 12px 14px 14px;
887 + background: #fff;
888 + box-shadow: none;
889 + color: #1d2327;
890 + font-size: 14px;
891 + font-weight: 600;
892 + line-height: 1.3;
893 +}
894 +
895 +.os-chromeless.themes-php
896 + .theme-browser
897 + .themes:not(.single-theme)
898 + .theme.active
899 + .theme-name
900 + span {
901 + display: block;
902 + margin-bottom: 2px;
903 + color: var(--wp-admin-theme-color, #2271b1);
904 + font-size: 10px;
905 + font-weight: 700;
906 + letter-spacing: 0.06em;
907 + line-height: 1.2;
908 + text-transform: uppercase;
909 +}
910 +
911 +.os-chromeless.themes-php
912 + .theme-browser
913 + .themes:not(.single-theme)
914 + .theme-actions,
915 +.os-chromeless.themes-php
916 + .theme-browser
917 + .themes:not(.single-theme)
918 + .theme.active
919 + .theme-actions {
920 + display: flex;
921 + align-items: center;
922 + flex: 0 0 auto;
923 + position: static;
924 + opacity: 1;
925 + padding: 10px 12px 10px 0;
926 + background: #fff;
927 + border: 0;
928 + box-shadow: none;
929 + transform: none;
930 +}
931 +
932 +/* Multiple-theme details remain a modal, but fit the iframe rather than
933 + * reserving the classic admin sidebar's 160px gutter. */
934 +.os-chromeless.themes-php .theme-overlay.active .theme-backdrop {
935 + position: fixed;
936 + left: 0;
937 + background: rgba(29, 35, 39, 0.62);
938 + backdrop-filter: blur(2px);
939 +}
940 +
941 +.os-chromeless.themes-php .theme-overlay.active .theme-wrap {
942 + top: 18px;
943 + right: 18px;
944 + bottom: 18px;
945 + left: 18px;
946 + border: 1px solid #c3c4c7;
947 + border-radius: 12px;
948 + box-shadow: 0 20px 55px rgba(0, 0, 0, 0.28);
949 + overflow: hidden;
950 +}
951 +
952 +.os-chromeless.themes-php .theme-overlay.active .theme-header {
953 + background: #fff;
954 +}
955 +
956 +.os-chromeless.themes-php .theme-overlay.active .theme-about {
957 + padding: 24px;
958 +}
959 +
960 +.os-chromeless.themes-php .theme-overlay.active .theme-actions {
961 + justify-content: flex-end;
962 + padding: 10px 16px 5px;
963 +}
964 +
965 +/* Single-theme mode is a workspace card, not a stranded modal. */
966 +.os-chromeless.themes-php .themes.single-theme {
967 + display: block;
968 + margin: 0;
969 +}
970 +
971 +.os-chromeless.themes-php .themes.single-theme .theme-overlay.active {
972 + display: block;
973 + position: static;
974 +}
975 +
976 +.os-chromeless.themes-php
977 + .themes.single-theme
978 + .theme-overlay.active
979 + .theme-backdrop,
980 +.os-chromeless.themes-php
981 + .themes.single-theme
982 + .theme-overlay.active
983 + .theme-header {
984 + display: none;
985 +}
986 +
987 +.os-chromeless.themes-php
988 + .themes.single-theme
989 + .theme-overlay.active
990 + .theme-wrap {
991 + display: grid;
992 + grid-template-columns: minmax(320px, 0.95fr) minmax(300px, 1.05fr);
993 + grid-template-areas: "screenshot details";
994 + column-gap: 24px;
995 + align-items: start;
996 + position: relative;
997 + top: auto;
998 + right: auto;
999 + bottom: auto;
1000 + left: auto;
1001 + min-height: 0;
1002 + padding: 12px 16px;
1003 + background: #fff;
1004 + border: 1px solid #c3c4c7;
1005 + border-radius: 12px;
1006 + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
1007 + overflow: visible;
1008 + z-index: 10;
1009 +}
1010 +
1011 +.os-chromeless.themes-php
1012 + .themes.single-theme
1013 + .theme-overlay.active
1014 + .theme-about {
1015 + display: contents;
1016 +}
1017 +
1018 +.os-chromeless.themes-php
1019 + .themes.single-theme
1020 + .theme-overlay.active
1021 + .theme-screenshots {
1022 + grid-area: screenshot;
1023 + float: none;
1024 + width: 100%;
1025 + max-width: none;
1026 + margin: 0;
1027 +}
1028 +
1029 +.os-chromeless.themes-php
1030 + .themes.single-theme
1031 + .theme-overlay.active
1032 + .screenshot {
1033 + background: #f0f0f1;
1034 + border: 0;
1035 + border-radius: 8px;
1036 + box-shadow: 0 0 0 1px #dcdcde;
1037 + overflow: hidden;
1038 +}
1039 +
1040 +.os-chromeless.themes-php
1041 + .themes.single-theme
1042 + .theme-overlay.active
1043 + .theme-info {
1044 + grid-area: details;
1045 + align-self: start;
1046 + float: none;
1047 + width: auto;
1048 + max-height: 240px;
1049 + padding: 2px 10px 2px 0;
1050 + overflow: auto;
1051 + scrollbar-gutter: stable;
1052 +}
1053 +
1054 +.os-chromeless.themes-php
1055 + .themes.single-theme
1056 + .theme-overlay.active
1057 + .current-label {
1058 + margin: 0 0 10px;
1059 + padding: 4px 9px;
1060 + background: #e7f5ed;
1061 + border: 1px solid #9ad7b5;
1062 + border-radius: 999px;
1063 + color: #0a5c36;
1064 + font-size: 10px;
1065 + font-weight: 700;
1066 + letter-spacing: 0.05em;
1067 + line-height: 1.4;
1068 + text-transform: uppercase;
1069 +}
1070 +
1071 +.os-chromeless.themes-php
1072 + .themes.single-theme
1073 + .theme-overlay.active
1074 + .theme-name {
1075 + margin: 0;
1076 + color: #1d2327;
1077 + font-size: clamp(25px, 3vw, 34px);
1078 + font-weight: 650;
1079 + letter-spacing: -0.03em;
1080 + line-height: 1.12;
1081 +}
1082 +
1083 +.os-chromeless.themes-php
1084 + .themes.single-theme
1085 + .theme-overlay.active
1086 + .theme-version {
1087 + margin-left: 8px;
1088 + color: #646970;
1089 + font-size: 11px;
1090 + font-weight: 500;
1091 + letter-spacing: 0;
1092 +}
1093 +
1094 +.os-chromeless.themes-php
1095 + .themes.single-theme
1096 + .theme-overlay.active
1097 + .theme-author {
1098 + margin: 8px 0 14px;
1099 + color: #646970;
1100 + font-size: 13px;
1101 +}
1102 +
1103 +.os-chromeless.themes-php
1104 + .themes.single-theme
1105 + .theme-overlay.active
1106 + .theme-autoupdate {
1107 + margin: 0 0 14px;
1108 + padding: 9px 10px;
1109 + background: #f6f7f7;
1110 + border: 1px solid #dcdcde;
1111 + border-radius: 6px;
1112 + font-size: 12px;
1113 +}
1114 +
1115 +.os-chromeless.themes-php
1116 + .themes.single-theme
1117 + .theme-overlay.active
1118 + .theme-description {
1119 + margin: 0;
1120 + color: #3c434a;
1121 + font-size: 13px;
1122 + line-height: 1.55;
1123 +}
1124 +
1125 +.os-chromeless.themes-php
1126 + .themes.single-theme
1127 + .theme-overlay.active
1128 + .theme-tags,
1129 +.os-chromeless.themes-php
1130 + .themes.single-theme
1131 + .theme-overlay.active
1132 + .parent-theme {
1133 + margin-top: 16px;
1134 + padding-top: 12px;
1135 + border-top-width: 1px;
1136 + color: #646970;
1137 + font-size: 11px;
1138 + line-height: 1.5;
1139 +}
1140 +
1141 +.os-chromeless.themes-php
1142 + .themes.single-theme
1143 + .theme-overlay.active
1144 + .theme-actions {
1145 + display: flex;
1146 + grid-area: details;
1147 + align-items: center;
1148 + align-self: end;
1149 + justify-content: flex-start;
1150 + position: static;
1151 + padding: 14px 0 0;
1152 + background: transparent;
1153 + border-top: 1px solid #dcdcde;
1154 + text-align: left;
1155 +}
1156 +
1157 +.os-chromeless.themes-php
1158 + .themes.single-theme
1159 + .theme-overlay.active
1160 + .theme-actions
1161 + .active-theme {
1162 + display: flex;
1163 + flex-wrap: wrap;
1164 + gap: 7px;
1165 +}
1166 +
1167 +.os-chromeless.themes-php
1168 + .themes.single-theme
1169 + .theme-overlay.active
1170 + .theme-actions
1171 + .inactive-theme {
1172 + display: none;
1173 +}
1174 +
1175 +.os-chromeless.themes-php
1176 + .themes.single-theme
1177 + .theme-overlay.active
1178 + .theme-actions
1179 + .button {
1180 + margin: 0;
1181 +}
1182 +
1183 +@media (max-width: 760px) {
1184 + .os-chromeless.themes-php #wpbody-content {
1185 + padding: 12px;
1186 + }
1187 +
1188 + .os-chromeless.themes-php .openstation-themes-intro {
1189 + align-items: flex-start;
1190 + gap: 16px;
1191 + padding: 16px;
1192 + }
1193 +
1194 + .os-chromeless.themes-php .openstation-themes-intro__copy > p:last-child {
1195 + display: none;
1196 + }
1197 +
1198 + .os-chromeless.themes-php
1199 + .themes.single-theme
1200 + .theme-overlay.active
1201 + .theme-wrap {
1202 + grid-template-columns: minmax(0, 1fr);
1203 + grid-template-areas:
1204 + "screenshot"
1205 + "info"
1206 + "actions";
1207 + padding: 16px;
1208 + }
1209 +
1210 + .os-chromeless.themes-php
1211 + .themes.single-theme
1212 + .theme-overlay.active
1213 + .theme-info {
1214 + grid-area: info;
1215 + max-height: none;
1216 + padding-right: 0;
1217 + overflow: visible;
1218 + }
1219 +
1220 + .os-chromeless.themes-php
1221 + .themes.single-theme
1222 + .theme-overlay.active
1223 + .theme-actions {
1224 + grid-area: actions;
1225 + }
1226 +
1227 + .os-chromeless.themes-php .theme-overlay.active .theme-wrap {
1228 + top: 10px;
1229 + right: 10px;
1230 + bottom: 10px;
1231 + left: 10px;
1232 + }
1233 +}
1234 +
1235 +@media (max-width: 480px) {
1236 + .os-chromeless.themes-php .openstation-themes-intro__count {
1237 + display: none;
1238 + }
1239 +
1240 + .os-chromeless.themes-php .openstation-themes-intro h1 {
1241 + font-size: 21px;
1242 + }
1243 +}
1244 +
1245 +@media (prefers-reduced-motion: reduce) {
1246 + .os-chromeless.themes-php
1247 + .theme-browser
1248 + .themes:not(.single-theme)
1249 + > .theme {
1250 + transition: none;
1251 + }
372 1252 }