PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.13
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.13
1.1.13 1.1.12 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 All 37 releases
← All changes | includes/welcome-dialog.php +508 -443 0.9.3 → 1.1.13 View file →
@@ -1,35 +1,36 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — First-run welcome dialog.
3 + * OpenStation — First-run welcome dialog.
4 4 *
5 - * Renders a one-time, self-contained modal inside the *classic* WordPress
6 - * admin (never inside the desktop shell or a chromeless iframe) telling
7 - * the user where the "Switch to Desktop Mode" button lives in the admin
8 - * bar. Dismissal is persisted via the existing seen-intros registry
5 + * Renders a one-time, self-contained modal inside the *classic*
6 + * WordPress admin (never inside the desktop shell or a chromeless
7 + * iframe) that introduces OpenStation to the user who activated it and
8 + * offers to switch it on. Dismissal is persisted via the existing
9 + * seen-intros registry
9 10 * (`desktop_mode_seen_intros` user meta, slug `activation-welcome`),
10 - * which means the "Reset what's-new dialogs" button in OS Settings →
11 - * Features brings it back exactly like every other intro dialog.
11 + * which means the "Reset what's-new dialogs" button in OpenStation
12 + * Preferences → Features brings it back exactly like every other intro
13 + * dialog.
12 14 *
13 15 * The dialog is intentionally self-contained — all HTML, CSS and JS are
14 16 * inlined into `admin_footer`. We deliberately do NOT use any of the
15 - * `<wpd-*>` shell components here because they only ship inside the
17 + * `<os-*>` shell components here because they only ship inside the
16 18 * desktop bundle, which is precisely *not* loaded on the classic admin
17 19 * screens where this dialog is allowed to appear.
18 20 *
19 - * @package Desktop_Mode
20 - * @since 0.8.2
21 + * @package OpenStation
21 22 */
22 23
23 24 defined( 'ABSPATH' ) || exit;
24 25
25 26 /** Slug stored in `desktop_mode_seen_intros` for this dialog. */
26 -const DESKTOP_MODE_WELCOME_INTRO_SLUG = 'activation-welcome';
27 +const OPENSTATION_WELCOME_INTRO_SLUG = 'activation-welcome';
27 28
28 29 /**
29 30 * Decides whether the welcome dialog should render on the current request.
30 31 *
31 - * Six gates:
32 + * Seven gates:
32 33 *
33 34 * 1. We're inside `/wp-admin` (`is_admin()`).
34 35 * 2. The user is logged in and can `read` (sanity gate — the dialog has
35 36 * no destructive surface, but anonymous output makes no sense).
@@ -35,25 +36,30 @@
35 36 * no destructive surface, but anonymous output makes no sense).
36 37 * 3. The request is NOT chromeless — chromeless pages are iframes
37 38 * rendering inside the desktop shell; the parent shell already shows
38 39 * its own UX.
39 - * 4. Desktop Mode is NOT already enabled for the user. This is a
40 - * "switch to Desktop Mode" promo, so it has nothing to say once the
40 + * 4. OpenStation is NOT already enabled for the user. This is a
41 + * "switch to OpenStation" promo, so it has nothing to say once the
41 42 * user is in the shell. The desktop shell's *parent* page is admin
42 43 * context and is not chromeless, so without this gate the dialog
43 - * re-renders there the moment the user clicks "Enable it now" — read
44 - * as a duplicate dialog, because the fire-and-forget seen-intro POST
45 - * races the redirect into the shell and often loses.
46 - * 5. The user has not already dismissed this intro.
47 - * 6. The `desktop_mode_show_welcome_dialog` filter returns truthy, so
44 + * re-renders there the moment the user clicks "Switch to
45 + * OpenStation", which reads as a duplicate dialog because the
46 + * fire-and-forget seen-intro POST races the redirect into the shell
47 + * and often loses.
48 + * 5. The user is the one who activated the plugin
49 + * ({@see openstation_record_activator()}). Dismissal is per user, so
50 + * without this every other account on the site would get the dialog
51 + * on its first wp-admin visit. Nobody gets it after an activation
52 + * without a user, or on an install activated before the activator
53 + * was recorded.
54 + * 6. The user has not already dismissed this intro.
55 + * 7. The `openstation_show_welcome_dialog` filter returns truthy, so
48 56 * sites can suppress the dialog entirely (e.g. managed-host onboarding
49 57 * flows that ship their own).
50 58 *
51 - * @since 0.8.2
52 - *
53 59 * @return bool
54 60 */
55 -function desktop_mode_should_show_welcome_dialog() {
61 +function openstation_should_show_welcome_dialog() {
56 62 if ( ! is_admin() || ! is_user_logged_in() ) {
57 63 return false;
58 64 }
59 65 if ( ! current_user_can( 'read' ) ) {
@@ -58,552 +64,605 @@
58 64 }
59 65 if ( ! current_user_can( 'read' ) ) {
60 66 return false;
61 67 }
62 - if ( function_exists( 'desktop_mode_is_chromeless_request' ) && desktop_mode_is_chromeless_request() ) {
68 + if ( function_exists( 'openstation_is_chromeless_request' ) && openstation_is_chromeless_request() ) {
63 69 return false;
64 70 }
65 - if ( function_exists( 'desktop_mode_is_enabled' ) && desktop_mode_is_enabled() ) {
71 + if ( function_exists( 'openstation_is_enabled' ) && openstation_is_enabled() ) {
66 72 return false;
67 73 }
68 74 $user_id = get_current_user_id();
69 - if ( desktop_mode_has_seen_intro( $user_id, DESKTOP_MODE_WELCOME_INTRO_SLUG ) ) {
75 + if ( (int) get_option( OPENSTATION_ACTIVATED_BY_OPTION, 0 ) !== $user_id ) {
70 76 return false;
71 77 }
78 + if ( openstation_has_seen_intro( $user_id, OPENSTATION_WELCOME_INTRO_SLUG ) ) {
79 + return false;
80 + }
72 81
73 82 /**
74 83 * Filters whether the first-run welcome dialog should render for
75 84 * the current user on the current request. All earlier gates
76 - * (admin context, capability, chromeless, seen-state) have
77 - * already passed by the time this filter fires.
85 + * (admin context, capability, chromeless, activator, seen-state)
86 + * have already passed by the time this filter fires.
78 87 *
79 - * @since 0.8.2
80 - *
81 88 * @param bool $show Whether to render the dialog. Default true.
82 89 * @param int $user_id Current user ID.
83 90 */
84 - return (bool) apply_filters( 'desktop_mode_show_welcome_dialog', true, $user_id );
91 + return (bool) apply_filters( 'openstation_show_welcome_dialog', true, $user_id );
85 92 }
86 93
87 94 /**
95 + * Returns one of OpenStation's own icons as inline SVG markup.
96 + *
97 + * Reads the outlined copies in `assets/icons/` (the ones registered with
98 + * Core's icon registry), which paint with `currentColor`, so the dialog's
99 + * CSS decides their colour. Returns an empty string for a missing file,
100 + * which leaves an empty icon slot rather than breaking the dialog.
101 + *
102 + * @param string $slug Icon slug, e.g. `windows`.
103 + * @return string Sanitised SVG markup.
104 + */
105 +function openstation_welcome_dialog_icon( $slug ) {
106 + $path = OPENSTATION_DIR . 'assets/icons/' . sanitize_key( $slug ) . '.svg';
107 + if ( ! is_readable( $path ) ) {
108 + return '';
109 + }
110 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local plugin file, not a remote URL.
111 + $svg = (string) file_get_contents( $path );
112 +
113 + return wp_kses(
114 + $svg,
115 + array(
116 + 'svg' => array(
117 + 'xmlns' => true,
118 + 'viewbox' => true,
119 + 'width' => true,
120 + 'height' => true,
121 + 'aria-hidden' => true,
122 + 'focusable' => true,
123 + ),
124 + 'path' => array(
125 + 'd' => true,
126 + 'fill' => true,
127 + ),
128 + )
129 + );
130 +}
131 +
132 +/**
88 133 * Prints the welcome dialog markup, styles and dismiss script into
89 134 * `admin_footer`.
90 135 *
91 136 * Self-contained on purpose: everything is scoped under the
92 - * `.desktop-mode-welcome` namespace so it cannot collide with the host
137 + * `.os-welcome` namespace so it cannot collide with the host
93 138 * admin theme. The dismiss button POSTs to the seen-intros REST route,
94 139 * which is exactly the same endpoint the in-shell intros use.
95 140 *
96 - * @since 0.8.2
141 + * The look is deliberately quiet: a light card with a dark panel that
142 + * shows two OpenStation windows, the holo mark, and Pulse kept to the
143 + * feature icons. Every colour is a literal from the brand palette rather
144 + * than a `--os-*` token, because `variables.css` is not loaded in classic
145 + * admin. Spacing sits on an 8px grid.
97 146 */
98 -function desktop_mode_render_welcome_dialog() {
99 - if ( ! desktop_mode_should_show_welcome_dialog() ) {
147 +function openstation_render_welcome_dialog() {
148 + if ( ! openstation_should_show_welcome_dialog() ) {
100 149 return;
101 150 }
102 151
103 - $rest_url = esc_url_raw( rest_url( 'desktop-mode/v1/intros/seen' ) );
104 - $rest_nonce = wp_create_nonce( 'wp_rest' );
105 - $ajax_url = esc_url_raw( admin_url( 'admin-ajax.php' ) );
106 - $ajax_nonce = wp_create_nonce( 'save-desktop-mode' );
107 - $slug = DESKTOP_MODE_WELCOME_INTRO_SLUG;
152 + $rest_url = esc_url_raw( rest_url( 'desktop-mode/v1/intros/seen' ) );
153 + $rest_nonce = wp_create_nonce( 'wp_rest' );
154 + $ajax_url = esc_url_raw( admin_url( 'admin-ajax.php' ) );
155 + $ajax_nonce = wp_create_nonce( 'save-openstation' );
156 + $slug = OPENSTATION_WELCOME_INTRO_SLUG;
157 + $font_url = OPENSTATION_URL . 'assets/fonts/Geist-Variable.woff2';
158 + $mono_url = OPENSTATION_URL . 'assets/fonts/GeistMono-Variable.woff2';
159 + $mark_url = OPENSTATION_URL . 'assets/images/openstation-mark-holo.svg';
108 160
109 161 // All user-facing strings are passed through translation; the dialog
110 162 // is keyboard-dismissible (Escape) and moves initial focus to the
111 163 // primary CTA.
112 - $title = __( 'Welcome to Desktop Mode', 'desktop-mode' );
113 - $subtitle = __( 'A floating, window-based workspace for the WordPress admin — more like a real OS, less like a webpage.', 'desktop-mode' );
114 - $body = __( 'Desktop Mode reimagines the WordPress admin as a true desktop environment. Open multiple admin screens side-by-side, drag and drop content between windows, and keep your work in view as you move between tasks.', 'desktop-mode' );
115 - $cta = __( 'Got it', 'desktop-mode' );
116 - $enable = __( 'Enable it now', 'desktop-mode' );
117 - $enabling = __( 'Enabling…', 'desktop-mode' );
118 - $close_a = __( 'Close', 'desktop-mode' );
164 + $title = __( 'Welcome to OpenStation', 'desktop-mode' );
165 + $body = __( 'Your admin, as a desktop. Keep several screens open at once and move between tasks without losing your place.', 'desktop-mode' );
166 + $later = __( 'Not now', 'desktop-mode' );
167 + $enable = __( 'Switch to OpenStation', 'desktop-mode' );
168 + $enabling = __( 'Switching…', 'desktop-mode' );
119 169
120 170 $features = array(
121 171 array(
122 - 'icon' => '🪟',
123 - 'title' => __( 'Multi-window workspace', 'desktop-mode' ),
124 - 'desc' => __( 'Open Posts, Media and Plugins in their own draggable, resizable windows. Tile, cascade or stack them however you work best.', 'desktop-mode' ),
172 + 'icon' => 'windows',
173 + 'title' => __( 'Work in windows', 'desktop-mode' ),
174 + 'desc' => __( 'Posts, media and settings side by side.', 'desktop-mode' ),
125 175 ),
126 176 array(
127 - 'icon' => '⚡',
128 - 'title' => __( 'Native, table-driven apps', 'desktop-mode' ),
129 - 'desc' => __( 'Posts, Pages, Users and Plugins are reimplemented as fast native windows with sticky headers, bulk actions, multi-select and inline previews.', 'desktop-mode' ),
177 + 'icon' => 'apps',
178 + 'title' => __( 'Apps for everyday tasks', 'desktop-mode' ),
179 + 'desc' => __( 'Fast lists with bulk actions and previews.', 'desktop-mode' ),
130 180 ),
131 181 array(
132 - 'icon' => '🎨',
133 - 'title' => __( 'Wallpapers, dock & themes', 'desktop-mode' ),
134 - 'desc' => __( 'Make it yours. Choose a wallpaper, pin your favorite screens to the dock, and switch accent colors — all from OS Settings.', 'desktop-mode' ),
182 + 'icon' => 'dock',
183 + 'title' => __( 'A dock for your screens', 'desktop-mode' ),
184 + 'desc' => __( 'Pin what you use most, one click away.', 'desktop-mode' ),
135 185 ),
136 186 array(
137 - 'icon' => '🤖',
138 - 'title' => __( 'AI Copilot, built in', 'desktop-mode' ),
139 - 'desc' => __( 'Press ⌘K anywhere to ask the AI Assistant — it can run commands, navigate windows and answer questions about your site.', 'desktop-mode' ),
187 + 'icon' => 'command',
188 + /* translators: %s: the keyboard shortcut that opens search, e.g. ⌘K. */
189 + 'title' => __( 'Search with %s', 'desktop-mode' ),
190 + 'desc' => __( 'Jump to any screen or run a command.', 'desktop-mode' ),
191 + 'kbd' => true,
140 192 ),
141 193 );
142 194 ?>
143 -<style id="desktop-mode-welcome-style">
144 - .desktop-mode-welcome,
145 - .desktop-mode-welcome * {
195 +<style id="os-welcome-style">
196 + @font-face {
197 + font-family: "OpenStation Geist";
198 + src: url( "<?php echo esc_url( $font_url ); ?>" ) format( "woff2" );
199 + font-weight: 100 900;
200 + font-style: normal;
201 + font-display: swap;
202 + }
203 + @font-face {
204 + font-family: "OpenStation Geist Mono";
205 + src: url( "<?php echo esc_url( $mono_url ); ?>" ) format( "woff2" );
206 + font-weight: 100 900;
207 + font-style: normal;
208 + font-display: swap;
209 + }
210 + .os-welcome,
211 + .os-welcome * {
146 212 box-sizing: border-box;
147 213 }
148 - .desktop-mode-welcome {
214 + .os-welcome {
215 + /* Brand palette, as literals: see the render function's docblock. */
216 + --_void: #0c0b0f;
217 + --_obsidian: #1a1721;
218 + --_astro: #33303a;
219 + --_silver: #4d4a52;
220 + --_pewter: #66636b;
221 + --_osmium: #99969c;
222 + --_ash: #b3afb5;
223 + --_cloud: #ccc8ce;
224 + --_mist: #e6e2e6;
225 + --_haze: #f4f1f4;
226 + --_starlight: #fffbff;
227 + --_pulse: #f252fc;
228 +
149 229 position: fixed;
150 230 inset: 0;
151 231 z-index: 100000;
152 232 display: flex;
153 - align-items: center;
154 - justify-content: center;
155 233 padding: 24px;
156 - background: radial-gradient(
157 - ellipse at 30% 20%,
158 - rgba( 56, 189, 248, 0.32 ),
159 - transparent 60%
160 - ),
161 - radial-gradient(
162 - ellipse at 70% 80%,
163 - rgba( 16, 185, 129, 0.26 ),
164 - transparent 55%
165 - ),
166 - rgba( 8, 14, 26, 0.62 );
167 - backdrop-filter: blur( 14px ) saturate( 140% );
168 - -webkit-backdrop-filter: blur( 14px ) saturate( 140% );
169 - animation: desktop-mode-welcome-fade 360ms cubic-bezier( 0.22, 1, 0.36, 1 );
170 - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
171 - "Helvetica Neue", Arial, sans-serif;
234 + overflow-y: auto;
235 + background: rgba( 12, 11, 15, 0.72 );
236 + backdrop-filter: blur( 16px );
237 + -webkit-backdrop-filter: blur( 16px );
238 + animation: os-welcome-fade 240ms ease-out;
239 + font-family: "OpenStation Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
240 + font-size: 16px;
241 + line-height: 1.5;
242 + color: var( --_obsidian );
172 243 -webkit-font-smoothing: antialiased;
173 244 -moz-osx-font-smoothing: grayscale;
174 245 }
175 - @keyframes desktop-mode-welcome-fade {
246 + @keyframes os-welcome-fade {
176 247 from { opacity: 0; }
177 248 to { opacity: 1; }
178 249 }
179 - @keyframes desktop-mode-welcome-pop {
180 - 0% { opacity: 0; transform: translateY( 14px ) scale( 0.96 ); }
181 - 100% { opacity: 1; transform: translateY( 0 ) scale( 1 ); }
250 + @keyframes os-welcome-pop {
251 + from { opacity: 0; transform: translateY( 8px ); }
252 + to { opacity: 1; transform: translateY( 0 ); }
182 253 }
183 - @keyframes desktop-mode-welcome-arrow {
184 - 0%, 100% { transform: translateX( 0 ); opacity: 0.85; }
185 - 50% { transform: translateX( 6px ); opacity: 1; }
186 - }
187 - @keyframes desktop-mode-welcome-shimmer {
188 - 0% { background-position: 0% 50%; }
189 - 100% { background-position: 200% 50%; }
190 - }
191 - .desktop-mode-welcome__card {
192 - position: relative;
254 + .os-welcome__card {
255 + /* margin:auto centres the card and still lets a short viewport scroll to its top. */
256 + margin: auto;
193 257 width: 100%;
194 - max-width: 760px;
195 - border-radius: 22px;
258 + max-width: 820px;
259 + min-height: 544px;
260 + display: grid;
261 + grid-template-columns: 5fr 6fr;
196 262 overflow: hidden;
197 - background: linear-gradient(
198 - 145deg,
199 - rgba( 255, 255, 255, 0.98 ) 0%,
200 - rgba( 244, 250, 253, 0.98 ) 100%
201 - );
263 + background: var( --_starlight );
264 + border-radius: 13px;
202 265 box-shadow:
203 - 0 1px 0 rgba( 255, 255, 255, 0.85 ) inset,
204 - 0 30px 60px -20px rgba( 8, 47, 73, 0.55 ),
205 - 0 18px 36px -18px rgba( 14, 165, 233, 0.45 );
206 - animation: desktop-mode-welcome-pop 460ms cubic-bezier( 0.22, 1, 0.36, 1 ) both;
207 - animation-delay: 60ms;
266 + 0 0 0 1px rgba( 12, 11, 15, 0.08 ),
267 + 0 32px 64px -24px rgba( 12, 11, 15, 0.48 );
268 + animation: os-welcome-pop 320ms cubic-bezier( 0.22, 1, 0.36, 1 ) both;
208 269 }
209 - .desktop-mode-welcome__card::before {
210 - content: "";
211 - position: absolute;
212 - inset: -1px;
213 - border-radius: inherit;
214 - padding: 1px;
215 - background: linear-gradient(
216 - 135deg,
217 - rgba( 14, 165, 233, 0.65 ),
218 - rgba( 6, 182, 212, 0.55 ),
219 - rgba( 16, 185, 129, 0.55 )
220 - );
221 - -webkit-mask: linear-gradient( #000 0 0 ) content-box,
222 - linear-gradient( #000 0 0 );
223 - -webkit-mask-composite: xor;
224 - mask-composite: exclude;
225 - pointer-events: none;
226 - }
227 - .desktop-mode-welcome__hero {
270 +
271 + /* ---- Dark panel: two windows on the station ------------------- */
272 +
273 + .os-welcome__art {
228 274 position: relative;
229 - padding: 36px 36px 24px;
230 - background: linear-gradient(
231 - 135deg,
232 - #0f172a 0%,
233 - #0e7490 45%,
234 - #06b6d4 100%
235 - );
236 - background-size: 200% 200%;
237 - animation: desktop-mode-welcome-shimmer 14s ease infinite alternate;
238 - color: #fff;
239 275 overflow: hidden;
276 + isolation: isolate;
277 + background:
278 + radial-gradient( 1px 1px at 12% 18%, rgba( 255, 251, 255, 0.55 ) 50%, transparent 51% ),
279 + radial-gradient( 1px 1px at 78% 12%, rgba( 255, 251, 255, 0.4 ) 50%, transparent 51% ),
280 + radial-gradient( 1px 1px at 64% 44%, rgba( 255, 251, 255, 0.3 ) 50%, transparent 51% ),
281 + radial-gradient( 1.5px 1.5px at 88% 62%, rgba( 255, 251, 255, 0.45 ) 50%, transparent 51% ),
282 + radial-gradient( 1px 1px at 22% 72%, rgba( 255, 251, 255, 0.35 ) 50%, transparent 51% ),
283 + radial-gradient( 1px 1px at 46% 88%, rgba( 255, 251, 255, 0.3 ) 50%, transparent 51% ),
284 + radial-gradient( 1px 1px at 8% 48%, rgba( 255, 251, 255, 0.3 ) 50%, transparent 51% ),
285 + radial-gradient( 1px 1px at 94% 90%, rgba( 255, 251, 255, 0.35 ) 50%, transparent 51% ),
286 + radial-gradient( 1px 1px at 36% 8%, rgba( 255, 251, 255, 0.35 ) 50%, transparent 51% ),
287 + radial-gradient( 60% 45% at 85% 100%, rgba( 236, 155, 255, 0.1 ), transparent 70% ),
288 + radial-gradient( 50% 40% at 0% 30%, rgba( 159, 152, 255, 0.07 ), transparent 70% ),
289 + var( --_void );
240 290 }
241 - .desktop-mode-welcome__hero::after {
291 + .os-welcome__art::after {
242 292 content: "";
243 293 position: absolute;
244 294 inset: 0;
295 + pointer-events: none;
245 296 background:
246 - radial-gradient( circle at 85% 15%, rgba( 56, 189, 248, 0.35 ), transparent 45% ),
247 - radial-gradient( circle at 15% 90%, rgba( 16, 185, 129, 0.22 ), transparent 50% ),
248 - linear-gradient( rgba( 255, 255, 255, 0.05 ) 1px, transparent 1px ) 0 0 / 28px 28px,
249 - linear-gradient( 90deg, rgba( 255, 255, 255, 0.05 ) 1px, transparent 1px ) 0 0 / 28px 28px;
250 - pointer-events: none;
251 - mask-image: radial-gradient( ellipse at 50% 40%, #000 35%, transparent 80% );
252 - -webkit-mask-image: radial-gradient( ellipse at 50% 40%, #000 35%, transparent 80% );
297 + linear-gradient( 180deg, rgba( 12, 11, 15, 0.85 ) 0%, rgba( 12, 11, 15, 0 ) 22% ),
298 + linear-gradient( 0deg, rgba( 12, 11, 15, 0.6 ) 0%, rgba( 12, 11, 15, 0 ) 26% );
253 299 }
254 - .desktop-mode-welcome__eyebrow {
255 - display: inline-flex;
300 + .os-welcome .os-welcome__mark {
301 + position: absolute;
302 + top: 32px;
303 + inset-inline-start: 32px;
304 + z-index: 2;
305 + display: block;
306 + width: 40px;
307 + height: 40px;
308 + max-width: none;
309 + }
310 + .os-welcome__pair {
311 + position: absolute;
312 + left: 50%;
313 + top: 50%;
314 + width: 300px;
315 + height: 276px;
316 + transform: translate( -50%, -44% );
317 + direction: ltr;
318 + }
319 + .os-welcome__win {
320 + position: absolute;
321 + overflow: hidden;
322 + border-radius: 9px;
323 + /* The window greys sit a step above the palette's dark ramp so the art reads against Void. */
324 + background: #201d28;
325 + border: 1px solid rgba( 255, 251, 255, 0.12 );
326 + box-shadow: 0 18px 36px -12px rgba( 0, 0, 0, 0.7 );
327 + font-size: 9px;
328 + line-height: 1;
329 + }
330 + .os-welcome__win--posts { left: 0; top: 0; width: 236px; height: 184px; }
331 + .os-welcome__win--media { right: 0; bottom: 0; width: 236px; height: 160px; }
332 + .os-welcome__bar {
333 + display: flex;
256 334 align-items: center;
335 + justify-content: space-between;
336 + height: 24px;
337 + padding: 0 8px;
338 + font-weight: 500;
339 + color: var( --_mist );
340 + border-bottom: 1px solid rgba( 255, 251, 255, 0.08 );
341 + }
342 + .os-welcome__bar-title { display: flex; align-items: center; gap: 8px; }
343 + .os-welcome__bar-title i { width: 7px; height: 7px; border: 1px solid var( --_osmium ); border-radius: 50%; }
344 + .os-welcome__bar-ctl { display: flex; gap: 8px; }
345 + .os-welcome__bar-ctl i { display: block; width: 6px; height: 6px; border: 1px solid #77747c; border-radius: 1.5px; }
346 + .os-welcome__bar-ctl i:first-child { height: 0; border-width: 1px 0 0; margin-top: 3px; border-radius: 0; }
347 + .os-welcome__rows { padding: 8px; }
348 + .os-welcome__row {
349 + display: grid;
350 + grid-template-columns: 7px 1fr 34px;
351 + align-items: center;
257 352 gap: 8px;
258 - padding: 5px 12px;
259 - font-size: 11px;
260 - font-weight: 600;
261 - letter-spacing: 0.12em;
262 - text-transform: uppercase;
263 - color: #fff;
264 - background: rgba( 255, 255, 255, 0.18 );
265 - border: 1px solid rgba( 255, 255, 255, 0.28 );
266 - border-radius: 999px;
267 - backdrop-filter: blur( 6px );
268 - -webkit-backdrop-filter: blur( 6px );
353 + height: 24px;
354 + border-bottom: 1px solid rgba( 255, 251, 255, 0.07 );
269 355 }
270 - .desktop-mode-welcome__eyebrow-dot {
271 - width: 6px;
272 - height: 6px;
273 - border-radius: 50%;
274 - background: #22d3ee;
275 - box-shadow: 0 0 0 4px rgba( 34, 211, 238, 0.28 );
356 + .os-welcome__row i { width: 7px; height: 7px; border: 1px solid #5c5963; border-radius: 2px; }
357 + .os-welcome__row b { height: 5px; border-radius: 3px; background: #5c5963; }
358 + .os-welcome__row em { height: 9px; border-radius: 999px; background: #3e3b46; }
359 + .os-welcome__row--head b,
360 + .os-welcome__row--head em { background: #3e3b46; }
361 + .os-welcome__row--selected { background: rgba( 255, 251, 255, 0.05 ); }
362 + .os-welcome__thumbs { padding: 8px; display: grid; grid-template-columns: repeat( 4, 1fr ); gap: 8px; }
363 + .os-welcome__thumbs i {
364 + display: block;
365 + aspect-ratio: 1;
366 + border-radius: 4px;
367 + background: linear-gradient( 150deg, #34303d, #27242f );
368 + border: 1px solid rgba( 255, 251, 255, 0.06 );
276 369 }
277 - .desktop-mode-welcome__title {
278 - margin: 14px 0 6px;
279 - font-size: 26px;
280 - line-height: 1.2;
281 - font-weight: 700;
370 + .os-welcome__thumbs i:nth-child( 3n + 1 ) { background: linear-gradient( 150deg, #3a3444, #2a2633 ); }
371 + .os-welcome__thumbs i:nth-child( 5 ) {
372 + background:
373 + radial-gradient( circle at 70% 30%, rgba( 236, 155, 255, 0.22 ), transparent 60% ),
374 + linear-gradient( 150deg, #36313f, #25222c );
375 + }
376 +
377 + /* ---- Content column ------------------------------------------- */
378 +
379 + .os-welcome__main {
380 + display: flex;
381 + flex-direction: column;
382 + min-width: 0;
383 + text-align: start;
384 + }
385 + .os-welcome__content {
386 + display: grid;
387 + gap: 24px;
388 + padding: 32px;
389 + }
390 + .os-welcome__head {
391 + display: grid;
392 + gap: 8px;
393 + }
394 + .os-welcome .os-welcome__title {
395 + margin: 0;
396 + padding: 0;
397 + font-family: inherit;
398 + font-size: 24px;
399 + line-height: 1.3;
400 + font-weight: 500;
282 401 letter-spacing: -0.01em;
283 - color: #fff;
402 + color: var( --_obsidian );
403 + text-wrap: balance;
284 404 }
285 - .desktop-mode-welcome__subtitle {
405 + .os-welcome .os-welcome__lede {
286 406 margin: 0;
287 - font-size: 14px;
407 + font-size: 16px;
288 408 line-height: 1.5;
289 - color: rgba( 255, 255, 255, 0.85 );
409 + color: var( --_silver );
290 410 }
291 - .desktop-mode-welcome__body {
292 - padding: 24px 36px 24px;
293 - font-size: 14.5px;
294 - line-height: 1.6;
295 - color: #1f2937;
296 - }
297 - .desktop-mode-welcome__lede {
411 + .os-welcome .os-welcome__features {
412 + display: grid;
413 + gap: 16px;
298 414 margin: 0;
299 - font-size: 14.5px;
300 - color: #374151;
301 - }
302 - .desktop-mode-welcome__features {
303 - display: grid;
304 - grid-template-columns: repeat( 2, minmax( 0, 1fr ) );
305 - gap: 14px;
306 - margin: 20px 0 0;
307 415 padding: 0;
308 416 list-style: none;
309 417 }
310 - .desktop-mode-welcome__feature {
311 - position: relative;
312 - padding: 14px 14px 14px 52px;
313 - background: linear-gradient(
314 - 145deg,
315 - rgba( 255, 255, 255, 0.85 ),
316 - rgba( 240, 249, 255, 0.85 )
317 - );
318 - border: 1px solid rgba( 14, 165, 233, 0.18 );
319 - border-radius: 12px;
320 - box-shadow: 0 1px 0 rgba( 255, 255, 255, 0.9 ) inset,
321 - 0 2px 6px -2px rgba( 8, 47, 73, 0.08 );
418 + .os-welcome .os-welcome__feature {
419 + display: flex;
420 + gap: 16px;
421 + margin: 0;
322 422 }
323 - .desktop-mode-welcome__feature-icon {
324 - position: absolute;
325 - top: 12px;
326 - left: 12px;
327 - width: 30px;
328 - height: 30px;
329 - display: inline-flex;
330 - align-items: center;
331 - justify-content: center;
332 - border-radius: 9px;
333 - background: linear-gradient( 135deg, rgba( 14, 165, 233, 0.16 ), rgba( 16, 185, 129, 0.16 ) );
334 - border: 1px solid rgba( 14, 165, 233, 0.22 );
335 - font-size: 16px;
336 - line-height: 1;
423 + .os-welcome__icon {
424 + flex: none;
425 + width: 20px;
426 + height: 20px;
427 + color: var( --_pulse );
337 428 }
338 - .desktop-mode-welcome__feature-title {
429 + .os-welcome__icon svg {
339 430 display: block;
340 - margin: 0 0 2px;
341 - font-size: 13.5px;
342 - font-weight: 700;
343 - color: #0f172a;
431 + width: 20px;
432 + height: 20px;
344 433 }
345 - .desktop-mode-welcome__feature-desc {
434 + .os-welcome__feature-title {
435 + display: block;
436 + font-size: 14px;
437 + font-weight: 600;
438 + line-height: 1.4;
439 + color: var( --_obsidian );
440 + }
441 + .os-welcome .os-welcome__feature-desc {
346 442 margin: 0;
347 - font-size: 12.5px;
443 + font-size: 14px;
348 444 line-height: 1.5;
349 - color: #475569;
445 + color: var( --_pewter );
350 446 }
351 - .desktop-mode-welcome__hint {
352 - display: flex;
353 - align-items: center;
354 - gap: 12px;
355 - margin: 18px 0 0;
356 - padding: 14px 16px;
357 - background: linear-gradient(
358 - 135deg,
359 - rgba( 14, 165, 233, 0.08 ),
360 - rgba( 16, 185, 129, 0.08 )
361 - );
362 - border: 1px solid rgba( 14, 165, 233, 0.22 );
363 - border-radius: 12px;
364 - font-size: 13.5px;
365 - color: #0c4a6e;
366 - }
367 - .desktop-mode-welcome__hint-icon {
368 - flex-shrink: 0;
369 - width: 24px;
370 - height: 24px;
371 - border-radius: 50%;
372 - display: inline-flex;
373 - align-items: center;
374 - justify-content: center;
375 - background: linear-gradient( 135deg, #0ea5e9, #06b6d4 );
376 - color: #fff;
377 - font-size: 14px;
378 - line-height: 1;
379 - animation: desktop-mode-welcome-arrow 1.8s ease-in-out infinite;
380 - }
381 - .desktop-mode-welcome__hint-icon::before {
382 - content: "→";
383 - font-weight: 700;
384 - }
385 - .desktop-mode-welcome__hint code {
447 + .os-welcome .os-welcome__kbd {
386 448 display: inline-block;
387 - padding: 2px 6px;
388 - margin: 0 2px;
389 - background: rgba( 255, 255, 255, 0.85 );
390 - border: 1px solid rgba( 14, 165, 233, 0.28 );
449 + margin: 0;
450 + padding: 0 8px;
451 + font-family: "OpenStation Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
452 + font-size: 12px;
453 + font-weight: 500;
454 + line-height: 16px;
455 + color: var( --_astro );
456 + background: var( --_haze );
457 + border: 1px solid var( --_mist );
458 + border-bottom-color: var( --_cloud );
391 459 border-radius: 5px;
392 - font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
393 - font-size: 12px;
394 - color: #0369a1;
460 + white-space: nowrap;
395 461 }
396 - .desktop-mode-welcome__actions {
462 +
463 + /* Anchored to the bottom of the card, however long the copy runs. */
464 + .os-welcome__actions {
465 + margin-top: auto;
397 466 display: flex;
398 - align-items: center;
467 + flex-wrap: wrap;
399 468 justify-content: flex-end;
400 - gap: 10px;
401 - padding: 0 36px 28px;
469 + gap: 8px;
470 + padding: 0 32px 32px;
402 471 }
403 - .desktop-mode-welcome__btn {
472 + .os-welcome .os-welcome__btn {
404 473 display: inline-flex;
405 474 align-items: center;
406 475 justify-content: center;
407 - gap: 6px;
408 - min-height: 38px;
409 - padding: 8px 18px;
476 + min-height: 40px;
477 + margin: 0;
478 + padding: 0 16px;
479 + font-family: inherit;
410 480 font-size: 14px;
411 - font-weight: 600;
412 - font-family: inherit;
481 + font-weight: 500;
413 482 line-height: 1;
414 - border-radius: 10px;
415 - border: 1px solid transparent;
483 + border-radius: 8px;
484 + border: 1px solid var( --_obsidian );
485 + box-shadow: none;
416 486 cursor: pointer;
417 - transition: transform 120ms ease, box-shadow 120ms ease,
418 - background 120ms ease, color 120ms ease;
487 + transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
419 488 }
420 - .desktop-mode-welcome__btn:focus-visible {
421 - outline: 2px solid #0ea5e9;
489 + .os-welcome .os-welcome__btn:focus-visible {
490 + outline: 2px solid var( --_obsidian );
422 491 outline-offset: 2px;
423 492 }
424 - .desktop-mode-welcome__btn--ghost {
425 - background: transparent;
426 - color: #475569;
427 - border-color: transparent;
493 + .os-welcome .os-welcome__btn--primary {
494 + color: var( --_starlight );
495 + background: var( --_obsidian );
428 496 }
429 - .desktop-mode-welcome__btn--ghost:hover {
430 - background: rgba( 15, 23, 42, 0.06 );
431 - color: #0f172a;
497 + .os-welcome .os-welcome__btn--primary:hover {
498 + background: var( --_astro );
499 + border-color: var( --_astro );
432 500 }
433 - .desktop-mode-welcome__btn--secondary {
434 - color: #0369a1;
435 - background: rgba( 14, 165, 233, 0.10 );
436 - border-color: rgba( 14, 165, 233, 0.28 );
501 + .os-welcome .os-welcome__btn--secondary {
502 + color: var( --_obsidian );
503 + background: transparent;
437 504 }
438 - .desktop-mode-welcome__btn--secondary:hover {
439 - background: rgba( 14, 165, 233, 0.16 );
440 - color: #075985;
505 + .os-welcome .os-welcome__btn--secondary:hover {
506 + background: var( --_haze );
441 507 }
442 - .desktop-mode-welcome__btn--primary {
443 - color: #fff;
444 - background: linear-gradient( 135deg, #0369a1, #0ea5e9 55%, #06b6d4 );
445 - background-size: 180% 180%;
446 - box-shadow: 0 10px 24px -10px rgba( 14, 165, 233, 0.75 ),
447 - 0 4px 10px -4px rgba( 6, 182, 212, 0.55 );
448 - }
449 - .desktop-mode-welcome__btn--primary:hover {
450 - transform: translateY( -1px );
451 - background-position: 100% 50%;
452 - box-shadow: 0 14px 30px -12px rgba( 14, 165, 233, 0.85 ),
453 - 0 6px 14px -4px rgba( 6, 182, 212, 0.65 );
454 - }
455 - .desktop-mode-welcome__btn--primary:active {
456 - transform: translateY( 0 );
457 - }
458 - .desktop-mode-welcome__btn[disabled] {
459 - opacity: 0.65;
508 + .os-welcome .os-welcome__btn[disabled] {
509 + opacity: 0.64;
460 510 cursor: progress;
461 511 }
462 - .desktop-mode-welcome__close {
463 - position: absolute;
464 - top: 14px;
465 - right: 14px;
466 - width: 32px;
467 - height: 32px;
468 - display: inline-flex;
469 - align-items: center;
470 - justify-content: center;
471 - padding: 0;
472 - background: rgba( 255, 255, 255, 0.18 );
473 - color: #fff;
474 - border: 1px solid rgba( 255, 255, 255, 0.25 );
475 - border-radius: 50%;
476 - cursor: pointer;
477 - font-size: 18px;
478 - line-height: 1;
479 - transition: background 120ms ease, transform 120ms ease;
480 - }
481 - .desktop-mode-welcome__close:hover {
482 - background: rgba( 255, 255, 255, 0.32 );
483 - transform: rotate( 90deg );
484 - }
485 - .desktop-mode-welcome__close:focus-visible {
486 - outline: 2px solid #fff;
487 - outline-offset: 2px;
488 - }
489 - body.desktop-mode-welcome-open {
512 + body.os-welcome-open {
490 513 overflow: hidden;
491 514 }
515 +
516 + /* ---- Narrow screens ------------------------------------------- */
517 +
492 518 @media ( max-width: 760px ) {
493 - .desktop-mode-welcome__features {
519 + .os-welcome__card {
494 520 grid-template-columns: 1fr;
521 + min-height: 0;
495 522 }
523 + .os-welcome__art {
524 + height: 216px;
525 + }
526 + .os-welcome__pair {
527 + transform: translate( -50%, -44% ) scale( 0.64 );
528 + }
496 529 }
497 - @media ( max-width: 600px ) {
498 - .desktop-mode-welcome__hero {
499 - padding: 28px 24px 20px;
530 + @media ( max-width: 480px ) {
531 + .os-welcome {
532 + padding: 16px;
500 533 }
501 - .desktop-mode-welcome__body,
502 - .desktop-mode-welcome__actions {
503 - padding-left: 24px;
504 - padding-right: 24px;
534 + .os-welcome .os-welcome__mark {
535 + top: 24px;
536 + inset-inline-start: 24px;
505 537 }
506 - .desktop-mode-welcome__title {
507 - font-size: 22px;
538 + .os-welcome__content {
539 + padding: 24px;
508 540 }
509 - .desktop-mode-welcome__actions {
510 - flex-direction: column-reverse;
511 - align-items: stretch;
541 + .os-welcome__actions {
542 + padding: 0 24px 24px;
512 543 }
513 - .desktop-mode-welcome__btn {
514 - width: 100%;
544 + .os-welcome .os-welcome__btn {
545 + flex: 1 1 auto;
515 546 }
516 547 }
517 548 @media ( prefers-reduced-motion: reduce ) {
518 - .desktop-mode-welcome,
519 - .desktop-mode-welcome__card,
520 - .desktop-mode-welcome__hero,
521 - .desktop-mode-welcome__hint-icon {
549 + .os-welcome,
550 + .os-welcome__card {
522 551 animation: none !important;
523 552 }
553 + .os-welcome .os-welcome__btn {
554 + transition: none;
555 + }
524 556 }
525 557 </style>
526 558 <div
527 - class="desktop-mode-welcome"
559 + class="os-welcome"
528 560 role="dialog"
529 561 aria-modal="true"
530 - aria-labelledby="desktop-mode-welcome-title"
531 - aria-describedby="desktop-mode-welcome-desc"
562 + aria-labelledby="os-welcome-title"
563 + aria-describedby="os-welcome-desc"
532 564 data-slug="<?php echo esc_attr( $slug ); ?>"
533 565 >
534 - <div class="desktop-mode-welcome__card">
535 - <button
536 - type="button"
537 - class="desktop-mode-welcome__close"
538 - aria-label="<?php echo esc_attr( $close_a ); ?>"
539 - data-desktop-mode-welcome-dismiss
540 - >&times;</button>
541 - <div class="desktop-mode-welcome__hero">
542 - <span class="desktop-mode-welcome__eyebrow">
543 - <span class="desktop-mode-welcome__eyebrow-dot" aria-hidden="true"></span>
544 - <?php echo esc_html__( 'New here', 'desktop-mode' ); ?>
545 - </span>
546 - <h2 id="desktop-mode-welcome-title" class="desktop-mode-welcome__title">
547 - <?php echo esc_html( $title ); ?>
548 - </h2>
549 - <p class="desktop-mode-welcome__subtitle">
550 - <?php echo esc_html( $subtitle ); ?>
551 - </p>
566 + <div class="os-welcome__card">
567 + <div class="os-welcome__art" aria-hidden="true">
568 + <img class="os-welcome__mark" src="<?php echo esc_url( $mark_url ); ?>" alt="" width="40" height="40" />
569 + <div class="os-welcome__pair">
570 + <div class="os-welcome__win os-welcome__win--posts">
571 + <div class="os-welcome__bar">
572 + <span class="os-welcome__bar-title"><i></i><?php echo esc_html__( 'Posts', 'desktop-mode' ); ?></span>
573 + <span class="os-welcome__bar-ctl"><i></i><i></i><i></i></span>
574 + </div>
575 + <div class="os-welcome__rows">
576 + <div class="os-welcome__row os-welcome__row--head"><i></i><b style="width:40%"></b><em></em></div>
577 + <div class="os-welcome__row"><i></i><b style="width:78%"></b><em></em></div>
578 + <div class="os-welcome__row os-welcome__row--selected"><i></i><b style="width:62%"></b><em></em></div>
579 + <div class="os-welcome__row"><i></i><b style="width:84%"></b><em></em></div>
580 + <div class="os-welcome__row"><i></i><b style="width:55%"></b><em></em></div>
581 + <div class="os-welcome__row"><i></i><b style="width:70%"></b><em></em></div>
582 + </div>
583 + </div>
584 + <div class="os-welcome__win os-welcome__win--media">
585 + <div class="os-welcome__bar">
586 + <span class="os-welcome__bar-title"><i></i><?php echo esc_html__( 'Media', 'desktop-mode' ); ?></span>
587 + <span class="os-welcome__bar-ctl"><i></i><i></i><i></i></span>
588 + </div>
589 + <div class="os-welcome__thumbs">
590 + <i></i><i></i><i></i><i></i>
591 + <i></i><i></i><i></i><i></i>
592 + </div>
593 + </div>
594 + </div>
552 595 </div>
553 - <div class="desktop-mode-welcome__body">
554 - <p id="desktop-mode-welcome-desc" class="desktop-mode-welcome__lede">
555 - <?php echo esc_html( $body ); ?>
556 - </p>
557 - <ul class="desktop-mode-welcome__features">
558 - <?php foreach ( $features as $feature ) : ?>
559 - <li class="desktop-mode-welcome__feature">
560 - <span class="desktop-mode-welcome__feature-icon" aria-hidden="true">
561 - <?php echo esc_html( $feature['icon'] ); ?>
562 - </span>
563 - <strong class="desktop-mode-welcome__feature-title">
564 - <?php echo esc_html( $feature['title'] ); ?>
565 - </strong>
566 - <p class="desktop-mode-welcome__feature-desc">
567 - <?php echo esc_html( $feature['desc'] ); ?>
568 - </p>
569 - </li>
570 - <?php endforeach; ?>
571 - </ul>
572 - <p class="desktop-mode-welcome__hint">
573 - <span class="desktop-mode-welcome__hint-icon" aria-hidden="true"></span>
574 - <?php
575 - printf(
576 - /* translators: %s: the label of the admin bar button. */
577 - esc_html__( 'Prefer to switch yourself? Click %s in the top-right of your admin bar.', 'desktop-mode' ),
578 - '<code>' . esc_html__( 'Switch to Desktop Mode', 'desktop-mode' ) . '</code>'
579 - );
580 - ?>
581 - </p>
596 + <div class="os-welcome__main">
597 + <div class="os-welcome__content">
598 + <div class="os-welcome__head">
599 + <h2 id="os-welcome-title" class="os-welcome__title">
600 + <?php echo esc_html( $title ); ?>
601 + </h2>
602 + <p id="os-welcome-desc" class="os-welcome__lede">
603 + <?php echo esc_html( $body ); ?>
604 + </p>
605 + </div>
606 + <ul class="os-welcome__features">
607 + <?php foreach ( $features as $feature ) : ?>
608 + <li class="os-welcome__feature">
609 + <span class="os-welcome__icon" aria-hidden="true">
610 + <?php echo openstation_welcome_dialog_icon( $feature['icon'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- sanitised with wp_kses() in the helper. ?>
611 + </span>
612 + <div>
613 + <strong class="os-welcome__feature-title">
614 + <?php
615 + if ( ! empty( $feature['kbd'] ) ) {
616 + echo wp_kses(
617 + sprintf(
618 + esc_html( $feature['title'] ),
619 + '<kbd class="os-welcome__kbd" data-os-welcome-shortcut>⌘K</kbd>'
620 + ),
621 + array(
622 + 'kbd' => array(
623 + 'class' => true,
624 + 'data-*' => true,
625 + ),
626 + )
627 + );
628 + } else {
629 + echo esc_html( $feature['title'] );
630 + }
631 + ?>
632 + </strong>
633 + <p class="os-welcome__feature-desc">
634 + <?php echo esc_html( $feature['desc'] ); ?>
635 + </p>
636 + </div>
637 + </li>
638 + <?php endforeach; ?>
639 + </ul>
640 + </div>
641 + <div class="os-welcome__actions">
642 + <button
643 + type="button"
644 + class="os-welcome__btn os-welcome__btn--secondary"
645 + data-os-welcome-cta
646 + >
647 + <?php echo esc_html( $later ); ?>
648 + </button>
649 + <button
650 + type="button"
651 + class="os-welcome__btn os-welcome__btn--primary"
652 + data-os-welcome-enable
653 + data-label-idle="<?php echo esc_attr( $enable ); ?>"
654 + data-label-busy="<?php echo esc_attr( $enabling ); ?>"
655 + >
656 + <?php echo esc_html( $enable ); ?>
657 + </button>
658 + </div>
582 659 </div>
583 - <div class="desktop-mode-welcome__actions">
584 - <button
585 - type="button"
586 - class="desktop-mode-welcome__btn desktop-mode-welcome__btn--secondary"
587 - data-desktop-mode-welcome-cta
588 - >
589 - <?php echo esc_html( $cta ); ?>
590 - </button>
591 - <button
592 - type="button"
593 - class="desktop-mode-welcome__btn desktop-mode-welcome__btn--primary"
594 - data-desktop-mode-welcome-enable
595 - data-label-idle="<?php echo esc_attr( $enable ); ?>"
596 - data-label-busy="<?php echo esc_attr( $enabling ); ?>"
597 - >
598 - <?php echo esc_html( $enable ); ?>
599 - </button>
600 - </div>
601 660 </div>
602 661 </div>
603 -<script id="desktop-mode-welcome-script">
662 +<script id="os-welcome-script">
604 663 ( function () {
605 - var root = document.querySelector( '.desktop-mode-welcome' );
664 + var root = document.querySelector( '.os-welcome' );
606 665 if ( ! root ) {
607 666 return;
608 667 }
609 668 var cfg = {
@@ -613,13 +672,19 @@
613 672 ajaxUrl: <?php echo wp_json_encode( $ajax_url ); ?>,
614 673 ajaxNonce: <?php echo wp_json_encode( $ajax_nonce ); ?>,
615 674 };
616 675
617 - document.body.classList.add( 'desktop-mode-welcome-open' );
676 + document.body.classList.add( 'os-welcome-open' );
618 677
678 + // The shell answers Cmd+K and Ctrl+K alike; show the one this keyboard has.
679 + var shortcut = root.querySelector( '[data-os-welcome-shortcut]' );
680 + if ( shortcut && ! /Mac|iPhone|iPad|iPod/.test( navigator.platform || navigator.userAgent ) ) {
681 + shortcut.textContent = 'Ctrl K';
682 + }
683 +
619 684 // Focus the primary CTA so keyboard users land somewhere meaningful.
620 - var primary = root.querySelector( '[data-desktop-mode-welcome-enable]' )
621 - || root.querySelector( '[data-desktop-mode-welcome-cta]' );
685 + var primary = root.querySelector( '[data-os-welcome-enable]' )
686 + || root.querySelector( '[data-os-welcome-cta]' );
622 687 if ( primary ) {
623 688 try { primary.focus( { preventScroll: true } ); } catch ( e ) {}
624 689 }
625 690
@@ -627,9 +692,9 @@
627 692 if ( ! root || ! root.parentNode ) {
628 693 return;
629 694 }
630 695 root.parentNode.removeChild( root );
631 - document.body.classList.remove( 'desktop-mode-welcome-open' );
696 + document.body.classList.remove( 'os-welcome-open' );
632 697 document.removeEventListener( 'keydown', onKey );
633 698 }
634 699
635 700 // Rebuilds an absolute URL onto the origin the admin page was actually
@@ -663,13 +728,14 @@
663 728 var url = sameOrigin( cfg.url );
664 729 var payload = JSON.stringify( { slug: cfg.slug } );
665 730
666 731 // Prefer `navigator.sendBeacon`: it is queued by the browser and
667 - // survives the navigation that "Enable it now" triggers without the
668 - // keepalive caveats, and it is inherently same-origin-credentialed.
669 - // The `wp_rest` nonce rides along as `_wpnonce` (REST cookie auth
670 - // reads it from `$_REQUEST`), and the Blob's `application/json` type
671 - // lets the REST server parse the `slug` body param.
732 + // survives the navigation that "Switch to OpenStation" triggers
733 + // without the keepalive caveats, and it is inherently
734 + // same-origin-credentialed. The `wp_rest` nonce rides along as
735 + // `_wpnonce` (REST cookie auth reads it from `$_REQUEST`), and the
736 + // Blob's `application/json` type lets the REST server parse the
737 + // `slug` body param.
672 738 try {
673 739 if ( navigator.sendBeacon ) {
674 740 var beaconUrl = url +
675 741 ( url.indexOf( '?' ) === -1 ? '?' : '&' ) +
@@ -681,9 +747,9 @@
681 747 }
682 748 } catch ( e ) {}
683 749
684 750 // Fallback: `keepalive: true` keeps the POST alive across the
685 - // "Enable it now" redirect on browsers without sendBeacon.
751 + // "Switch to OpenStation" redirect on browsers without sendBeacon.
686 752 try {
687 753 var headers = { 'Content-Type': 'application/json' };
688 754 if ( cfg.nonce ) {
689 755 headers[ 'X-WP-Nonce' ] = cfg.nonce;
@@ -720,9 +786,9 @@
720 786 // need to see the welcome again.
721 787 persist();
722 788
723 789 var form = new FormData();
724 - form.append( 'action', 'save-desktop-mode' );
790 + form.append( 'action', 'save-openstation' );
725 791 form.append( 'nonce', cfg.ajaxNonce );
726 792 form.append( 'enabled', '1' );
727 793
728 794 fetch( sameOrigin( cfg.ajaxUrl ), {
@@ -740,9 +806,9 @@
740 806 return;
741 807 }
742 808 // Fallback — reload so the shell takes over (the AJAX endpoint
743 809 // already wrote the user meta, so this request will boot
744 - // straight into Desktop Mode via the portal flow).
810 + // straight into OpenStation via the portal flow).
745 811 window.location.reload();
746 812 } ).catch( function () {
747 813 enabling = false;
748 814 btn.disabled = false;
@@ -754,16 +820,15 @@
754 820 var target = event.target;
755 821 if ( ! ( target instanceof Element ) ) {
756 822 return;
757 823 }
758 - var enableBtn = target.closest( '[data-desktop-mode-welcome-enable]' );
824 + var enableBtn = target.closest( '[data-os-welcome-enable]' );
759 825 if ( enableBtn ) {
760 826 event.preventDefault();
761 827 enableNow( enableBtn );
762 828 return;
763 829 }
764 - if ( target.closest( '[data-desktop-mode-welcome-dismiss]' ) ||
765 - target.closest( '[data-desktop-mode-welcome-cta]' ) ) {
830 + if ( target.closest( '[data-os-welcome-cta]' ) ) {
766 831 event.preventDefault();
767 832 dismiss();
768 833 return;
769 834 }
@@ -784,5 +849,5 @@
784 849 } )();
785 850 </script>
786 851 <?php
787 852 }
788 -add_action( 'admin_footer', 'desktop_mode_render_welcome_dialog' );
853 +add_action( 'admin_footer', 'openstation_render_welcome_dialog' );