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

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

494 lines 20.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * OpenStation — Chromeless Overrides.
3 *
4 * CSS adjustments for legacy admin pages rendered inside OpenStation
5 * iframes (chromeless mode). All rules are scoped to .os-chromeless
6 * so they never affect the classic admin or the desktop shell.
7 *
8 * Plugin and theme developers: to add your own chromeless overrides,
9 * enqueue a stylesheet on the 'openstation_chromeless_styles' action.
10 * Your CSS just needs to target .os-chromeless as the body class.
11 *
12 * Example:
13 * add_action( 'openstation_chromeless_styles', function() {
14 * wp_enqueue_style( 'my-plugin-chromeless', plugin_dir_url( __FILE__ ) . 'chromeless.css' );
15 * } );
16 *
17 * @since 0.1.0
18 */
19
20 /* ---------------------------------------------------------------
21 * Hide the classic admin chrome elements. These would otherwise
22 * reserve space (sidebar gutter, footer, admin bar) around the
23 * chromeless page content.
24 * --------------------------------------------------------------- */
25 .os-chromeless #adminmenuwrap,
26 .os-chromeless #adminmenuback,
27 .os-chromeless #wpfooter,
28 .os-chromeless #wpadminbar,
29 .os-chromeless .wp-responsive-toggle,
30 .os-chromeless #collapse-menu {
31 display: none !important;
32 }
33
34 /* ---------------------------------------------------------------
35 * The iframe paints its own canvas.
36 *
37 * An admin page leaves whole regions unpainted — the gutter around
38 * `#wpbody-content`, the strip behind `.subsubsub` and `.tablenav`,
39 * everything below short content. In a normal admin tab the browser
40 * canvas is white underneath, so nobody notices. Inside an iframe the
41 * canvas is TRANSPARENT, and what shows through is the window element
42 * behind it — `--os-window-bg`.
43 *
44 * That was invisible while the window body was white and became a
45 * black band the moment the station's palette made it Obsidian. It is
46 * a real coupling either way: the look of a wp-admin page must not
47 * depend on the colour of the frame around it.
48 *
49 * So the chromeless document paints itself, in the colour the browser
50 * canvas would have been. Deliberately a literal and deliberately NOT
51 * a token: nothing about the shell's palette — or any desktop theme —
52 * should reach a real admin page. **An admin page in a window renders
53 * exactly as it does outside one.**
54 * --------------------------------------------------------------- */
55 body.os-chromeless {
56 background: #fff;
57 }
58
59 /*
60 * _wp_admin_html_begin() adds the `wp-toolbar` class to <html> whenever
61 * is_admin_bar_showing() is true — which, in admin, is unconditional.
62 * The class carries a `padding-top: var(--wp-admin--admin-bar--height)`
63 * that would leave a 32px (or 46px) dead gap at the top of the iframe.
64 * Zero it out inside chromeless iframes.
65 *
66 * We also rebind `--wp-admin--admin-bar--height` (and the derived
67 * `--wp-admin--admin-bar--position-offset` from block-library) to 0px
68 * inside chromeless so plugins that position UI relative to the admin
69 * bar resolve their math against the iframe's actual chrome state.
70 *
71 * WooCommerce's activity-panel wrapper, the block editor's sticky
72 * header, and several others use `top: var(--wp-admin--admin-bar--height)`
73 * to clear the bar. Without this override they reserve a 32px (or 46px
74 * on small screens) gap that no longer exists, producing visible jumps
75 * on first paint and dead space at the top of the content area.
76 */
77 html.wp-toolbar:has( body.os-chromeless ) {
78 padding-top: 0 !important;
79 --wp-admin--admin-bar--height: 0px;
80 --wp-admin--admin-bar--position-offset: 0px;
81 }
82
83 /* Remove the sidebar gutter on #wpcontent and let the body fill the frame. */
84 .os-chromeless #wpcontent {
85 margin-inline-start: 0 !important;
86 padding-inline-start: 0 !important;
87 }
88
89 /*
90 * WooCommerce sidebar-reservation override.
91 *
92 * `.woocommerce-layout__header` is a `position: fixed` bar WC
93 * mounts on every wc-admin and wc-embedded page. Its width is
94 * compiled from the SCSS source (header/style.scss) as a literal:
95 *
96 * position: fixed;
97 * top: 32px; // $adminbar-height
98 * width: calc(100% - 160px); // reserves classic sidebar
99 * z-index: 1001;
100 *
101 * The 160px subtraction is the classic admin menu width — WC bakes
102 * it in at build time so the header doesn't overlap the sidebar
103 * in standard admin. Inside chromeless we hide the sidebar, but
104 * the header keeps the reservation — so the header ends 160px
105 * short of the iframe right edge, and `.woocommerce-layout__activity-panel-wrapper`
106 * (`position: absolute; right: 0; top: 100%; transform: translateX(100%)`,
107 * containing block = the fixed header) translates past the
108 * header's right edge, NOT past the iframe's right edge. The
109 * resulting visible strip is exactly 160px wide — the size of the
110 * sidebar gap WC reserved for nothing.
111 *
112 * Reclaim the reservation: pin the header to full iframe width
113 * inside chromeless. The activity panel then translates past the
114 * iframe edge as WC's design intended, the gray strip disappears,
115 * and the header's content uses the full window width. No
116 * transform / overflow / visibility tricks needed.
117 */
118 .os-chromeless .woocommerce-layout__header {
119 width: 100% !important;
120 }
121
122 /* ---------------------------------------------------------------
123 * Screen Meta (Screen Options / Help panels)
124 * The toggle buttons (#screen-meta-links) are hidden because
125 * the parent desktop shell adds its own buttons to the window
126 * title bar. The panels themselves stay visible and functional —
127 * they're toggled via postMessage from the parent.
128 *
129 * Collapse all margins on the hidden links and the panel container
130 * so they don't leave a gap at the top of the iframe content.
131 * --------------------------------------------------------------- */
132 .os-chromeless #screen-meta-links {
133 display: none;
134 margin: 0;
135 }
136
137 .os-chromeless #screen-meta {
138 margin: 0;
139 border: none;
140 }
141
142 /* ---------------------------------------------------------------
143 * Wrap container
144 * Remove the default left margin that accounts for the sidebar
145 * which doesn't exist in chromeless mode.
146 * --------------------------------------------------------------- */
147 .os-chromeless .wrap {
148 margin: 0;
149 }
150
151 /* ---------------------------------------------------------------
152 * Page title & header
153 * The window title bar already shows the page title, so the
154 * in-page <h1> + header separator (.wp-header-end) are redundant
155 * inside chromeless iframes. Hide them so the window content
156 * starts flush.
157 *
158 * `.page-title-action` (the "Add New" / "Add Order" button next to
159 * the H1) stays VISIBLE by default — it's the only entry point to
160 * the add-new flow on many plugin pages (WooCommerce Orders, custom
161 * CPTs, plugin settings pages, etc.). A blanket hide would break
162 * every third-party plugin page that has no submenu equivalent, so
163 * the button is only removed where the window's own tab strip
164 * demonstrably leads to the same place — see
165 * `includes/render/chromeless-title-actions.php`, which emits one
166 * `[href="…"]` rule per submenu tab URL of the current screen.
167 *
168 * Sites that want to hide it somewhere that rule deliberately
169 * doesn't reach can add their own via the
170 * `openstation_chromeless_styles` action — e.g. WooCommerce's "Add
171 * order", which we keep because it points at `&action=new` and the
172 * Orders tab doesn't:
173 *
174 * body.woocommerce_page_wc-orders .wrap > .page-title-action {
175 * display: none;
176 * }
177 *
178 * --------------------------------------------------------------- */
179 .os-chromeless .wrap > h1,
180 .os-chromeless .wrap > h1.wp-heading-inline,
181 .os-chromeless #wpbody-content > .wrap > h1,
182 .os-chromeless .wrap > .wp-header-end {
183 display: none;
184 }
185
186 /*
187 * The H1 above it is hidden, so the button would float at the very
188 * top of the iframe area without breathing room — give it a small
189 * margin so it lands cleanly.
190 *
191 * `block` + `fit-content` instead of Core's `inline-block`: with the
192 * H1 hidden, an inline button ends up on the same line as the
193 * floated `.subsubsub` filter row and reads as one more filter link
194 * ("All (6) | Published (4) | Trash (2) Add Post"). Its own row is
195 * also what the screen already looks like when an admin notice is
196 * showing. `clear` keeps it below anything floated before it.
197 *
198 * Themes.php's native "Add Theme" button lives in the submenu tab
199 * strip via `openstation_inject_appearance_tabs`
200 * (includes/themes-tabs.php), so its in-page page-title-action is
201 * redundant on that one screen. The generic href-matching hide in
202 * `includes/render/chromeless-title-actions.php` only matches exact
203 * URLs, and the injected tab points at
204 * `theme-install.php?browse=popular` while the button points at
205 * plain `theme-install.php`. Hence the per-page rule below.
206 */
207 .os-chromeless .wrap > .page-title-action {
208 display: block;
209 width: fit-content;
210 margin-top: 12px;
211 margin-bottom: 12px;
212 clear: both;
213 }
214
215 .os-chromeless.themes-php .wrap > .page-title-action {
216 display: none;
217 }
218
219 /* ---------------------------------------------------------------
220 * WooCommerce "embed page" header overlay — page-scoped.
221 *
222 * Background: WC renders TWO layouts simultaneously on "connected"
223 * pages like `wc-orders` — the PHP-rendered legacy `.wrap` (with
224 * the h1 + the "Add order" `.page-title-action`) AND a React-mounted
225 * `EmbedHeader` (`client/admin/client/header/embed.tsx`) that
226 * `position: fixed`-overlays the page from the top. References:
227 * - `src/Internal/Admin/Loader.php::embed_page_header`
228 * - `includes/react-admin/connect-existing-pages.php` (registers
229 * wc-orders as a connected page when HPOS is on)
230 * - `client/admin/client/header/shared.tsx::useUpdateBodyMargin`
231 * (the hook that pushes `#wpbody.style.marginTop` to make
232 * room for the fixed header)
233 *
234 * The conflict, narrowly:
235 * 1. The legacy `.wrap > .page-title-action` ("Add order") at
236 * the top of the document body — primary add-new affordance.
237 * 2. The React `EmbedHeader` overlays the top of the iframe with
238 * a redundant `<h1>Orders</h1>` and an Activity Panel toggle.
239 *
240 * The fixed React header OBSCURES the legacy `.page-title-action`
241 * button when we reset `#wpbody`'s margin-top.
242 *
243 * Scope: ONLY the pages where the dual rendering is genuinely a
244 * problem — `wc-orders` (Orders list, the user-reported failure
245 * mode) and `wc-orders--shop_order` (the trash view variant). Other
246 * WC-admin pages (Analytics, Marketing, Customers, Coupons,
247 * Products, Reports, …) DON'T render a competing legacy `.wrap`
248 * with a `.page-title-action` button; their primary content IS the
249 * React app. On those pages the Activity Panel + EmbedHeader are
250 * the only header affordance the user has, so we keep them visible.
251 *
252 * If new HPOS-style connected pages emerge where the same dual
253 * layout creates the same conflict, add their per-page body class
254 * (`.woocommerce_page_<slug>`) to the selector — don't broaden the
255 * rule to `.woocommerce-admin-page` (that would silently strip the
256 * Activity Panel from every WC screen).
257 *
258 * @since 0.8.9
259 */
260 .os-chromeless.woocommerce_page_wc-orders .woocommerce-layout__header,
261 .os-chromeless.woocommerce_page_wc-orders--shop_order .woocommerce-layout__header {
262 display: none !important;
263 }
264 .os-chromeless.woocommerce_page_wc-orders #wpbody,
265 .os-chromeless.woocommerce_page_wc-orders--shop_order #wpbody {
266 margin-top: 0 !important;
267 }
268
269 /* ---------------------------------------------------------------
270 * Revisions screen — page-scoped.
271 *
272 * "← Go to editor" is a back button for a screen the user navigated
273 * into. Here they didn't: the editor is open in its own window
274 * behind this one, and closing this window is the way back.
275 *
276 * The revision tooltip is positioned upward from the bottom of
277 * `.revisions-control-frame` and clears the top of the viewport only
278 * thanks to the screen H1, which chromeless hides. An iframe can't
279 * overflow its box, so the space has to be given back: 48px covers
280 * both slider modes.
281 *
282 * The padding goes on `.revisions`, not on the frame — the frame is
283 * the positioned ancestor for the compare-mode checkbox and the
284 * tooltip, so padding it would leave both behind at the old top edge.
285 * --------------------------------------------------------------- */
286 .os-chromeless.revision-php .wrap > h1.long-header + a {
287 display: none;
288 }
289
290 .os-chromeless.revision-php .revisions {
291 padding-top: 48px;
292 }
293
294 /* ---------------------------------------------------------------
295 * Dashboard welcome panel
296 * The default 16px top margin pushes the panel down and creates a
297 * visible gap at the top of the iframe. Collapse it in chromeless
298 * mode so the panel sits flush with the top of the window body.
299 * --------------------------------------------------------------- */
300 .os-chromeless #welcome-panel,
301 .os-chromeless .welcome-panel {
302 margin-top: 0.5em;
303 }
304
305 /* ---------------------------------------------------------------
306 * Footer
307 * The classic footer is not rendered in chromeless mode,
308 * but some pages have bottom padding assuming it exists.
309 * --------------------------------------------------------------- */
310 .os-chromeless #wpbody-content {
311 margin: 0;
312 padding: 0 8px 8px;
313 float: none;
314 width: auto;
315 }
316
317 /* ---------------------------------------------------------------
318 * Block Editor — hide Gutenberg chrome that would break the window.
319 *
320 * The fullscreen-mode close button (the "W" logo top-left of the editor)
321 * is an <a href="/wp-admin/edit.php"> that navigates the iframe to a
322 * non-chromeless URL — which re-renders the entire classic admin
323 * inside our desktop window. The link interceptor in the chromeless
324 * bridge catches it too, but hiding the button removes the visual
325 * affordance so users never try to click "back to dashboard" inside
326 * what looks like a self-contained window.
327 *
328 * The site editor's equivalent navigation affordances (site hub toggle,
329 * "back to dashboard" link) get the same treatment.
330 *
331 * The welcome guide is intentionally NOT hidden anymore. Earlier
332 * iterations both CSS-hid the dialog AND flipped `core/edit-post:
333 * welcomeGuide` to `false` at the data layer on every chromeless
334 * mount — but Gutenberg already persists the user's "Get started"
335 * dismissal to user meta the moment they close it, so the override
336 * was permanently stealing the one-time orientation tour from every
337 * user who never got to see it. Modal's focus trap is Tab-only and
338 * doesn't fight the shell. Let it run. (See git history for the
339 * removed `openstation_chromeless_editor_preferences` override.)
340 * --------------------------------------------------------------- */
341 .os-chromeless .edit-post-fullscreen-mode-close,
342 .os-chromeless .edit-post-fullscreen-mode-close__view-mode-toggle,
343 .os-chromeless .edit-site-navigation-link,
344 .os-chromeless .edit-site-site-hub,
345 .os-chromeless .edit-site-site-hub__toggle {
346 display: none !important;
347 }
348
349 /* ---------------------------------------------------------------
350 * Block Editor & Site Editor — full-bleed layouts.
351 * Gutenberg owns its entire viewport (its own header bar, side
352 * panels, etc.). Any padding around #wpbody-content crops the
353 * editor and breaks its layout, so reset to zero on those pages
354 * and let the editor render edge-to-edge.
355 * --------------------------------------------------------------- */
356 .os-chromeless.block-editor-page #wpbody-content,
357 .os-chromeless.site-editor-php #wpbody-content,
358 .os-chromeless.is-fullscreen-mode #wpbody-content {
359 padding: 0;
360 }
361
362 /* ---------------------------------------------------------------
363 * "Boot" SPA pages (Font Library, Options → Connectors).
364 *
365 * These screens mount a React app into `.boot-layout-container` and
366 * paint `#wpwrap` a dark `#1e1e1e` via inline <style>. Our 8px
367 * right/bottom padding on `#wpbody-content` lets that dark color
368 * bleed through as two black gaps on the right and bottom edges of
369 * the window. Zero the padding for any page hosting a boot layout
370 * — detection is purely structural (`:has()`), so a plugin that
371 * adopts the same container class inherits the fix automatically.
372 * --------------------------------------------------------------- */
373 .os-chromeless #wpbody-content:has( .boot-layout-container ) {
374 padding: 0;
375 }
376
377 .os-chromeless:has( .boot-layout-container ) #wpwrap {
378 /* Neutralize the inline dark background so any stray overflow
379 * matches the window body instead of flashing black. */
380 background: transparent;
381 }
382
383 /*
384 * `boot-layout__surfaces` is the white card the React app draws its
385 * content onto. In classic admin it sits on the dark #wpwrap with
386 * rounded corners and a margin that makes it read as a "card." Inside
387 * a desktop window the surface IS the content — rounded corners and
388 * side margins leave ugly bleed at the edges. Collapse it to a plain
389 * edge-to-edge surface.
390 */
391 .os-chromeless .boot-layout__surfaces,
392 .os-chromeless .boot-layout__stage,
393 .os-chromeless .boot-layout__canvas {
394 border-radius: 0 !important;
395 margin: 0 !important;
396 }
397
398 /*
399 * The boot screens render their own H1 (`.boot-navigation-screen__title`,
400 * e.g., "Fonts") at the top of the surface. The desktop window already
401 * shows the page title in its title bar, so the in-page title is
402 * redundant. Visually hide it but keep it in the DOM for screen readers
403 * and for the boot app's own focus management.
404 */
405 .os-chromeless .boot-navigation-screen__title,
406 .os-chromeless .boot-navigation-screen__title-icon {
407 position: absolute;
408 width: 1px;
409 height: 1px;
410 padding: 0;
411 margin: -1px;
412 overflow: hidden;
413 clip: rect( 0, 0, 0, 0 );
414 white-space: nowrap;
415 border: 0;
416 }
417
418 /* ---------------------------------------------------------------
419 * Snackbars
420 *
421 * `components-snackbar-list` (Gutenberg `@wordpress/components`) and
422 * `boot-notices__snackbar` (Font Library / Connectors) position
423 * themselves `fixed` at the viewport bottom. Because a chromeless
424 * iframe IS its own viewport, those anchors already land inside the
425 * window — but our 8px bottom padding on `#wpbody-content` (applied
426 * to non-boot pages) plus some plugin-level `bottom: 40px` offsets
427 * meant for the classic admin footer can push them out of sight.
428 *
429 * Pin them flush to the bottom of the iframe and make sure nothing
430 * in the window chrome can occlude them.
431 *
432 * Triggering a snackbar to verify (OpenStation enabled):
433 * - Open Posts → Add New, save/publish → "Post published" toast.
434 * - Open Media, upload a file → "Uploaded" toast.
435 * - Open Appearance → Fonts, install a Google Font → toast.
436 * --------------------------------------------------------------- */
437 .os-chromeless .components-snackbar-list,
438 .os-chromeless .boot-notices__snackbar {
439 bottom: 16px !important;
440 z-index: 100000;
441 }
442
443 /*
444 * Force the Gutenberg interface skeleton to fill the iframe regardless
445 * of fullscreen state. When fullscreenMode is off, Gutenberg hardcodes
446 * `top: 32px` (admin bar reservation) and `left: 160px` (sidebar
447 * reservation) on `.interface-interface-skeleton` — both chromes we've
448 * already hidden, so those offsets become dead gaps at the top and
449 * left of the window. Zero them out unconditionally.
450 *
451 * The default is fullscreen (inset: 0), but users who turned fullscreen
452 * off in classic admin carry that preference into chromeless via the
453 * shared persistence layer. This rule keeps chromeless rendering
454 * edge-to-edge either way.
455 */
456 .os-chromeless .interface-interface-skeleton {
457 top: 0 !important;
458 left: 0 !important;
459 right: 0 !important;
460 bottom: 0 !important;
461 }
462
463 /* ---------------------------------------------------------------
464 * Notices
465 * Give notices a bit of top margin since there's no admin bar above.
466 * --------------------------------------------------------------- */
467 .os-chromeless .notice:first-child {
468 margin-top: 4px;
469 }
470
471 /* ---------------------------------------------------------------
472 * Update nag
473 * Core's global update / maintenance nags are detached server-side
474 * inside chromeless iframes (see
475 * `openstation_chromeless_suppress_update_nags()`) and re-surfaced
476 * once by the shell as a single notice.
477 * --------------------------------------------------------------- */
478 .os-chromeless .update-nag {
479 display: none !important;
480 }
481
482 /* ---------------------------------------------------------------
483 * Suppress WordPress's native command palette inside chromeless
484 * iframes. The chromeless bridge intercepts Cmd+K and forwards to
485 * the desktop shell, but if an in-page component triggers the
486 * palette via another path (programmatic dispatch, a menu item)
487 * the dialog would still render. Hide it so the shell's palette
488 * stays the only surface users ever see.
489 * --------------------------------------------------------------- */
490 .os-chromeless .commands-command-menu__container,
491 .os-chromeless .commands-command-menu {
492 display: none !important;
493 }
494