get_required_capability(); $page_slug = $this->get_page_slug(); // 1. Primary Top-Level Admin Menu Item in the WordPress sidebar navigation. $this->page_hook = add_menu_page( esc_html__( 'AeroSpin CSS Loading Spinners', 'aerospin-css-loading-spinners' ), esc_html__( 'AeroSpin Spinners', 'aerospin-css-loading-spinners' ), $capability, $page_slug, array( $this, 'render_settings_page' ), 'dashicons-update', 65 ); // 2. Also register under Settings -> AeroSpin CSS Loading Spinners for users checking Settings. $this->settings_page_hook = add_options_page( esc_html__( 'AeroSpin CSS Loading Spinners', 'aerospin-css-loading-spinners' ), esc_html__( 'AeroSpin CSS Loading Spinners', 'aerospin-css-loading-spinners' ), $capability, $page_slug, array( $this, 'render_settings_page' ) ); } /** * Register a quick shortcut node in the top WordPress admin bar. * * @since 1.1.6 * * @param WP_Admin_Bar $wp_admin_bar The WordPress admin bar instance. * @return void */ public function register_admin_bar_node( $wp_admin_bar ) { if ( ! is_object( $wp_admin_bar ) || ! current_user_can( $this->get_required_capability() ) ) { return; } $settings_url = admin_url( 'admin.php?page=' . $this->get_page_slug() ); $wp_admin_bar->add_node( array( 'id' => 'aerospin-spinners', 'title' => '' . esc_html__( 'AeroSpin', 'aerospin-css-loading-spinners' ) . '', 'href' => esc_url( $settings_url ), 'meta' => array( 'title' => esc_html__( 'AeroSpin CSS Loading Spinners Settings', 'aerospin-css-loading-spinners' ), ), ) ); } /** * Enqueue admin assets for the AeroSpin CSS Loading Spinners page only. * * @since 1.0.0 * * @param string $hook_suffix Current admin page hook suffix. * @return void */ public function enqueue_assets( $hook_suffix ) { $page_slug = $this->get_page_slug(); $valid_hooks = array( $this->page_hook, $this->settings_page_hook, 'toplevel_page_' . $page_slug, 'settings_page_' . $page_slug, 'toplevel_page_acls-settings', 'settings_page_acls-settings', 'toplevel_page_aerospin-css-loading-spinners', 'settings_page_aerospin-css-loading-spinners', 'settings_page_pure-css-spinners', 'settings_page_clearpath-spinners', 'settings_page_cp-spinners', ); if ( ! in_array( $hook_suffix, array_filter( $valid_hooks ), true ) ) { return; } wp_enqueue_style( 'wp-color-picker' ); // Load public spinner styles so the admin preview uses the same animation classes. wp_enqueue_style( 'acls-public', $this->get_plugin_url( 'public/css/acls-spinners.css' ), array(), $this->get_plugin_version() ); wp_enqueue_style( 'acls-admin', $this->get_plugin_url( 'admin/css/acls-admin.css' ), array( 'wp-color-picker', 'acls-public' ), $this->get_plugin_version() ); wp_enqueue_script( 'acls-admin', $this->get_plugin_url( 'admin/js/acls-admin.js' ), array( 'jquery', 'wp-color-picker' ), $this->get_plugin_version(), true ); } /* |-------------------------------------------------------------------------- | SECTION 3: Page Rendering |-------------------------------------------------------------------------- */ /** * Render the settings page. * * @since 1.0.0 * * @return void */ public function render_settings_page() { if ( ! current_user_can( $this->get_required_capability() ) ) { wp_die( esc_html__( 'You do not have permission to access AeroSpin CSS Loading Spinners settings.', 'aerospin-css-loading-spinners' ), esc_html__( 'AeroSpin CSS Loading Spinners Permission Error', 'aerospin-css-loading-spinners' ), array( 'response' => 403, ) ); } $settings = $this->get_settings(); $spinners = $this->get_spinners(); $categories = $this->get_categories( $spinners ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tab navigation only; no form processing or data mutation occurs. $raw_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'settings'; $active_tab = 'settings'; if ( 'docs' === $raw_tab || 'documentation' === $raw_tab ) { $active_tab = 'documentation'; } elseif ( 'status' === $raw_tab || 'diagnostics' === $raw_tab ) { $active_tab = 'diagnostics'; } ?>
render_admin_data( $spinners ); ?> render_settings_message(); ?>

🚀 vget_plugin_version() ); ?>

