diff --git a/e2e-tests/specs/onboarding.spec.js b/e2e-tests/specs/onboarding.spec.js
index d737ce93..81a3c35b 100644
--- a/e2e-tests/specs/onboarding.spec.js
+++ b/e2e-tests/specs/onboarding.spec.js
@@ -62,41 +62,6 @@ test.describe('Onboarding', () => {
await expect(firstListedSiteCard.locator('.ss-title')).not.toBeEmpty();
});
- test('Onboarding promo notice can be dismissed and stays hidden after reload', async ({ page, admin }) => {
- await admin.visitAdminPage(ONBOARDING_URL);
-
- const promoNotice = page.locator('.ob-onboarding-promo');
- await expect(promoNotice).toBeVisible();
-
- // The client sends the dismiss action via FormData, which fetch encodes as
- // multipart/form-data. Inspect the raw body bytes so the predicate works
- // regardless of whether the encoding is multipart or url-encoded.
- const isDismissCall = (request) =>
- request.url().includes('admin-ajax.php') &&
- request.method() === 'POST' &&
- (request.postDataBuffer()?.toString('utf8') ?? '').includes(
- 'dismiss_onboarding_promo_notice'
- );
-
- const dismissRequest = page.waitForRequest(isDismissCall);
- const dismissResponse = page.waitForResponse((response) =>
- isDismissCall(response.request())
- );
-
- await promoNotice.getByRole('button', { name: 'Dismiss notice' }).click();
-
- const request = await dismissRequest;
- const response = await dismissResponse;
- expect(request.postDataBuffer()?.toString('utf8')).toContain(
- 'dismiss_onboarding_promo_notice'
- );
- expect(response.ok()).toBeTruthy();
-
- await expect(promoNotice).toBeHidden();
- await page.reload();
- await expect(promoNotice).toBeHidden();
- });
-
test('Site Import Customization Rendering', async ({ page, admin }) => {
await admin.visitAdminPage(ONBOARDING_URL);
await openFirstSiteAndWaitForData( page );
diff --git a/includes/Admin.php b/includes/Admin.php
index 625c1a2d..e8cd0ac9 100755
--- a/includes/Admin.php
+++ b/includes/Admin.php
@@ -24,10 +24,9 @@ class Admin {
const IMPORTED_TEMPLATES_COUNT_OPT = 'tiob_premade_imported';
const FEEDBACK_DISMISSED_OPT = 'tiob_feedback_dismiss';
- const TC_REMOVED_KEY = 'tiob_tc_removed';
- const TC_NEW_NOTICE_DISMISSED = 'tiob_new_tc_notice_dismissed';
- const ONBOARDING_PROMO_NOTICE_DISMISSED = 'tiob_onboarding_promo_notice_dismissed';
- const VISITED_LIBRARY_OPT = 'tiob_library_visited';
+ const TC_REMOVED_KEY = 'tiob_tc_removed';
+ const TC_NEW_NOTICE_DISMISSED = 'tiob_new_tc_notice_dismissed';
+ const VISITED_LIBRARY_OPT = 'tiob_library_visited';
/**
* Admin page slug
@@ -89,7 +88,6 @@ public function init() {
add_action( 'wp_ajax_tpc_get_logs', array( $this, 'external_get_logs' ) );
add_action( 'wp_ajax_dismiss_new_tc_notice', array( $this, 'dismiss_new_tc_notice' ) );
- add_action( 'wp_ajax_dismiss_onboarding_promo_notice', array( $this, 'dismiss_onboarding_promo_notice' ) );
$this->register_feedback_settings();
@@ -163,52 +161,6 @@ public function dismiss_new_tc_notice() {
$this->ensure_ajax_response( $response );
}
- /**
- * Dismiss onboarding promo notice.
- *
- * @return void
- */
- public function dismiss_onboarding_promo_notice() {
- $response = array(
- 'success' => false,
- 'code' => 'ti__ob_not_allowed',
- 'message' => 'Not allowed!',
- );
-
- if ( ! isset( $_REQUEST['nonce'] ) ) {
- $this->ensure_ajax_response( $response );
- return;
- }
-
- $nonce = sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) );
-
- if ( ! wp_verify_nonce( $nonce, 'dismiss_onboarding_promo_notice' ) ) {
- $this->ensure_ajax_response( $response );
- return;
- }
-
- if ( ! current_user_can( 'install_plugins' ) ) {
- $this->ensure_ajax_response( $response );
- return;
- }
-
- $response['success'] = true;
- unset( $response['code'] );
- unset( $response['message'] );
-
- update_option( self::ONBOARDING_PROMO_NOTICE_DISMISSED, 'yes' );
- $this->ensure_ajax_response( $response );
- }
-
- /**
- * Decide if the onboarding promo notice should be shown.
- *
- * @return bool
- */
- private function should_show_onboarding_promo_notice() {
- return get_option( self::ONBOARDING_PROMO_NOTICE_DISMISSED, 'no' ) !== 'yes';
- }
-
/**
* Decide if the business/agency variant of the onboarding promo text should be shown.
*
@@ -937,11 +889,6 @@ private function get_localization() {
'ajaxURL' => esc_url( admin_url( 'admin-ajax.php' ) ),
'nonce' => wp_create_nonce( 'dismiss_new_tc_notice' ),
),
- 'onboardingPromoNotice' => array(
- 'show' => $this->should_show_onboarding_promo_notice(),
- 'ajaxURL' => esc_url( admin_url( 'admin-ajax.php' ) ),
- 'nonce' => wp_create_nonce( 'dismiss_onboarding_promo_notice' ),
- ),
'onboardingPluginCompatibility' => array(
'hyve-lite' => is_php_version_compatible( '8.1' ),
),
diff --git a/onboarding/src/Components/OnboardingPromoNotice.js b/onboarding/src/Components/OnboardingPromoNotice.js
deleted file mode 100644
index 9ddfee31..00000000
--- a/onboarding/src/Components/OnboardingPromoNotice.js
+++ /dev/null
@@ -1,96 +0,0 @@
-/* global tiobDash */
-import { __, sprintf } from '@wordpress/i18n';
-import { createInterpolateElement, useState } from '@wordpress/element';
-import { ajaxAction } from '../utils/rest';
-
-const OnboardingPromoNotice = () => {
- const shouldShowNotice = Boolean( tiobDash.onboardingPromoNotice?.show );
- const showProMessage = Boolean( tiobDash.onboardingShowProNoticeText );
-
- const emailBody = sprintf(
- /* translators: %s: double line break in the starter site request email template */
- __(
- 'Hi Neve team,%1$sI\'m looking for a starter site for the following project:%1$sProject type: (e.g. Restaurant, Law Firm, SaaS)%1$sKey pages needed: (e.g. Home, About, Services, Contact)%1$sStyle preference: (e.g. Minimal, Bold, Corporate)%1$sAny references: (optional)%1$sThanks',
- 'templates-patterns-collection'
- ),
- '\n\n'
- );
-
- const requestSiteLink =
- 'mailto:contact@themeisle.com?subject=' +
- encodeURIComponent(
- __( 'Starter Site Request', 'templates-patterns-collection' )
- ) +
- '&body=' +
- encodeURIComponent( emailBody );
-
- const noticeMessage = showProMessage
- ? createInterpolateElement(
- __(
- 'Fresh designs built for every niche. Can\'t find what you\'re looking for? As a Pro user, request a site and we\'ll build it for you.',
- 'templates-patterns-collection'
- ),
- {
- requestSiteLink: (
- /* eslint-disable-next-line jsx-a11y/anchor-has-content */
-
- ),
- }
- )
- : __(
- 'From free to pro, fresh designs built for every niche. More coming soon.',
- 'templates-patterns-collection'
- );
-
- const [ isVisible, setIsVisible ] = useState( shouldShowNotice );
-
- if ( ! isVisible ) {
- return null;
- }
-
- const dismissNotice = () => {
- setIsVisible( false );
- ajaxAction(
- tiobDash.onboardingPromoNotice.ajaxURL,
- 'dismiss_onboarding_promo_notice',
- tiobDash.onboardingPromoNotice.nonce
- ).catch( () => null );
- };
-
- return (
-
-
- { __( 'New', 'templates-patterns-collection' ) }
-
-
-
- { sprintf(
- /* translators: %s: number of new starter sites */
- __(
- '%s new starter sites, just landed.',
- 'templates-patterns-collection'
- ),
- '80+'
- ) }
-
-
{ noticeMessage }
-
-
-
- );
-};
-
-export default OnboardingPromoNotice;
diff --git a/onboarding/src/Components/Steps/SiteList.js b/onboarding/src/Components/Steps/SiteList.js
index 563b4827..f59700b6 100644
--- a/onboarding/src/Components/Steps/SiteList.js
+++ b/onboarding/src/Components/Steps/SiteList.js
@@ -11,7 +11,6 @@ import Toast from '../Toast';
import Filters from '../Filters';
import Sites from '../Sites';
import EditorSelector from '../EditorSelector';
-import OnboardingPromoNotice from '../OnboardingPromoNotice';
import SVG from '../../utils/svg';
import { get, track } from '../../utils/rest';
@@ -41,7 +40,7 @@ const SiteList = ( {
const toastMessage = createInterpolateElement(
__(
- 'Unlock Access to all premium templates with Neve Business plan. .',
+ 'Included with Neve Business. See plans',
'templates-patterns-collection'
),
{
@@ -49,10 +48,8 @@ const SiteList = ( {
- { __( 'Get Started', 'templates-patterns-collection' ) }
-
+ rel="noopener noreferrer"
+ />
),
}
);
@@ -211,13 +208,30 @@ const SiteList = ( {
-
- { __( 'Choose a design', 'templates-patterns-collection' ) }
-
+
+
+ { __(
+ 'Choose a design',
+ 'templates-patterns-collection'
+ ) }
+
+
+ { createInterpolateElement(
+ __(
+ 'Nearly 200 starter sites across every niche, with dozens added recently.',
+ 'templates-patterns-collection'
+ ),
+ {
+ count: (
+
+ ),
+ }
+ ) }
+
+
-
{ ( personalizing || searching ) && (
) }
diff --git a/onboarding/src/Components/Toast.js b/onboarding/src/Components/Toast.js
index e65be8f6..8e619675 100644
--- a/onboarding/src/Components/Toast.js
+++ b/onboarding/src/Components/Toast.js
@@ -1,18 +1,33 @@
import classnames from 'classnames';
+import { __ } from '@wordpress/i18n';
-const Toast = ( { svgIcon, message, className, setShowToast } ) => {
+const Toast = ( { svgIcon, heading, message, className, setShowToast } ) => {
const handleClose = () => {
setShowToast( 'dismissed' );
};
return (
-
+
+ { svgIcon && (
+
+ { svgIcon }
+
+ ) }
- { svgIcon &&
{ svgIcon }
}
- { message &&
{ message }
}
+ { heading &&
{ heading }
}
+ { message &&
{ message }
}
-
);
diff --git a/onboarding/src/scss/_general.scss b/onboarding/src/scss/_general.scss
index f3b1268a..0e5b0abc 100644
--- a/onboarding/src/scss/_general.scss
+++ b/onboarding/src/scss/_general.scss
@@ -135,6 +135,41 @@ iframe {
display: flex;
align-items: center;
justify-content: space-between;
+ gap: 16px;
+}
+
+.ob-title-text {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ h1 {
+ margin: 0;
+ }
+}
+
+.ob-subtitle {
+ margin: 6px 0 0;
+ max-width: 56ch;
+ // ~#6E6D6D on white ≈ 4.6:1 → passes WCAG AA while staying clearly secondary to the near-black h1.
+ color: mix($main-text, $inverted-text, 56%);
+ font-size: 14px;
+ line-height: 20px;
+ font-weight: 400;
+ letter-spacing: 0.1px;
+
+ // Subtle emphasis on the catalog claim — weight only, no brand color (would read promotional).
+ .ob-subtitle__count {
+ font-weight: 600;
+ color: $main-text;
+ }
+}
+
+@media (max-width: #{$tablet}) {
+ .ob-subtitle {
+ font-size: 13px;
+ line-height: 19px;
+ }
}
/**
diff --git a/onboarding/src/scss/_onboarding-promo-notice.scss b/onboarding/src/scss/_onboarding-promo-notice.scss
deleted file mode 100644
index ddc1ef2c..00000000
--- a/onboarding/src/scss/_onboarding-promo-notice.scss
+++ /dev/null
@@ -1,105 +0,0 @@
-.ob-onboarding-promo {
- display: flex;
- align-items: center;
- gap: 24px;
- background: $primary;
- border-radius: 16px;
- padding: 18px 24px;
- margin: 32px 0;
-}
-
-.ob-onboarding-promo-badge {
- background: rgba(255, 255, 255, 0.2);
- color: $inverted-text;
- border-radius: 10px;
- padding: 8px 14px;
- text-transform: uppercase;
- font-size: 14px;
- line-height: 20px;
- font-weight: 700;
- letter-spacing: 0.06em;
- flex-shrink: 0;
-}
-
-.ob-onboarding-promo-content {
- flex: 1;
-
- h3 {
- margin: 0 0 6px;
- color: $inverted-text;
- font-size: 20px;
- line-height: 30px;
- font-weight: 700;
- }
-
- p {
- margin: 0;
- color: rgba(255, 255, 255, 0.92);
- font-size: 15px;
- line-height: 24px;
- font-weight: 400;
- }
-
- .ob-onboarding-promo-link {
- color: $inverted-text;
- font-weight: 700;
- text-decoration: underline;
- }
-}
-
-.ob-onboarding-promo-close {
- border: 0;
- background: rgba(255, 255, 255, 0.2);
- color: $inverted-text;
- width: 40px;
- height: 40px;
- border-radius: 12px;
- font-size: 24px;
- line-height: 1;
- cursor: pointer;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
-}
-
-@media (max-width: #{$laptop}) {
- .ob-onboarding-promo {
- padding: 16px;
- gap: 16px;
- }
-
- .ob-onboarding-promo-badge {
- font-size: 12px;
- line-height: 18px;
- padding: 6px 10px;
- }
-
- .ob-onboarding-promo-content {
- h3 {
- font-size: 18px;
- line-height: 28px;
- }
-
- p {
- font-size: 14px;
- line-height: 22px;
- }
- }
-
- .ob-onboarding-promo-close {
- width: 36px;
- height: 36px;
- font-size: 20px;
- }
-}
-
-@media (max-width: #{$tablet}) {
- .ob-onboarding-promo {
- flex-wrap: wrap;
- }
-
- .ob-onboarding-promo-close {
- margin-left: auto;
- }
-}
diff --git a/onboarding/src/scss/_toast.scss b/onboarding/src/scss/_toast.scss
index 4e6c3418..4b481153 100644
--- a/onboarding/src/scss/_toast.scss
+++ b/onboarding/src/scss/_toast.scss
@@ -1,50 +1,118 @@
.ob-toast {
position: fixed;
- bottom: 49px;
- right: 18px;
- opacity: 0;
- transform: translateY(20px); /* Start 20px below the final position */
- transition: opacity 0.5s ease, transform 0.5s ease; /* Add transition */
- display: inline-flex;
- padding: 0 16px;
- align-items: center;
- gap: 16px;
- flex-shrink: 0;
+ bottom: 24px;
+ right: 24px;
+ z-index: 100;
+ display: flex;
+ align-items: flex-start;
+ gap: 10px;
+ max-width: 340px;
+ padding: 12px 14px;
border-radius: $button-radius;
border: 1px solid $border;
- background: $light-bg;
- box-shadow: 8px 8px 40px 0px rgba(0, 0, 0, 0.30);
- height: 70px;
- font-size: 16px;
- font-style: normal;
- font-weight: 400;
+ background: #fff;
+ box-shadow: 0 6px 24px rgba(0, 0, 0, 0.12);
+ opacity: 0;
+ transform: translateY(12px);
+ transition: opacity 0.35s ease, transform 0.35s ease;
+ pointer-events: none;
}
-.ob-toast-content {
- display: flex;
- flex: 1;
- align-items: center;
- gap: 16px;
- p {
- color: $main-text;
- font-size: 16px;
+.ob-toast.show {
+ opacity: 1;
+ transform: translateY(0);
+ pointer-events: auto;
+}
+
+.ob-toast-icon {
+ flex: 0 0 auto;
+ line-height: 0;
+ margin-top: 1px;
+
+ svg {
+ width: 24px;
+ height: 24px;
+ display: block;
}
+}
+
+.ob-toast-content {
+ flex: 1 1 auto;
+ min-width: 0;
+}
+
+.ob-toast-heading {
+ margin: 0;
+ color: $main-text;
+ font-size: 13px;
+ line-height: 18px;
+ font-weight: 600;
+}
+
+.ob-toast-message {
+ margin: 2px 0 0;
+ color: $secondary-text;
+ font-size: 13px;
+ line-height: 18px;
+ font-weight: 400;
+
a {
- text-decoration: none;
color: $primary;
- font-weight: 700;
+ font-weight: 600;
+ text-decoration: none;
+ white-space: nowrap;
+
+ &:hover,
+ &:focus-visible {
+ text-decoration: underline;
+ }
+
+ &:focus-visible {
+ outline: 2px solid $primary;
+ outline-offset: 2px;
+ border-radius: 2px;
+ }
}
}
.ob-toast-close {
+ flex: 0 0 auto;
+ margin: -4px -4px 0 2px;
+ padding: 4px;
background: none;
border: none;
- font-size: 20px;
+ line-height: 1;
+ font-size: 16px;
+ color: $secondary-text;
cursor: pointer;
+ border-radius: 4px;
+
+ &:hover {
+ color: $main-text;
+ }
+
+ &:focus-visible {
+ outline: 2px solid $primary;
+ outline-offset: 1px;
+ }
}
-/* Add a class to control the appearance */
-.ob-toast.show {
- opacity: 1; /* Show with full opacity */
- transform: translateY(0); /* Move to the final position */
+@media (prefers-reduced-motion: reduce) {
+ .ob-toast {
+ transition: opacity 0.2s ease;
+ transform: none;
+ }
+
+ .ob-toast.show {
+ transform: none;
+ }
+}
+
+@media (max-width: #{$tablet}) {
+ .ob-toast {
+ left: 16px;
+ right: 16px;
+ bottom: 16px;
+ max-width: none;
+ }
}
diff --git a/onboarding/src/style.scss b/onboarding/src/style.scss
index 9938a679..c5a758d8 100644
--- a/onboarding/src/style.scss
+++ b/onboarding/src/style.scss
@@ -4,7 +4,6 @@
@import "scss/category-buttons";
@import "scss/search";
@import "scss/filters";
-@import "scss/onboarding-promo-notice";
@import "scss/starter-site-card";
@import "scss/editor-selector";
@import "scss/site-settings";
diff --git a/onboarding/src/utils/svg.js b/onboarding/src/utils/svg.js
index 3985f31e..0fa1308b 100644
--- a/onboarding/src/utils/svg.js
+++ b/onboarding/src/utils/svg.js
@@ -4,6 +4,7 @@ const SVG = {
xmlns="http://www.w3.org/2000/svg"
width="40"
height="40"
+ viewBox="0 0 40 40"
fill="none"
>