render_nav_tabs( $active_tab ); ?>
render_general_card( $settings ); ?> render_spinner_card( $settings, $spinners, $categories ); ?> render_appearance_card( $settings ); ?> render_behavior_card( $settings ); ?> render_exclusions_card( $settings ); ?> render_custom_css_card( $settings ); ?>
render_documentation_tab(); ?>
render_diagnostics_tab( $settings, $spinners ); ?>
$this->get_script_spinners_map( $spinners ), 'i18n' => array( 'invalidSpinnerStyle' => esc_html__( 'Invalid spinner style selected.', 'aerospin-css-loading-spinners' ), 'selectedSpinner' => esc_html__( 'Selected spinner style', 'aerospin-css-loading-spinners' ), 'previewUpdated' => esc_html__( 'Preview updated.', 'aerospin-css-loading-spinners' ), 'rangeUpdated' => esc_html__( 'Value updated.', 'aerospin-css-loading-spinners' ), ), ); printf( '', esc_textarea( wp_json_encode( $data ) ) ); } /** * Render redirect-based settings messages. * * @since 1.1.4 * * @return void */ private function render_settings_message() { $message = get_transient( 'AeroSpin_CSS_Loading_Spinners_settings_messages' ); if ( false === $message ) { $message = get_transient( 'Pure_CSS_Spinners_settings_messages' ); } if ( ! is_array( $message ) || empty( $message['message'] ) ) { return; } delete_transient( 'AeroSpin_CSS_Loading_Spinners_settings_messages' ); delete_transient( 'Pure_CSS_Spinners_settings_messages' ); $type = isset( $message['type'] ) ? sanitize_key( $message['type'] ) : 'success'; if ( ! in_array( $type, array( 'success', 'info', 'warning', 'error' ), true ) ) { $type = 'info'; } printf( '

%2$s

', esc_attr( $type ), esc_html( $message['message'] ) ); } /** * Render the general settings card. * * @since 1.0.0 * * @param array $settings Normalized settings. * @return void */ private function render_general_card( $settings ) { $is_active = ! empty( $settings['enabled'] ); ?>

render_toggle_field( 'cps_enabled', esc_html__( 'Enable Loader', 'aerospin-css-loading-spinners' ), $is_active, esc_html__( 'Display the loading overlay for qualifying frontend navigation events.', 'aerospin-css-loading-spinners' ), esc_html__( 'When enabled, clicking standard internal links activates the transition screen. Anchor jumps (#), email/phone links, right clicks, downloads, and new tabs (target="_blank") are intelligently ignored.', 'aerospin-css-loading-spinners' ) ); ?>

$spinner ) : ?>

render_color_field( 'cps_spinner_color', esc_html__( 'Primary Color', 'aerospin-css-loading-spinners' ), $settings['spinner_color'], esc_html__( 'Main spinner animation color.', 'aerospin-css-loading-spinners' ), esc_html__( 'Used for primary lines, rings, dots, and foreground shapes. Recommended: Select your primary brand color with sufficient contrast against the overlay.', 'aerospin-css-loading-spinners' ) ); $this->render_color_field( 'cps_spinner_secondary', esc_html__( 'Secondary Color', 'aerospin-css-loading-spinners' ), $settings['spinner_secondary'], esc_html__( 'Secondary spinner accent color.', 'aerospin-css-loading-spinners' ), esc_html__( 'Utilized by dual-tone spinners (rings, pulse waves, track rings, and multi-dot loaders) for track backgrounds or contrasting segments.', 'aerospin-css-loading-spinners' ) ); $this->render_range_field( 'cps_spinner_size', esc_html__( 'Spinner Size', 'aerospin-css-loading-spinners' ), $settings['spinner_size'], 16, 240, 1, 'px', esc_html__( 'Size of the loader animation.', 'aerospin-css-loading-spinners' ), esc_html__( 'Controls the bounding size of the spinner icon. Recommended sweet spot: 40px–64px for desktop balance, and 32px–48px for compact mobile layouts.', 'aerospin-css-loading-spinners' ) ); $this->render_color_field( 'cps_overlay_color', esc_html__( 'Overlay Color', 'aerospin-css-loading-spinners' ), $settings['overlay_color'], esc_html__( 'Background color behind the spinner.', 'aerospin-css-loading-spinners' ), esc_html__( 'The full-screen canvas color that covers the viewport during navigation. Pure white (#ffffff) or dark obsidian (#0f172a / #1a202c) are common choices.', 'aerospin-css-loading-spinners' ) ); $this->render_range_field( 'cps_overlay_opacity', esc_html__( 'Overlay Opacity', 'aerospin-css-loading-spinners' ), $settings['overlay_opacity'], 0, 100, 1, '%', esc_html__( 'Opacity level for the loading overlay.', 'aerospin-css-loading-spinners' ), esc_html__( 'Recommended: 75%–90%. This gently masks the old page layout without feeling totally opaque, preserving spatial context for visitors.', 'aerospin-css-loading-spinners' ) ); $this->render_range_field( 'cps_overlay_blur', esc_html__( 'Overlay Blur', 'aerospin-css-loading-spinners' ), $settings['overlay_blur'], 0, 30, 1, 'px', esc_html__( 'Backdrop blur applied behind the overlay.', 'aerospin-css-loading-spinners' ), esc_html__( 'Applies a modern frosted-glass blur to elements behind the loading veil. Note: 0px offers maximum GPU performance; 4px–10px yields a refined glassmorphic aesthetic.', 'aerospin-css-loading-spinners' ) ); $this->render_number_field( 'cps_z_index', esc_html__( 'Z-Index', 'aerospin-css-loading-spinners' ), $settings['z_index'], 1, 2147483647, esc_html__( 'Stacking order for the frontend overlay.', 'aerospin-css-loading-spinners' ), esc_html__( 'Default is 99999. If your theme has floating chat widgets, sticky headers, or modals that appear above the loader, increase this number.', 'aerospin-css-loading-spinners' ) ); ?>

render_range_field( 'cps_fade_out_speed', esc_html__( 'Fade Out Speed', 'aerospin-css-loading-spinners' ), $settings['fade_out_speed'], 0, 5000, 50, 'ms', esc_html__( 'Time used when hiding the overlay after a page is ready.', 'aerospin-css-loading-spinners' ), esc_html__( 'Transition duration as the overlay vanishes. 250ms–350ms provides a natural fade. Setting to 0ms hides the spinner instantaneously.', 'aerospin-css-loading-spinners' ) ); $this->render_range_field( 'cps_min_display_time', esc_html__( 'Minimum Display Time', 'aerospin-css-loading-spinners' ), $settings['min_display_time'], 0, 10000, 50, 'ms', esc_html__( 'Minimum time the overlay should remain visible.', 'aerospin-css-loading-spinners' ), esc_html__( 'Prevents jarring micro-flashes on fast cache hits or rapid page loads. Setting this to 200ms–350ms ensures transitions look intentional without creating artificial wait times.', 'aerospin-css-loading-spinners' ) ); $this->render_toggle_field( 'cps_show_on_external', esc_html__( 'External Links', 'aerospin-css-loading-spinners' ), ! empty( $settings['show_on_external'] ), esc_html__( 'Show the loader when visitors follow external links.', 'aerospin-css-loading-spinners' ), esc_html__( 'When active, clicking a link pointing to an external domain displays the loader until the browser unloads to that destination.', 'aerospin-css-loading-spinners' ) ); $this->render_toggle_field( 'cps_show_on_admin_bar', esc_html__( 'Admin Bar Links', 'aerospin-css-loading-spinners' ), ! empty( $settings['show_on_admin_bar'] ), esc_html__( 'Allow the loader on frontend admin bar links.', 'aerospin-css-loading-spinners' ), esc_html__( 'Disabled by default so administrators can navigate directly into the WordPress admin toolbar without triggering frontend loading screens.', 'aerospin-css-loading-spinners' ) ); $this->render_toggle_field( 'cps_show_on_forms', esc_html__( 'Form Submissions', 'aerospin-css-loading-spinners' ), ! empty( $settings['show_on_forms'] ), esc_html__( 'Show the loader for qualifying frontend form submissions.', 'aerospin-css-loading-spinners' ), esc_html__( 'Activates the overlay when visitors submit standard POST forms. Forms targeting new windows or with excluded selectors are safely ignored.', 'aerospin-css-loading-spinners' ) ); ?>

render_textarea_field( 'cps_exclude_selectors', esc_html__( 'Exclude Link Selectors', 'aerospin-css-loading-spinners' ), $settings['exclude_selectors'], esc_html__( 'Enter one CSS selector per line for links or containers that should not trigger the loader.', 'aerospin-css-loading-spinners' ), esc_html__( 'Use this to bypass in-page AJAX tabs, modal buttons, popups, and file downloads. Matching links or links inside matching containers will not trigger the overlay.', 'aerospin-css-loading-spinners' ), $selector_examples ); $this->render_textarea_field( 'cps_exclude_pages', esc_html__( 'Exclude Paths', 'aerospin-css-loading-spinners' ), $settings['exclude_pages'], esc_html__( 'Enter one path or URL fragment per line where the loader should stay disabled.', 'aerospin-css-loading-spinners' ), esc_html__( 'Any page matching one of these URL fragments will completely suppress the loading overlay on both page entry and page departure.', 'aerospin-css-loading-spinners' ), $path_examples ); ?>

Customize -> Additional CSS).', 'aerospin-css-loading-spinners' ); ?>

⚡
🧩

.add_to_cart_button .single_add_to_cart_button .woocommerce-cart-link .wc-block-components-checkout-place-order-button

.elementor-clickable .dialog-close-button .et_pb_button .fl-button

.gform_button .wpcf7-submit .fancybox-active .lightbox-trigger
🛠️

#acls-overlay (legacy: #cps-overlay) DOM ID
.acls-spinner (legacy: .cps-spinner) CSS Class
--acls-color (legacy: --cps-color) CSS Var
--acls-secondary (legacy: --cps-secondary) CSS Var
acls:shown (legacy: aerospin_css_loading_spinners:shown) JS Event
aerospin_css_loading_spinners:hidden (legacy: pure_css_spinners:hidden) JS Event
window.AeroSpin_CSS_Loading_Spinners.hide() (legacy: window.Pure_CSS_Spinners.hide()) JS API
❓

get_spinner_html( $spinner ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Sanitized by get_spinner_html(). ?>
px % px

array( 'icon' => '⚙', 'label' => esc_html__( 'Settings', 'aerospin-css-loading-spinners' ), 'title' => esc_attr__( 'Configure loading spinners, appearance, and exclusions', 'aerospin-css-loading-spinners' ), ), 'documentation' => array( 'icon' => '📚', 'label' => esc_html__( 'Documentation', 'aerospin-css-loading-spinners' ), 'title' => esc_attr__( 'Quick start guide, plugin recipes, developer hooks, and FAQ', 'aerospin-css-loading-spinners' ), ), 'diagnostics' => array( 'icon' => '🩺', 'label' => esc_html__( 'Diagnostics', 'aerospin-css-loading-spinners' ), 'title' => esc_attr__( 'Server environment, active configuration, and cache health', 'aerospin-css-loading-spinners' ), ), ); ?> render_guide_card(); $this->render_testing_checklist_card(); } /** * Render the Testing Checklist card. * * @since 1.1.6 * * @return void */ private function render_testing_checklist_card() { ?>

render_system_info_card(); $this->render_active_config_card( $settings, $spinners ); $this->render_transients_card(); } /** * Render server and WordPress environment diagnostics. * * @since 1.1.6 * * @return void */ private function render_system_info_card() { global $wp_version; $server_software = isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : 'N/A'; $php_memory = ini_get( 'memory_limit' ) ? ini_get( 'memory_limit' ) : 'N/A'; $max_execution = ini_get( 'max_execution_time' ) ? ini_get( 'max_execution_time' ) . 's' : 'N/A'; $ob_level = ob_get_level(); $debug_mode = defined( 'WP_DEBUG' ) && WP_DEBUG ? esc_html__( 'Enabled', 'aerospin-css-loading-spinners' ) : esc_html__( 'Disabled', 'aerospin-css-loading-spinners' ); $textdomain_stat = is_textdomain_loaded( 'aerospin-css-loading-spinners' ) ? esc_html__( 'Loaded', 'aerospin-css-loading-spinners' ) : esc_html__( 'Default', 'aerospin-css-loading-spinners' ); ?>

()
()
aerospin-css-loading-spinners ()

()
px
 / 
@ % (Blur: px)
Min Display: ms | Fade-Out: ms
selectors | URL paths

aerospin_css_loading_spinners_settings_cache (legacy: Pure_CSS_Spinners_settings_cache)
aerospin_css_loading_spinners_spinner_categories (legacy: Pure_CSS_Spinners_spinner_categories)

$spinner ) { $key = sanitize_key( $key ); if ( '' === $key ) { continue; } $map[ $key ] = array( 'label' => isset( $spinner['label'] ) ? wp_strip_all_tags( $spinner['label'] ) : $key, 'description' => isset( $spinner['description'] ) ? wp_strip_all_tags( $spinner['description'] ) : '', 'category' => isset( $spinner['category'] ) ? wp_strip_all_tags( $spinner['category'] ) : '', 'html' => $this->get_spinner_html( $spinner ), ); } return $map; } /** * Return sanitized spinner HTML. * * @since 1.0.0 * * @param array $spinner Spinner definition. * @return string */ private function get_spinner_html( $spinner ) { $html = isset( $spinner['html'] ) ? $spinner['html'] : ''; if ( function_exists( 'aerospin_css_loading_spinners_kses_spinner_html' ) ) { return aerospin_css_loading_spinners_kses_spinner_html( $html ); } elseif ( function_exists( 'Pure_CSS_Spinners_kses_spinner_html' ) ) { return Pure_CSS_Spinners_kses_spinner_html( $html ); } elseif ( function_exists( 'cp_spinners_kses_spinner_html' ) ) { return cp_spinners_kses_spinner_html( $html ); } return wp_kses_post( $html ); } /** * Get the required capability. * * @since 1.0.0 * * @return string */ private function get_required_capability() { $capability = 'manage_options'; if ( defined( 'AEROSPIN_CSS_LOADING_SPINNERS_REQUIRED_CAPABILITY' ) ) { $capability = AEROSPIN_CSS_LOADING_SPINNERS_REQUIRED_CAPABILITY; } elseif ( defined( 'PURE_CSS_SPINNERS_REQUIRED_CAPABILITY' ) ) { $capability = PURE_CSS_SPINNERS_REQUIRED_CAPABILITY; } /** * Filter the capability required to access AeroSpin CSS Loading Spinners admin settings. * * @since 1.1.6 * * @param string $capability Required capability. Default 'manage_options'. */ return apply_filters( 'aerospin_css_loading_spinners_required_capability', $capability ); } /** * Get the settings page slug. * * @since 1.0.0 * * @return string */ private function get_page_slug() { $page_slug = defined( 'ACLS_SETTINGS_PAGE_SLUG' ) ? ACLS_SETTINGS_PAGE_SLUG : 'aerospin-css-loading-spinners'; $page_slug = sanitize_key( $page_slug ); return '' !== $page_slug ? $page_slug : 'aerospin-css-loading-spinners'; } /** * Get the plugin version. * * @since 1.0.0 * * @return string */ private function get_plugin_version() { if ( defined( 'AEROSPIN_CSS_LOADING_SPINNERS_VERSION' ) ) { return AEROSPIN_CSS_LOADING_SPINNERS_VERSION; } return '1.1.6'; } /** * Build a plugin asset URL. * * @since 1.0.0 * * @param string $relative_path Relative asset path. * @return string */ private function get_plugin_url( $relative_path ) { $relative_path = ltrim( (string) $relative_path, '/\\' ); if ( defined( 'ACLS_PLUGIN_URL' ) ) { $plugin_url = ACLS_PLUGIN_URL; } elseif ( defined( 'AEROSPIN_CSS_LOADING_SPINNERS_PLUGIN_URL' ) ) { $plugin_url = AEROSPIN_CSS_LOADING_SPINNERS_PLUGIN_URL; } else { $plugin_url = plugin_dir_url( dirname( __DIR__ ) ); } return $plugin_url . $relative_path; } /** * Get the brand website URL. * * @since 1.1.6 * * @return string */ private function get_brand_url() { if ( defined( 'ACLS_BRAND_URL' ) ) { return ACLS_BRAND_URL; } elseif ( defined( 'AEROSPIN_CSS_LOADING_SPINNERS_BRAND_URL' ) ) { return AEROSPIN_CSS_LOADING_SPINNERS_BRAND_URL; } return defined( 'AEROSPIN_CSS_LOADING_SPINNERS_SUPPORT_URL' ) ? AEROSPIN_CSS_LOADING_SPINNERS_SUPPORT_URL : 'https://wpclearpath.com'; } /** * Get the support URL (backward compatibility). * * @since 1.0.0 * * @return string */ private function get_support_url() { return $this->get_brand_url(); } } } if ( ! class_exists( 'AeroSpin_CSS_Loading_Spinners_Admin' ) ) { class_alias( 'ACLS_Admin', 'AeroSpin_CSS_Loading_Spinners_Admin' ); } if ( ! class_exists( 'Pure_CSS_Spinners_Admin' ) ) { class_alias( 'ACLS_Admin', 'Pure_CSS_Spinners_Admin' ); } if ( ! class_exists( 'AreoSpin_Css_Loading_Spinners_Admin' ) ) { class_alias( 'ACLS_Admin', 'AreoSpin_Css_Loading_Spinners_Admin' ); } /* |-------------------------------------------------------------------------- | SECTION 6: Initialize Admin Controller |-------------------------------------------------------------------------- */ // Initialize the admin controller so WordPress can register the settings page and admin bar. ACLS_Admin::get_instance();