<?php
/**
 * Login Page Customizor Module.
 * @package LoginPageCustomizor
 */

namespace LoginPageCustomizor;

use WP_PowerSuite\Core\AbstractModule;
use WP_PowerSuite\Core\OptionStoreModuleSettingsRepository;

class Module extends AbstractModule {

	private const MODULE_ID = 'login-page-customizor';
	private const MODULE_VERSION = '1.0.0';
	public const PREVIEW_QUERY_ARG = 'wpps_lpc_preview';

private bool $login_hooks_registered = false;

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Login Page Customizer', 'wp-powersuite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'Brand the login page with colors, background, and layout options.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return [ 'admin', 'ajax', 'login' ];
	}

	public function getDependencies(): array {
		return [];
	}

	private function isLicenseActive(): bool {
		return function_exists( 'wp_powersuite_is_license_valid' ) && wp_powersuite_is_license_valid();
	}

		protected function registerHooks(): void {
		if ( ! $this->isLicenseActive() ) {
			return;
		}

		add_action( 'wp_powersuite_module_toggled', [ self::class, 'handleModuleToggled' ], 10, 3 );
		add_action( 'wp_ajax_wpps_restore_lpc_settings', [ $this, 'handleRestoreLpcSettings' ] );

		if ( is_admin() ) {
			add_action( $this->adminMenuHook(), [ $this, 'addAdminMenu' ], 20 );
			add_action( 'admin_enqueue_scripts', [ $this, 'enqueueAdminAssets' ] );

			return;
		}

		// Login screen only (wp-login.php or Change Login URL after bootstrap).
		add_action( 'login_init', [ $this, 'registerLoginCustomizationHooks' ], 0 );
		add_action( 'wpps_before_custom_login_render', [ $this, 'registerLoginCustomizationHooks' ], 0 );
	}

	/**
	 * Register the Login Page Customizer admin screen under PowerSuite.
	 */
	public function addAdminMenu(): void {
		$this->registerPowerSuiteSubmenu(
			__( 'Login Page Customizer', 'wp-powersuite' ),
			__( 'Login Customizer', 'wp-powersuite' ),
			'powersuite-login-page-customizor',
			[ $this, 'renderAdminPage' ]
		);
	}

	/**
	 * Render the full-page settings UI.
	 */
	public function renderAdminPage(): void {
		if ( ! $this->canManagePlugin() ) {
			wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'wp-powersuite' ) );
		}

		$is_full_page = true;
		$module_id    = self::MODULE_ID;
		include __DIR__ . '/admin.php';
	}

	/**
	 * Enqueue editor assets required by the settings screen (TinyMCE for split column content).
	 *
	 * @param string $hook_suffix Current admin screen hook.
	 */
	public function enqueueAdminAssets( string $hook_suffix ): void {
		if ( $hook_suffix !== 'wp-powersuite_page_powersuite-login-page-customizor' ) {
			return;
		}

		wp_enqueue_editor();
		wp_enqueue_style(
			'wpps-login-page-customizor-admin',
			$this->getAssetUrl( 'admin.css' ),
			[ 'wp-powersuite-admin' ],
			self::MODULE_VERSION
		);
		$presets_file = __DIR__ . '/layout-presets.php';
		if ( is_readable( $presets_file ) ) {
			require_once __DIR__ . '/admin-helpers.php';
			require_once $presets_file;
			$presets_js = $this->getAssetUrl( 'layout-presets.js' );
			wp_enqueue_script(
				'wpps-lpc-layout-presets',
				$presets_js,
				[],
				self::MODULE_VERSION,
				true
			);
			wp_localize_script(
				'wpps-lpc-layout-presets',
				'wppsLpcLayoutPresets',
				LayoutPresets::getPresetsForJs()
			);
			wp_add_inline_script(
				'wpps-lpc-layout-presets',
				'window.wppsLpcModuleId = ' . wp_json_encode( self::MODULE_ID ) . ';',
				'before'
			);
		}

		$actions_js = $this->getAssetUrl( 'lpc-admin-actions.js' );
		wp_enqueue_script(
			'wpps-lpc-admin-actions',
			$actions_js,
			[ 'wp-powersuite-admin' ],
			self::MODULE_VERSION,
			true
		);
		wp_localize_script(
			'wpps-lpc-admin-actions',
			'wppsLpcAdmin',
			[
				'strings' => [
					'confirmRestore' => __( 'Are you sure you want to restore default settings? This will remove all customizations.', 'wp-powersuite' ),
					'restoring'      => __( 'Restoring...', 'wp-powersuite' ),
					'restored'       => __( 'Settings restored to defaults successfully!', 'wp-powersuite' ),
					'error'          => __( 'Could not restore settings. Please try again.', 'wp-powersuite' ),
				],
			]
		);
	}

	/**
	 * Settings used when restoring to the PowerSuite Default layout preset.
	 *
	 * @return array<string, mixed>
	 */
	public static function getRestoreDefaultSettings(): array {
		$presets_file = __DIR__ . '/layout-presets.php';
		if ( is_readable( $presets_file ) ) {
			require_once $presets_file;
			$preset_settings = LayoutPresets::getPresetSettings( 'default' );
			if ( is_array( $preset_settings ) ) {
				return $preset_settings;
			}
		}

		return self::getDefaultSettings();
	}

	/**
	 * Restore Login Page Customizer settings to the PowerSuite Default preset.
	 */
	public function handleRestoreLpcSettings(): void {
		check_ajax_referer( 'wp_powersuite_ajax', 'nonce' );

		if ( ! $this->isLicenseActive() ) {
			wp_send_json_error( [ 'message' => __( 'Login Page Customizer requires an active license.', 'wp-powersuite' ) ] );
		}

		if ( ! $this->canManagePlugin() ) {
			wp_send_json_error( [ 'message' => __( 'You do not have permission to restore these settings.', 'wp-powersuite' ) ] );
		}

		$saved = OptionStoreModuleSettingsRepository::forModule( self::MODULE_ID )->save( self::getRestoreDefaultSettings() );
		if ( ! $saved ) {
			wp_send_json_error( [ 'message' => __( 'Could not restore settings. Please try again.', 'wp-powersuite' ) ] );
		}

		wp_send_json_success( [ 'message' => __( 'Settings restored to defaults successfully!', 'wp-powersuite' ) ] );
	}

	/**
	 * @param string $file Filename under assets/.
	 */
	private function getAssetUrl( string $file ): string {
		return plugins_url( 'assets/' . $file, __FILE__ );
	}

	/**
	 * Preview URL that shows the login screen while the viewer is logged in as an admin.
	 */
	public static function getPreviewUrl(): string {
		return add_query_arg(
			[
				self::PREVIEW_QUERY_ARG => '1',
				'action'                => 'login',
			],
			wp_login_url()
		);
	}

	/**
	 * Whether the current request is an administrator preview of the login page.
	 */
	public function isLoginPreviewRequest(): bool {
		if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) {
			return false;
		}

		$raw = isset( $_GET[ self::PREVIEW_QUERY_ARG ] ) ? wp_unslash( $_GET[ self::PREVIEW_QUERY_ARG ] ) : '';
		if ( is_array( $raw ) ) {
			return false;
		}

		return (string) $raw === '1';
	}


	/**
	 * Register login screen hooks once per request (standard or custom login URL).
	 */
	public function registerLoginCustomizationHooks(): void {
		if ( $this->login_hooks_registered ) {
			return;
		}
		$this->login_hooks_registered = true;

		if ( $this->isLoginPreviewRequest() ) {
			add_action( 'login_header', [ $this, 'outputLoginPreviewBanner' ], 0 );
			add_action( 'login_enqueue_scripts', [ $this, 'enqueueLoginPreviewBannerStyles' ], 5 );
			add_filter( 'login_body_class', [ $this, 'addLoginPreviewBodyClass' ], 20, 2 );
		}

		$settings  = $this->getSettings();
		$raw_type  = $settings['template_type'] ?? 'classic-centered';
		$template  = in_array( $raw_type, [ 'classic-centered', 'split-layout' ], true ) ? $raw_type : 'classic-centered';

		if ( $template === 'split-layout' ) {
			add_filter( 'login_body_class', [ $this, 'addSplitLayoutBodyClass' ], 10, 2 );
			add_action( 'login_header', [ $this, 'outputSplitLayoutLeftColumnOpen' ], 1 );
			add_action( 'login_footer', [ $this, 'outputSplitLayoutColumnsClose' ], 1 );
			add_action( 'login_enqueue_scripts', [ $this, 'enqueueSplitLayoutStyles' ] );
			$split_form_bg  = isset( $settings['split_form_bg_type'] ) ? (string) $settings['split_form_bg_type'] : '';
			$split_empty_bg = isset( $settings['split_empty_bg_type'] ) ? (string) $settings['split_empty_bg_type'] : '';
			if ( $split_form_bg === 'video' || $split_empty_bg === 'video' ) {
				add_action( 'login_footer', [ $this, 'outputSplitLayoutVideoPlayScript' ], 5 );
			}
		} else {
			add_action( 'login_enqueue_scripts', [ $this, 'enqueueClassicLayoutFormAlignmentStyles' ], 12 );
			$classic_bg_type = isset( $settings['classic_bg_type'] ) ? (string) $settings['classic_bg_type'] : '';
			if ( in_array( $classic_bg_type, [ 'color', 'gradient', 'image', 'video' ], true ) ) {
				add_action( 'login_enqueue_scripts', [ $this, 'enqueueClassicLayoutBackgroundStyles' ], 11 );
				if ( $classic_bg_type === 'video' ) {
					add_filter( 'login_body_class', [ $this, 'addClassicVideoBodyClass' ], 10, 2 );
					add_action( 'login_header', [ $this, 'outputClassicVideoBackground' ], 0 );
					add_action( 'login_footer', [ $this, 'outputClassicVideoPlayScript' ], 5 );
				}
			}
		}

		if ( ! empty( $settings['hide_back_link'] ) || ! empty( $settings['hide_lost_password'] ) ) {
			add_action( 'login_enqueue_scripts', [ $this, 'outputHideBackAndLostPasswordStyles' ], 10 );
		}

		if ( ! empty( $settings['hide_remember_me'] ) && $settings['hide_remember_me'] !== '0' ) {
			add_action( 'login_enqueue_scripts', [ $this, 'outputHideRememberMeStyles' ], 10 );
		}

		if ( ! empty( $settings['hide_language_switcher'] ) && $settings['hide_language_switcher'] !== '0' ) {
			add_filter( 'login_display_language_dropdown', '__return_false' );
		}

		if ( isset( $settings['custom_css'] ) && trim( (string) $settings['custom_css'] ) !== '' ) {
			add_action( 'login_enqueue_scripts', [ $this, 'outputCustomCss' ], 30 );
		}

		$hide_logo = ! empty( $settings['hide_logo'] ) && $settings['hide_logo'] !== '0';
		if ( $this->hasLogoCustomization( $settings ) || ! $hide_logo ) {
			add_action( 'login_enqueue_scripts', [ $this, 'applyFormCustomizationLogoStyles' ], 20 );
		}
		if ( $this->hasLogoCustomization( $settings ) ) {
			add_filter( 'login_headerurl', [ $this, 'filterLoginLogoUrl' ], 10, 1 );
			add_filter( 'login_headertext', [ $this, 'filterLoginLogoText' ], 10, 1 );
			if ( $this->resolveLogoUrl( $settings ) !== '' && ( $settings['logo_source'] ?? 'upload' ) !== 'site_icon' ) {
				add_action( 'login_footer', [ $this, 'injectLoginLogoImg' ], 5 );
			}
		}

		if ( $this->hasFieldCustomization( $settings ) || $this->hasButtonCustomization( $settings ) ) {
			add_action( 'login_enqueue_scripts', [ $this, 'applyFormCustomizationEyeFocusFix' ], 19 );
		}
		if ( $this->hasBoxOrFormCustomization( $settings ) ) {
			add_action( 'login_enqueue_scripts', [ $this, 'applyFormCustomizationBoxStyles' ], 21 );
		}
		if ( $this->hasFieldCustomization( $settings ) ) {
			add_action( 'login_enqueue_scripts', [ $this, 'applyFormCustomizationFieldStyles' ], 22 );
		}
		if ( $this->hasButtonCustomization( $settings ) ) {
			add_action( 'login_enqueue_scripts', [ $this, 'applyFormCustomizationButtonStyles' ], 23 );
		}
		if ( $this->hasFooterCustomization( $settings ) ) {
			add_action( 'login_enqueue_scripts', [ $this, 'applyFormCustomizationFooterStyles' ], 24 );
		}
		if ( $this->hasLabelCustomization( $settings ) ) {
			add_action( 'login_enqueue_scripts', [ $this, 'applyFormCustomizationLabelStyles' ], 25 );
		}
		add_action( 'login_enqueue_scripts', [ $this, 'enqueueResponsiveLoginStyles' ], 26 );
	}

	/**
	 * Resolve the login logo URL from source settings.
	 *
	 * @param array<string, mixed> $settings Module settings.
	 */
	private function resolveLogoUrl( array $settings ): string {
		$source = isset( $settings['logo_source'] ) ? (string) $settings['logo_source'] : 'upload';
		if ( ! in_array( $source, [ 'upload', 'site_icon', 'url' ], true ) ) {
			$source = 'upload';
		}

		if ( $source === 'site_icon' ) {
			$icon = get_site_icon_url( 512 );
			return is_string( $icon ) && $icon !== '' ? esc_url( $icon ) : '';
		}

		if ( $source === 'url' ) {
			$url = isset( $settings['logo_external_url'] ) ? trim( (string) $settings['logo_external_url'] ) : '';
			return $url !== '' ? esc_url( $url ) : '';
		}

		$url = isset( $settings['logo_url'] ) ? trim( (string) $settings['logo_url'] ) : '';
		return $url !== '' ? esc_url( $url ) : '';
	}

	/**
	 * Parse an optional configured logo width (px).
	 *
	 * @param array<string, mixed> $settings Module settings.
	 */
	private function parseConfiguredLogoWidth( array $settings, string $key ): ?int {
		if ( ! isset( $settings[ $key ] ) || ! is_numeric( $settings[ $key ] ) || (int) $settings[ $key ] <= 0 ) {
			return null;
		}

		return max( 50, min( 500, (int) $settings[ $key ] ) );
	}

	/**
	 * Natural logo image width when known (attachment, site icon, or default WP logo).
	 *
	 * @param array<string, mixed> $settings Module settings.
	 */
	private function getLogoIntrinsicWidth( array $settings ): ?int {
		$source = isset( $settings['logo_source'] ) ? (string) $settings['logo_source'] : 'upload';
		if ( ! in_array( $source, [ 'upload', 'site_icon', 'url' ], true ) ) {
			$source = 'upload';
		}

		if ( $source === 'site_icon' ) {
			$icon_id = (int) get_option( 'site_icon' );
			if ( $icon_id > 0 ) {
				$meta = wp_get_attachment_metadata( $icon_id );
				if ( is_array( $meta ) && ! empty( $meta['width'] ) ) {
					return (int) $meta['width'];
				}
			}
			return null;
		}

		$url = $this->resolveLogoUrl( $settings );
		if ( $url === '' ) {
			return 84;
		}

		$attachment_id = attachment_url_to_postid( $url );
		if ( $attachment_id > 0 ) {
			$meta = wp_get_attachment_metadata( $attachment_id );
			if ( is_array( $meta ) && ! empty( $meta['width'] ) ) {
				return (int) $meta['width'];
			}
			$src = wp_get_attachment_image_src( $attachment_id, 'full' );
			if ( is_array( $src ) && ! empty( $src[1] ) ) {
				return (int) $src[1];
			}
		}

		return null;
	}

	/**
	 * Default desktop logo width: 200px, or less when the image is narrower.
	 *
	 * @param array<string, mixed> $settings Module settings.
	 */
	private function getDefaultLogoWidthPx( array $settings, int $max = 200 ): int {
		$intrinsic = $this->getLogoIntrinsicWidth( $settings );
		if ( $intrinsic !== null && $intrinsic > 0 && $intrinsic < $max ) {
			return max( 50, $intrinsic );
		}

		return $max;
	}

	/** @param array<string, mixed> $settings */
	private function hasLogoCustomization( array $settings ): bool {
		if ( ! empty( $settings['hide_logo'] ) && $settings['hide_logo'] !== '0' ) {
			return true;
		}
		if ( $this->resolveLogoUrl( $settings ) !== '' ) {
			return true;
		}
		$align = isset( $settings['logo_alignment'] ) ? (string) $settings['logo_alignment'] : 'center';
		if ( ! in_array( $align, [ 'left', 'center', 'right' ], true ) ) {
			$align = 'center';
		}
		if ( $align !== 'center' ) {
			return true;
		}
		foreach ( [ 'logo_width_desktop', 'logo_width_tablet', 'logo_width_mobile' ] as $key ) {
			if ( isset( $settings[ $key ] ) && is_numeric( $settings[ $key ] ) && (int) $settings[ $key ] > 0 ) {
				return true;
			}
		}
		if ( isset( $settings['logo_link'] ) && trim( (string) $settings['logo_link'] ) !== '' ) {
			return true;
		}
		if ( isset( $settings['logo_margin_top'] ) && trim( (string) $settings['logo_margin_top'] ) !== '' && is_numeric( trim( (string) $settings['logo_margin_top'] ) ) ) {
			return true;
		}
		if ( isset( $settings['logo_margin_bottom'] ) && trim( (string) $settings['logo_margin_bottom'] ) !== '' && is_numeric( trim( (string) $settings['logo_margin_bottom'] ) ) ) {
			return true;
		}
		return false;
	}

	/** @param array<string, mixed> $settings */
	private function hasBoxOrFormCustomization( array $settings ): bool {
		if ( isset( $settings['box_form_width'] ) && trim( (string) $settings['box_form_width'] ) !== '' && (int) $settings['box_form_width'] > 0 ) {
			return true;
		}
		if ( isset( $settings['form_width'] ) && trim( (string) $settings['form_width'] ) !== '' && (int) $settings['form_width'] > 0 ) {
			return true;
		}
		$pad_keys = [
			'box_padding_desktop', 'box_padding_tablet', 'box_padding_mobile', 'box_padding',
			'box_padding_top', 'box_padding_right', 'box_padding_bottom', 'box_padding_left',
			'box_padding_top_tablet', 'box_padding_right_tablet', 'box_padding_bottom_tablet', 'box_padding_left_tablet',
			'box_padding_top_mobile', 'box_padding_right_mobile', 'box_padding_bottom_mobile', 'box_padding_left_mobile',
		];
		foreach ( $pad_keys as $key ) {
			if ( isset( $settings[ $key ] ) && trim( (string) $settings[ $key ] ) !== '' && (int) $settings[ $key ] >= 0 ) {
				return true;
			}
		}
		$bt = isset( $settings['box_border_type'] ) ? (string) $settings['box_border_type'] : 'none';
		if ( $bt !== '' && $bt !== 'none' ) {
			return true;
		}
		if ( isset( $settings['box_shadow_x'] ) && (int) $settings['box_shadow_x'] !== 0 ) { return true; }
		if ( isset( $settings['box_shadow_y'] ) && (int) $settings['box_shadow_y'] !== 0 ) { return true; }
		if ( isset( $settings['box_shadow_blur'] ) && (int) $settings['box_shadow_blur'] !== 0 ) { return true; }
		if ( isset( $settings['box_shadow_spread'] ) && (int) $settings['box_shadow_spread'] !== 0 ) { return true; }
		$bg = isset( $settings['box_bg_type'] ) ? (string) $settings['box_bg_type'] : '';
		if ( $bg === 'color' || $bg === 'image' || $bg === 'gradient' ) {
			return true;
		}
		$form_pad = [
			'form_padding_desktop', 'form_padding_tablet', 'form_padding_mobile',
			'form_padding_top', 'form_padding_right', 'form_padding_bottom', 'form_padding_left',
			'form_padding_top_tablet', 'form_padding_right_tablet', 'form_padding_bottom_tablet', 'form_padding_left_tablet',
			'form_padding_top_mobile', 'form_padding_right_mobile', 'form_padding_bottom_mobile', 'form_padding_left_mobile',
		];
		foreach ( $form_pad as $key ) {
			if ( isset( $settings[ $key ] ) && trim( (string) $settings[ $key ] ) !== '' && (int) $settings[ $key ] >= 0 ) {
				return true;
			}
		}
		if ( array_key_exists( 'form_border_type', $settings ) ) {
			return true;
		}
		$fbg = isset( $settings['form_bg_type'] ) ? (string) $settings['form_bg_type'] : '';
		if ( $fbg === 'color' || $fbg === 'image' || $fbg === 'gradient' ) {
			return true;
		}
		if ( isset( $settings['form_shadow_x'] ) && (int) $settings['form_shadow_x'] !== 0 ) { return true; }
		if ( isset( $settings['form_shadow_y'] ) && (int) $settings['form_shadow_y'] !== 0 ) { return true; }
		if ( isset( $settings['form_shadow_blur'] ) && (int) $settings['form_shadow_blur'] !== 0 ) { return true; }
		if ( isset( $settings['form_shadow_spread'] ) && (int) $settings['form_shadow_spread'] !== 0 ) { return true; }
		return false;
	}

	/** @param array<string, mixed> $settings */
	private function hasFieldCustomization( array $settings ): bool {
		$str_keys = [
			'field_font_family', 'field_font_size_desktop', 'field_font_size', 'field_font_size_tablet', 'field_font_size_mobile',
			'field_font_weight', 'field_line_height_desktop', 'field_line_height', 'field_line_height_tablet', 'field_line_height_mobile',
			'field_letter_spacing_desktop', 'field_letter_spacing', 'field_letter_spacing_tablet', 'field_letter_spacing_mobile',
			'field_text_transform', 'field_font_style', 'field_normal_text_color', 'field_normal_bg_color', 'field_normal_bg_overlay_opacity', 'field_normal_border_width',
			'field_normal_border_radius', 'field_normal_border_radius_desktop', 'field_normal_border_radius_tablet', 'field_normal_border_radius_mobile', 'field_normal_padding_top', 'field_normal_padding_right', 'field_normal_padding_bottom', 'field_normal_padding_left', 'field_normal_padding_top_tablet', 'field_normal_padding_right_tablet', 'field_normal_padding_bottom_tablet', 'field_normal_padding_left_tablet', 'field_normal_padding_top_mobile', 'field_normal_padding_right_mobile', 'field_normal_padding_bottom_mobile', 'field_normal_padding_left_mobile',
			'field_focus_border_width', 'field_focus_border_radius',
			'field_focus_padding_top', 'field_focus_padding_right', 'field_focus_padding_bottom', 'field_focus_padding_left',
		];
		foreach ( $str_keys as $key ) {
			if ( isset( $settings[ $key ] ) && trim( (string) $settings[ $key ] ) !== '' ) {
				return true;
			}
		}
		$border_type = isset( $settings['field_normal_border_type'] ) ? (string) $settings['field_normal_border_type'] : 'none';
		if ( $border_type !== '' && $border_type !== 'none' ) {
			return true;
		}
		$focus_bt = isset( $settings['field_focus_border_type'] ) ? (string) $settings['field_focus_border_type'] : 'none';
		if ( $focus_bt !== '' && $focus_bt !== 'none' ) {
			return true;
		}
		foreach ( [ 'field_normal_shadow_x', 'field_normal_shadow_y', 'field_normal_shadow_blur', 'field_normal_shadow_spread' ] as $key ) {
			if ( isset( $settings[ $key ] ) && (int) $settings[ $key ] !== 0 ) {
				return true;
			}
		}
		return false;
	}

	/** @param array<string, mixed> $settings */
	private function hasButtonCustomization( array $settings ): bool {
		$keys = [
			'button_font_family', 'button_font_size_desktop', 'button_font_size_tablet', 'button_font_size_mobile',
			'button_font_weight', 'button_line_height_desktop', 'button_line_height_tablet', 'button_line_height_mobile',
			'button_letter_spacing_desktop', 'button_letter_spacing_tablet', 'button_letter_spacing_mobile',
			'button_text_transform', 'button_font_style',
			'button_normal_padding_top', 'button_normal_padding_right', 'button_normal_padding_bottom', 'button_normal_padding_left',
			'button_normal_padding_top_tablet', 'button_normal_padding_right_tablet', 'button_normal_padding_bottom_tablet', 'button_normal_padding_left_tablet',
			'button_normal_padding_top_mobile', 'button_normal_padding_right_mobile', 'button_normal_padding_bottom_mobile', 'button_normal_padding_left_mobile',
			'button_normal_text_color', 'button_normal_bg_type', 'button_normal_bg_color', 'button_normal_bg_gradient',
			'button_normal_border_type', 'button_normal_border_color', 'button_normal_border_width', 'button_normal_border_radius',
			'button_normal_border_radius_desktop', 'button_normal_border_radius_tablet', 'button_normal_border_radius_mobile',
			'button_normal_shadow_x', 'button_normal_shadow_y', 'button_normal_shadow_blur', 'button_normal_shadow_spread', 'button_normal_shadow_color', 'button_normal_shadow_inset',
			'button_hover_text_color', 'button_hover_bg_type', 'button_hover_bg_color', 'button_hover_bg_gradient',
			'button_hover_border_type', 'button_hover_border_color', 'button_hover_border_width', 'button_hover_border_radius',
			'button_hover_border_radius_desktop', 'button_hover_border_radius_tablet', 'button_hover_border_radius_mobile',
			'button_hover_padding_desktop', 'button_hover_padding_tablet', 'button_hover_padding_mobile',
			'button_hover_shadow_x', 'button_hover_shadow_y', 'button_hover_shadow_blur', 'button_hover_shadow_spread', 'button_hover_shadow_color', 'button_hover_shadow_inset',
		];
		foreach ( $keys as $key ) {
			if ( isset( $settings[ $key ] ) && trim( (string) $settings[ $key ] ) !== '' ) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Whether any Footer tab setting is non-default.
	 *
	 * @param array<string, mixed> $settings Module settings.
	 * @return bool
	 */
	private function hasFooterCustomization( array $settings ): bool {
		$keys = [
			'footer_font_family', 'footer_font_size_desktop', 'footer_font_size_tablet', 'footer_font_size_mobile',
			'footer_font_weight', 'footer_line_height_desktop', 'footer_line_height_tablet', 'footer_line_height_mobile',
			'footer_letter_spacing_desktop', 'footer_letter_spacing_tablet', 'footer_letter_spacing_mobile',
			'footer_text_transform', 'footer_font_style', 'footer_normal_text_color', 'footer_hover_text_color',
		];
		foreach ( $keys as $key ) {
			if ( isset( $settings[ $key ] ) && trim( (string) $settings[ $key ] ) !== '' ) {
				return true;
			}
		}
		return false;
	}

	/** @param array<string, mixed> $settings */
	private function hasLabelCustomization( array $settings ): bool {
		$keys = [
			'label_font_family', 'label_font_size_desktop', 'label_font_size_tablet', 'label_font_size_mobile',
			'label_font_weight', 'label_line_height_desktop', 'label_line_height_tablet', 'label_line_height_mobile',
			'label_letter_spacing_desktop', 'label_letter_spacing_tablet', 'label_letter_spacing_mobile',
			'label_text_transform', 'label_font_style', 'label_text_color', 'label_spacing_bottom',
		];
		foreach ( $keys as $key ) {
			if ( isset( $settings[ $key ] ) && trim( (string) $settings[ $key ] ) !== '' ) {
				return true;
			}
		}
		return false;
	}

	/** @return array<string, mixed> */
	public static function getDefaultSettings(): array {
		return [
			'layout_preset'               => 'default',
			'template_type'               => 'classic-centered',
			'split_ratio'                 => '50',
			'split_column_order'          => 'form-first',
			'split_form_form_alignment'   => 'center',
			'split_form_bg_type'          => '',
			'split_form_bg_color'         => '#f0f0f1',
			'split_form_bg_image'         => '',
			'split_form_bg_image_overlay_color'   => '#000000',
			'split_form_bg_image_overlay_opacity' => '0',
			'split_form_bg_image_position'       => 'center center',
			'split_form_bg_image_size'            => 'cover',
			'split_form_bg_image_repeat'          => 'no-repeat',
			'split_form_bg_video'                  => '',
			'split_form_bg_video_overlay_color'   => '#000000',
			'split_form_bg_video_overlay_opacity' => '0',
			'split_form_bg_gradient'      => '{"type":"linear","angle":135,"stops":[{"color":"#f0f0f1","pos":0},{"color":"#e0e0e0","pos":100}]}',
			// Split layout: Column without Form
			'split_empty_bg_type'         => '',
			'split_empty_bg_color'        => '#f0f0f1',
			'split_empty_bg_image'        => '',
			'split_empty_bg_image_overlay_color'   => '#000000',
			'split_empty_bg_image_overlay_opacity' => '0',
			'split_empty_bg_image_position'       => 'center center',
			'split_empty_bg_image_size'            => 'cover',
			'split_empty_bg_image_repeat'          => 'no-repeat',
			'split_empty_bg_video'                 => '',
			'split_empty_bg_video_overlay_color'   => '#000000',
			'split_empty_bg_video_overlay_opacity' => '0',
			'split_empty_bg_gradient'     => '{"type":"linear","angle":135,"stops":[{"color":"#f0f0f1","pos":0},{"color":"#e0e0e0","pos":100}]}',
			'split_empty_add_content'     => '0',
			'split_empty_content'         => '<h2 style="font-family: Arial, sans-serif; font-size: 28px; font-weight: 700; margin: 0 0 12px; color: #111;">' . "\n  " . esc_html__( 'Welcome back', 'wp-powersuite' ) . "\n" . '</h2>' . "\n\n" . '<p style="font-family: Arial, sans-serif; font-size: 16px; line-height: 1.7; margin: 0; color: #444;">' . "\n  " . esc_html__( 'Sign in to continue. Replace this text with your own message in the login customizer settings.', 'wp-powersuite' ) . "\n" . '</p>',
			'form_alignment'              => 'center',
			'classic_bg_type'             => 'gradient',
			'classic_bg_color'            => '#f6f8fc',
			'classic_bg_image'            => '',
			'classic_bg_image_overlay_color'   => '#000000',
			'classic_bg_image_overlay_opacity' => '0',
			'classic_bg_image_position'       => 'center center',
			'classic_bg_image_size'            => 'cover',
			'classic_bg_image_repeat'          => 'no-repeat',
			'classic_bg_video'                  => '',
			'classic_bg_video_overlay_color'   => '#000000',
			'classic_bg_video_overlay_opacity' => '0',
			'classic_bg_gradient'         => '{"type":"linear","angle":160,"stops":[{"color":"#f6f8fc","pos":0},{"color":"#e2e8f0","pos":100}]}',
			'form_customization_enabled'  => '0',
			'logo_source'                 => 'site_icon',
			'logo_url'                    => '',
			'logo_external_url'           => '',
			'logo_width_desktop'          => '',
			'logo_width_tablet'           => '',
			'logo_width_mobile'           => '',
			'logo_alignment'              => 'center',
			'logo_link'                  => '',
			'logo_margin_top'             => '',
			'logo_margin_bottom'          => '24',
			'hide_logo'                  => '0',
			'box_form_width'             => '380',
			'box_form_width_unit'        => 'px',
			'form_width'                 => '',
			'form_width_unit'            => 'px',
			'box_bg_type'                => 'color',
			'box_bg_color'               => '#ffffff',
			'box_bg_image'               => '',
			'box_bg_image_overlay_color' => '#000000',
			'box_bg_image_overlay_opacity' => '0',
			'box_bg_gradient'            => '{"type":"linear","angle":135,"stops":[{"color":"#ffffff","pos":0},{"color":"#e8e8e8","pos":100}]}',
			'box_padding_desktop'        => '',
			'box_padding_tablet'         => '',
			'box_padding_mobile'         => '',
			'box_padding_top'            => '32',
			'box_padding_right'          => '40',
			'box_padding_bottom'         => '32',
			'box_padding_left'           => '40',
			'box_padding_top_tablet'     => '',
			'box_padding_right_tablet'   => '',
			'box_padding_bottom_tablet'  => '',
			'box_padding_left_tablet'    => '',
			'box_padding_top_mobile'     => '',
			'box_padding_right_mobile'   => '',
			'box_padding_bottom_mobile'  => '',
			'box_padding_left_mobile'    => '',
			'box_border_type'             => 'none',
			'box_border_color'            => '#cccccc',
			'box_border_width'            => '1',
			'box_border_radius_desktop'   => '12',
			'box_border_radius_tablet'   => '12',
			'box_border_radius_mobile'   => '10',
			'box_shadow_x'                => 0,
			'box_shadow_y'                => 8,
			'box_shadow_blur'             => 24,
			'box_shadow_spread'           => 0,
			'box_shadow_color'            => '#0000001a',
			'box_shadow_inset'            => '0',
			'form_padding_desktop'        => '',
			'form_padding_tablet'         => '',
			'form_padding_mobile'         => '',
			'form_padding_top'            => '10',
			'form_padding_right'          => '10',
			'form_padding_bottom'         => '10',
			'form_padding_left'           => '10',
			'form_padding_top_tablet'     => '',
			'form_padding_right_tablet'   => '',
			'form_padding_bottom_tablet'  => '',
			'form_padding_left_tablet'    => '',
			'form_padding_top_mobile'     => '',
			'form_padding_right_mobile'   => '',
			'form_padding_bottom_mobile'  => '',
			'form_padding_left_mobile'    => '',
			'form_border_type'             => 'none',
			'form_border_color'            => '#cccccc',
			'form_border_width'            => '1',
			'form_border_radius_desktop'   => '',
			'form_border_radius_tablet'   => '',
			'form_border_radius_mobile'   => '',
			'form_shadow_x'                => 0,
			'form_shadow_y'                => 0,
			'form_shadow_blur'             => 0,
			'form_shadow_spread'           => 0,
			'form_shadow_color'            => '#000000',
			'form_shadow_inset'            => '0',
			'form_bg_type'                 => '',
			'form_bg_color'                => '#ffffff',
			'form_bg_image'               => '',
			'form_bg_image_overlay_color'  => '#000000',
			'form_bg_image_overlay_opacity' => '0',
			'form_bg_gradient'            => '{"type":"linear","angle":135,"stops":[{"color":"#ffffff","pos":0},{"color":"#e8e8e8","pos":100}]}',
			'field_font_family'            => '',
			'field_font_size'              => '',
			'field_font_size_desktop'      => '15',
			'field_font_size_tablet'       => '',
			'field_font_size_mobile'       => '',
			'field_font_weight'            => '',
			'field_line_height'            => '',
			'field_line_height_desktop'    => '',
			'field_line_height_tablet'     => '',
			'field_line_height_mobile'     => '',
			'field_letter_spacing'         => '',
			'field_letter_spacing_desktop' => '',
			'field_letter_spacing_tablet'  => '',
			'field_letter_spacing_mobile'  => '',
			'field_text_transform'         => '',
			'field_font_style'              => '',
			// Fields: Normal state
			'field_normal_text_color'      => '#1d2327',
			'field_normal_bg_color'        => '#ffffff',
			'field_normal_bg_overlay_opacity' => '100',
			'field_normal_padding_top'     => '12',
			'field_normal_padding_right'   => '14',
			'field_normal_padding_bottom'  => '12',
			'field_normal_padding_left'    => '14',
			'field_normal_padding_top_tablet'    => '',
			'field_normal_padding_right_tablet'  => '',
			'field_normal_padding_bottom_tablet' => '',
			'field_normal_padding_left_tablet'   => '',
			'field_normal_padding_top_mobile'    => '',
			'field_normal_padding_right_mobile'  => '',
			'field_normal_padding_bottom_mobile' => '',
			'field_normal_padding_left_mobile'   => '',
			'field_normal_border_type'     => 'none',
			'field_normal_border_color'    => '#c3c4c7',
			'field_normal_border_width'    => '1',
			'field_normal_border_radius'   => '',
			'field_normal_border_radius_desktop' => '6',
			'field_normal_border_radius_tablet'  => '',
			'field_normal_border_radius_mobile'  => '',
			'field_normal_shadow_x'        => 0,
			'field_normal_shadow_y'        => 0,
			'field_normal_shadow_blur'     => 0,
			'field_normal_shadow_spread'   => 0,
			'field_normal_shadow_color'    => '#000000',
			'field_normal_shadow_inset'    => '0',
			'hide_back_link'                => '0',
			'hide_lost_password'            => '0',
			'hide_remember_me'              => '0',
			'hide_language_switcher'        => '0',
			'custom_css'                    => '',
			'field_focus_text_color'       => '',
			'field_focus_padding_top'      => '',
			'field_focus_padding_right'    => '',
			'field_focus_padding_bottom'   => '',
			'field_focus_padding_left'     => '',
			'field_focus_border_type'      => 'none',
			'field_focus_border_color'     => '#2271b1',
			'field_focus_border_width'     => '1',
			'field_focus_border_radius'    => '6',
			// Button tab: typography
			'button_font_family'            => '',
			'button_font_size_desktop'      => '15',
			'button_font_size_tablet'       => '',
			'button_font_size_mobile'       => '',
			'button_font_weight'            => '600',
			'button_line_height_desktop'    => '',
			'button_line_height_tablet'     => '',
			'button_line_height_mobile'     => '',
			'button_letter_spacing_desktop' => '',
			'button_letter_spacing_tablet'  => '',
			'button_letter_spacing_mobile'  => '',
			'button_text_transform'         => '',
			'button_font_style'             => '',
			'button_normal_padding_top'     => '',
			'button_normal_padding_right'   => '',
			'button_normal_padding_bottom'  => '',
			'button_normal_padding_left'    => '',
			'button_normal_padding_top_tablet' => '',
			'button_normal_padding_right_tablet' => '',
			'button_normal_padding_bottom_tablet' => '',
			'button_normal_padding_left_tablet' => '',
			'button_normal_padding_top_mobile' => '',
			'button_normal_padding_right_mobile' => '',
			'button_normal_padding_bottom_mobile' => '',
			'button_normal_padding_left_mobile' => '',
			'button_normal_text_color'      => '',
			'button_normal_bg_type'         => '',
			'button_normal_bg_color'        => '#2271b1',
			'button_normal_bg_gradient'     => '{"type":"linear","angle":135,"stops":[{"color":"#2271b1","pos":0},{"color":"#135e96","pos":100}]}',
			'button_normal_border_type'     => 'none',
			'button_normal_border_color'     => '#cccccc',
			'button_normal_border_width'    => '1',
			'button_normal_border_radius'   => '',
			'button_normal_border_radius_desktop' => '6',
			'button_normal_border_radius_tablet'  => '',
			'button_normal_border_radius_mobile'  => '',
			'button_normal_shadow_x'        => 0,
			'button_normal_shadow_y'        => 0,
			'button_normal_shadow_blur'     => 0,
			'button_normal_shadow_spread'   => 0,
			'button_normal_shadow_color'   => '#000000',
			'button_normal_shadow_inset'    => '0',
			'button_hover_text_color'      => '',
			'button_hover_bg_type'          => '',
			'button_hover_bg_color'        => '#135e96',
			'button_hover_bg_gradient'     => '{"type":"linear","angle":135,"stops":[{"color":"#135e96","pos":0},{"color":"#2271b1","pos":100}]}',
			'button_hover_border_type'     => 'none',
			'button_hover_border_color'     => '#cccccc',
			'button_hover_border_width'    => '1',
			'button_hover_border_radius'   => '',
			'button_hover_border_radius_desktop' => '',
			'button_hover_border_radius_tablet'  => '',
			'button_hover_border_radius_mobile'  => '',
			'button_hover_padding_desktop' => '',
			'button_hover_padding_tablet'  => '',
			'button_hover_padding_mobile'  => '',
			'button_hover_shadow_x'        => 0,
			'button_hover_shadow_y'        => 0,
			'button_hover_shadow_blur'     => 0,
			'button_hover_shadow_spread'   => 0,
			'button_hover_shadow_color'   => '#000000',
			'button_hover_shadow_inset'    => '0',
			// Footer tab (#login #nav a, #login #backtoblog a)
			'footer_font_family'            => '',
			'footer_font_size_desktop'      => '13',
			'footer_font_size_tablet'       => '',
			'footer_font_size_mobile'       => '',
			'footer_font_weight'            => '',
			'footer_line_height_desktop'    => '',
			'footer_line_height_tablet'     => '',
			'footer_line_height_mobile'     => '',
			'footer_letter_spacing_desktop' => '',
			'footer_letter_spacing_tablet'  => '',
			'footer_letter_spacing_mobile'  => '',
			'footer_text_transform'         => '',
			'footer_font_style'             => '',
			'footer_normal_text_color'      => '#646970',
			'footer_hover_text_color'       => '#2271b1',
			'label_font_family'            => '',
			'label_font_size_desktop'      => '14',
			'label_font_size_tablet'       => '',
			'label_font_size_mobile'       => '',
			'label_font_weight'            => '500',
			'label_line_height_desktop'    => '',
			'label_line_height_tablet'     => '',
			'label_line_height_mobile'     => '',
			'label_letter_spacing_desktop' => '',
			'label_letter_spacing_tablet'  => '',
			'label_letter_spacing_mobile'  => '',
			'label_text_transform'         => '',
			'label_font_style'             => '',
			'label_text_color'             => '#3c434a',
			'label_spacing_bottom'         => '6',
		];
	}

	/**
	 * Persist starter styling when the module is enabled and no settings exist yet.
	 */
	public static function seedStarterSettingsIfEmpty(): bool {
		$repo  = OptionStoreModuleSettingsRepository::forModule( self::MODULE_ID );
		$saved = $repo->all( [] );

		if ( $saved !== [] ) {
			return false;
		}

		return $repo->save( self::getDefaultSettings() );
	}

	/**
	 * @param array<string, mixed> $module_metadata
	 */
	public static function handleModuleToggled( string $module_id, bool $enable, array $module_metadata = [] ): void {
		unset( $module_metadata );

		if ( $module_id !== self::MODULE_ID || ! $enable ) {
			return;
		}

		self::seedStarterSettingsIfEmpty();
	}

	/**
	 * Get module settings (defaults merged with saved).
	 *
	 * @return array<string, mixed>
	 */
	private function getSettings(): array {
$raw = $this->moduleSettings()->all( [] );
		return is_array( $raw ) ? array_merge( self::getDefaultSettings(), $raw ) : self::getDefaultSettings();
	}

	/**
	 * Admin preview banner fixed to the top-left of the login screen.
	 */
	public function outputLoginPreviewBanner(): void {
		if ( ! $this->isLoginPreviewRequest() ) {
			return;
		}

		$settings_url = admin_url( 'admin.php?page=powersuite-login-page-customizor' );
		echo '<div class="wpps-lpc-preview-banner-wrap" role="status" aria-live="polite">';
		echo '<div class="wpps-lpc-preview-banner">';
		echo '<span class="wpps-lpc-preview-banner__icon dashicons dashicons-visibility" aria-hidden="true"></span>';
		echo '<div class="wpps-lpc-preview-banner__content">';
		echo '<strong class="wpps-lpc-preview-banner__title">' . esc_html__( 'Admin preview', 'wp-powersuite' ) . '</strong>';
		echo '<p class="wpps-lpc-preview-banner__text">' . esc_html__( 'You are viewing the login page while signed in. Save settings first to see the latest changes.', 'wp-powersuite' ) . '</p>';
		echo '<a class="wpps-lpc-preview-banner__link" href="' . esc_url( $settings_url ) . '">' . esc_html__( 'Back to Login Customizer', 'wp-powersuite' ) . '</a>';
		echo '</div></div></div>';
	}

	/**
	 * Styles for the admin preview banner on the login screen.
	 */
	public function enqueueLoginPreviewBannerStyles(): void {
		if ( ! $this->isLoginPreviewRequest() ) {
			return;
		}

		wp_enqueue_style( 'dashicons' );

		$css = '
.login.wpps-lpc-preview-mode .wpps-lpc-preview-banner-wrap {
	position: fixed;
	top: 16px;
	left: 16px;
	z-index: 100000;
	max-width: min(400px, calc(100vw - 32px));
	margin: 0;
}
.login.wpps-lpc-preview-mode .wpps-lpc-preview-banner {
	display: flex;
	align-items: flex-start;
	gap: 12px;
	margin: 0;
	padding: 14px 16px;
	border: 1px solid #c3d9f0;
	border-radius: 10px;
	background: #f8fbff;
	color: #1d2327;
	box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
	backdrop-filter: blur(6px);
}
.login.wpps-lpc-preview-mode .wpps-lpc-preview-banner__icon {
	flex-shrink: 0;
	width: 22px;
	height: 22px;
	font-size: 22px;
	line-height: 1;
	color: #2271b1;
	margin-top: 1px;
}
.login.wpps-lpc-preview-mode .wpps-lpc-preview-banner__content {
	flex: 1;
	min-width: 0;
}
.login.wpps-lpc-preview-mode .wpps-lpc-preview-banner__title {
	display: block;
	margin: 0 0 4px;
	font-size: 14px;
	font-weight: 600;
	color: #1d2327;
}
.login.wpps-lpc-preview-mode .wpps-lpc-preview-banner__text {
	margin: 0 0 10px;
	font-size: 13px;
	line-height: 1.5;
	color: #50575e;
}
.login.wpps-lpc-preview-mode .wpps-lpc-preview-banner__link {
	display: inline-flex;
	align-items: center;
	padding: 6px 12px;
	border-radius: 4px;
	background: #2271b1;
	color: #fff !important;
	font-size: 12px;
	font-weight: 600;
	line-height: 1.4;
	text-decoration: none;
	box-shadow: none;
}
.login.wpps-lpc-preview-mode .wpps-lpc-preview-banner__link:hover,
.login.wpps-lpc-preview-mode .wpps-lpc-preview-banner__link:focus {
	background: #135e96;
	color: #fff !important;
}
';
		wp_add_inline_style( 'login', $css );
	}

	/**
	 * Body class when an administrator previews the login screen.
	 *
	 * @param array<int, string> $classes Existing body classes.
	 * @param string             $action Login action.
	 * @return array<int, string>
	 */
	public function addLoginPreviewBodyClass( array $classes, string $action ): array {
		if ( $this->isLoginPreviewRequest() ) {
			$classes[] = 'wpps-lpc-preview-mode';
		}

		return $classes;
	}

	/**
	 * Add body class for split layout.
	 *
	 * @param array<int, string> $classes Existing body classes.
	 * @param string             $action Login action (e.g. 'login', 'lostpassword').
	 * @return array<int, string>
	 */
	public function addSplitLayoutBodyClass( array $classes, string $action ): array {
		$classes[] = 'wpps-login-split-layout';
		return $classes;
	}

	/**
	 * Output opening wrapper for the left column so #login stays inside it with default form width.
	 * When Column with Form background type is video, output the video element as first child.
	 */
	public function outputSplitLayoutLeftColumnOpen(): void {
		echo '<div class="wpps-login-split-left">';
		$settings = $this->getSettings();
		$bg_type  = isset( $settings['split_form_bg_type'] ) ? (string) $settings['split_form_bg_type'] : '';
		$url      = isset( $settings['split_form_bg_video'] ) ? trim( (string) $settings['split_form_bg_video'] ) : '';
		if ( $bg_type === 'video' && $url !== '' ) {
			$url = esc_url( $url );
			echo '<div class="wpps-split-column-video-wrap" aria-hidden="true"><video autoplay muted loop playsinline src="' . $url . '"></video></div>';
		}
	}

	/**
	 * Close the left column wrapper and output the right column (Column without Form).
	 * Outputs video background and/or custom content when configured.
	 */
	public function outputSplitLayoutColumnsClose(): void {
		echo '</div>';
		$settings = $this->getSettings();
		$bg_type  = isset( $settings['split_empty_bg_type'] ) ? (string) $settings['split_empty_bg_type'] : '';
		$add_content = ! empty( $settings['split_empty_add_content'] ) && $settings['split_empty_add_content'] !== '0';
		$content = isset( $settings['split_empty_content'] ) ? (string) $settings['split_empty_content'] : '';
		$url = isset( $settings['split_empty_bg_video'] ) ? trim( (string) $settings['split_empty_bg_video'] ) : '';
		$has_video = ( $bg_type === 'video' && $url !== '' );
		$aria_hidden = ( ! $add_content && ! $has_video ) ? ' aria-hidden="true"' : '';
		echo '<div class="wpps-login-split-right"' . $aria_hidden . '>';
		if ( $has_video ) {
			$url = esc_url( $url );
			echo '<div class="wpps-split-column-video-wrap" aria-hidden="true"><video autoplay muted loop playsinline src="' . $url . '"></video></div>';
		}
		if ( $add_content && $content !== '' ) {
			echo '<div class="wpps-split-empty-content">' . wp_kses_post( $content ) . '</div>';
		}
		echo '</div>';
	}

	/**
	 * Output script to start video playback in split columns (when autoplay is blocked).
	 */
	public function outputSplitLayoutVideoPlayScript(): void {
		?>
		<script>
		(function() {
			document.querySelectorAll('.wpps-split-column-video-wrap video').forEach(function(v) {
				if (v && v.pause) {
					v.muted = true;
					v.play().catch(function() {});
				}
			});
		})();
		</script>
		<?php
	}

	/**
	 * Enqueue CSS for split layout. Applies form alignment and column backgrounds (Column with Form / Column without Form).
	 */
	public function enqueueSplitLayoutStyles(): void {
		$settings = $this->getSettings();
		$left_pct  = isset( $settings['split_ratio'] ) && in_array( (string) $settings['split_ratio'], [ '25', '30', '35', '40', '45', '50' ], true ) ? (int) $settings['split_ratio'] : 50;
		$right_pct = 100 - $left_pct;
		$order     = ( isset( $settings['split_column_order'] ) && (string) $settings['split_column_order'] === 'form-second' ) ? 'form-second' : 'form-first';
		$left_order  = $order === 'form-second' ? 2 : 1;
		$right_order = $order === 'form-second' ? 1 : 2;

		// Form alignment (Column with Form)
		$form_align = isset( $settings['split_form_form_alignment'] ) ? (string) $settings['split_form_form_alignment'] : 'center';
		if ( ! in_array( $form_align, [ 'left', 'center', 'right' ], true ) ) {
			$form_align = 'center';
		}
		$align_items = $form_align === 'left' ? 'flex-start' : ( $form_align === 'right' ? 'flex-end' : 'center' );
		$login_margin = $form_align === 'center' ? 'margin-left: auto; margin-right: auto;' : ( $form_align === 'left' ? 'margin-left: 0; margin-right: auto;' : 'margin-left: auto; margin-right: 0;' );

		// Left column background (split_form_bg_*)
		$left_bg = $this->buildSplitColumnBackgroundCss( $settings, 'split_form_bg' );
		// Right column background (split_empty_bg_*)
		$right_bg = $this->buildSplitColumnBackgroundCss( $settings, 'split_empty_bg' );

		$css = '
			body.wpps-login-split-layout {
				display: flex;
				flex-wrap: nowrap;
				margin: 0;
				' . $this->viewportHeightCss( true ) . '
			}
			body.wpps-login-split-layout .wpps-login-split-left {
				flex: 0 0 ' . $left_pct . '%;
				width: ' . $left_pct . '%;
				order: ' . $left_order . ';
				' . $this->viewportHeightCss( true ) . '
				display: flex;
				flex-direction: column;
				justify-content: center;
				align-items: ' . $align_items . ';
				padding: 0;
				' . $left_bg['rules'] . '
			}
			body.wpps-login-split-layout .wpps-login-split-left #login {
				' . $login_margin . '
			}
			body.wpps-login-split-layout .wpps-login-split-right {
				flex: 0 0 ' . $right_pct . '%;
				width: ' . $right_pct . '%;
				' . $this->viewportHeightCss( true ) . '
				order: ' . $right_order . ';
				padding: 0;
				' . $right_bg['rules'] . '
			}
			@media (max-width: 767px) {
				body.wpps-login-split-layout {
					flex-direction: column;
					flex-wrap: nowrap;
					overflow-x: hidden;
					' . $this->viewportHeightCss( true ) . '
					' . $left_bg['rules'] . '
				}
				body.wpps-login-split-layout .wpps-login-split-right {
					display: none !important;
				}
				body.wpps-login-split-layout .wpps-login-split-left {
					flex: 1 1 auto;
					align-self: stretch;
					width: 100%;
					max-width: 100%;
					' . $this->viewportHeightCss( true ) . '
					padding: 24px 16px;
					box-sizing: border-box;
					order: 1 !important;
				}
				body.wpps-login-split-layout .wpps-login-split-left #login {
					width: 100% !important;
					max-width: 100% !important;
					margin-left: auto !important;
					margin-right: auto !important;
				}
			}
		';
		$css .= $left_bg['extra'];
		$css .= $right_bg['extra'];
		$css .= '
			.wpps-split-column-video-wrap { position: absolute; inset: 0; z-index: 0; overflow: hidden; min-height: 100%; }
			.wpps-split-column-video-wrap video { width: 100%; height: 100%; min-height: 100%; object-fit: cover; object-position: center center; }
			body.wpps-login-split-layout .wpps-login-split-right { display: flex; flex-direction: column; align-items: center; justify-content: center; }
			body.wpps-login-split-layout .wpps-login-split-right .wpps-split-empty-content { position: relative; z-index: 2; width: 100%; max-width: 100%; box-sizing: border-box; padding: 2em 5%; }
		';
		wp_add_inline_style( 'login', $css );
	}

	/**
	 * Build CSS for one split column background (color, gradient, image, or video).
	 *
	 * @param array<string, mixed> $settings Module settings.
	 * @param string               $prefix  'split_form_bg' or 'split_empty_bg'.
	 * @return array{ rules: string, extra: string } 'rules' for the column selector, 'extra' for overlay/video rules.
	 */
	private function buildSplitColumnBackgroundCss( array $settings, string $prefix ): array {
		$rules = 'background: #f0f0f1;'; // default left
		if ( $prefix === 'split_empty_bg' ) {
			$rules = 'background: #fff;';
		}
		$extra = '';
		$bg_type = isset( $settings[ $prefix . '_type' ] ) ? (string) $settings[ $prefix . '_type' ] : '';
		if ( ! in_array( $bg_type, [ 'color', 'gradient', 'image', 'video' ], true ) ) {
			return [ 'rules' => $rules, 'extra' => $extra ];
		}
		if ( $bg_type === 'color' ) {
			$color = isset( $settings[ $prefix . '_color' ] ) ? trim( (string) $settings[ $prefix . '_color' ] ) : '#f0f0f1';
			if ( $color === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $color ) ) {
				$color = $prefix === 'split_empty_bg' ? '#fff' : '#f0f0f1';
			}
			$rules = 'background-color: ' . $color . ';';
			return [ 'rules' => $rules, 'extra' => $extra ];
		}
		if ( $bg_type === 'gradient' ) {
			$gradient_css = $this->buildGradientCss( $settings, $prefix . '_gradient' );
			if ( $gradient_css !== '' ) {
				$rules = 'background: ' . $gradient_css . ';';
			}
			return [ 'rules' => $rules, 'extra' => $extra ];
		}
		if ( $bg_type === 'image' ) {
			$url = isset( $settings[ $prefix . '_image' ] ) ? trim( (string) $settings[ $prefix . '_image' ] ) : '';
			if ( $url !== '' ) {
				$url = esc_url( $url );
				$size  = isset( $settings[ $prefix . '_image_size' ] ) ? (string) $settings[ $prefix . '_image_size' ] : 'cover';
				$size  = in_array( $size, [ 'auto', 'cover', 'contain' ], true ) ? $size : 'cover';
				$pos   = isset( $settings[ $prefix . '_image_position' ] ) ? (string) $settings[ $prefix . '_image_position' ] : 'center center';
				$valid_pos = [ 'left top', 'center top', 'right top', 'left center', 'center center', 'right center', 'left bottom', 'center bottom', 'right bottom' ];
				$pos   = in_array( $pos, $valid_pos, true ) ? $pos : 'center center';
				$repeat = isset( $settings[ $prefix . '_image_repeat' ] ) ? (string) $settings[ $prefix . '_image_repeat' ] : 'no-repeat';
				$repeat = in_array( $repeat, [ 'repeat', 'no-repeat', 'repeat-x', 'repeat-y' ], true ) ? $repeat : 'no-repeat';
				$rules = 'position: relative; background-image: url(' . $url . '); background-size: ' . $size . '; background-position: ' . $pos . '; background-repeat: ' . $repeat . ';';
				$overlay_color   = isset( $settings[ $prefix . '_image_overlay_color' ] ) ? trim( (string) $settings[ $prefix . '_image_overlay_color' ] ) : '#000000';
				$overlay_opacity = isset( $settings[ $prefix . '_image_overlay_opacity' ] ) ? trim( (string) $settings[ $prefix . '_image_overlay_opacity' ] ) : '0';
				$overlay_val = $overlay_opacity !== '' && is_numeric( $overlay_opacity ) ? max( 0, min( 100, (int) $overlay_opacity ) ) : 0;
				if ( $overlay_val > 0 && preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $overlay_color ) ) {
					$sel = $prefix === 'split_form_bg' ? 'body.wpps-login-split-layout .wpps-login-split-left' : 'body.wpps-login-split-layout .wpps-login-split-right';
					$extra .= $sel . '::before { content: ""; position: absolute; inset: 0; background-color: ' . $overlay_color . '; opacity: ' . ( $overlay_val / 100 ) . '; pointer-events: none; z-index: 0; }';
					$extra .= $sel . ' > * { position: relative; z-index: 1; }';
				}
			}
			return [ 'rules' => $rules, 'extra' => $extra ];
		}
		if ( $bg_type === 'video' ) {
			$rules = 'position: relative; background-color: #000;';
			$overlay_color   = isset( $settings[ $prefix . '_video_overlay_color' ] ) ? trim( (string) $settings[ $prefix . '_video_overlay_color' ] ) : '#000000';
			$overlay_opacity = isset( $settings[ $prefix . '_video_overlay_opacity' ] ) ? trim( (string) $settings[ $prefix . '_video_overlay_opacity' ] ) : '0';
			$overlay_val = $overlay_opacity !== '' && is_numeric( $overlay_opacity ) ? max( 0, min( 100, (int) $overlay_opacity ) ) : 0;
			$sel = $prefix === 'split_form_bg' ? 'body.wpps-login-split-layout .wpps-login-split-left' : 'body.wpps-login-split-layout .wpps-login-split-right';
			if ( $overlay_val > 0 && preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $overlay_color ) ) {
				$extra .= $sel . '::before { content: ""; position: absolute; inset: 0; background-color: ' . $overlay_color . '; opacity: ' . ( $overlay_val / 100 ) . '; pointer-events: none; z-index: 1; }';
			}
			$extra .= $sel . ' > *:not(.wpps-split-column-video-wrap) { position: relative; z-index: 2; }';
			return [ 'rules' => $rules, 'extra' => $extra ];
		}
		return [ 'rules' => $rules, 'extra' => $extra ];
	}

	/**
	 * Enqueue CSS for classic layout: vertical centering (like split layout) and form alignment (left / center / right).
	 */
	public function enqueueClassicLayoutFormAlignmentStyles(): void {
		$settings = $this->getSettings();
		$align = isset( $settings['form_alignment'] ) ? (string) $settings['form_alignment'] : 'center';
		if ( ! in_array( $align, [ 'left', 'center', 'right' ], true ) ) {
			$align = 'center';
		}

		$align_items  = 'center';
		$body_padding = '';
		if ( $align === 'left' ) {
			$align_items  = 'flex-start';
			$body_padding = 'padding-left: 5% !important;';
		} elseif ( $align === 'right' ) {
			$align_items  = 'flex-end';
			$body_padding = 'padding-right: 5% !important;';
		}

		$css  = "\nhtml {\n";
		$css .= "\t" . $this->viewportHeightCss() . "\n";
		$css .= "}\n";
		$css .= "body.login {\n";
		$css .= "\tmargin: 0 !important;\n";
		$css .= "\t" . $this->viewportHeightCss() . " !important;\n";
		$css .= "\tdisplay: flex !important;\n";
		$css .= "\tflex-direction: column !important;\n";
		$css .= "\tjustify-content: center !important;\n";
		$css .= "\talign-items: " . $align_items . " !important;\n";
		$css .= "\tbox-sizing: border-box !important;\n";
		if ( $body_padding !== '' ) {
			$css .= "\t" . $body_padding . "\n";
		} else {
			$css .= "\tpadding: 0 !important;\n";
		}
		$css .= "}\n";
		$css .= "#login {\n";
		$css .= "\tmargin: 0 !important;\n";
		$css .= "\tpadding: 0;\n";
		$css .= "\twidth: 320px;\n";
		$css .= "\tmax-width: calc(100vw - 32px);\n";
		$css .= "\tbox-sizing: border-box !important;\n";
		$css .= "\tflex-shrink: 0;\n";
		$css .= "}\n";
		$css .= "#login form {\n";
		$css .= "\tmargin-top: 0;\n";
		$css .= "\tpadding: 0;\n";
		$css .= "}\n";
		$css .= "#login form::after {\n";
		$css .= "\tcontent: \"\";\n";
		$css .= "\tdisplay: table;\n";
		$css .= "\tclear: both;\n";
		$css .= "}\n";

		wp_add_inline_style( 'login', $css );
	}

	/**
	 * Shared mobile/tablet safeguards so presets and custom layouts do not overflow small screens.
	 */
	public function enqueueResponsiveLoginStyles(): void {
		$settings     = $this->getSettings();
		$box_selector = $this->getLoginBoxCssSelector( $settings );
		$form_selector = $this->getLoginFormCssSelector( $settings );

		$css  = "\nhtml {\n";
		$css .= "\t" . $this->viewportHeightCss() . "\n";
		$css .= "}\n";
		$css .= "body.login {\n";
		$css .= "\toverflow-x: hidden;\n";
		$css .= "\t" . $this->viewportHeightCss() . ";\n";
		$css .= "}\n";
		$css .= "@media (max-width: 1024px) {\n";
		$css .= "\tbody.login #login #nav,\n";
		$css .= "\tbody.login #login #backtoblog {\n";
		$css .= "\t\tmargin-left: 0;\n";
		$css .= "\t\tmargin-right: 0;\n";
		$css .= "\t\tpadding-left: 0;\n";
		$css .= "\t\tpadding-right: 0;\n";
		$css .= "\t}\n";
		$css .= "}\n";
		$css .= "@media (max-width: 600px) {\n";
		$css .= "\t" . $box_selector . " {\n";
		$css .= "\t\twidth: 100% !important;\n";
		$css .= "\t\tmax-width: calc(100vw - 32px) !important;\n";
		$css .= "\t\tbox-sizing: border-box !important;\n";
		$css .= "\t}\n";
		$css .= "\t" . $form_selector . " input[type=\"text\"],\n";
		$css .= "\t" . $form_selector . " input[type=\"password\"],\n";
		$css .= "\t" . $form_selector . " input[type=\"email\"],\n";
		$css .= "\t" . $form_selector . " input[type=\"submit\"],\n";
		$css .= "\t" . $form_selector . " .button:not(.wp-hide-pw):not(.show-password-input) {\n";
		$css .= "\t\tmax-width: 100%;\n";
		$css .= "\t\tbox-sizing: border-box;\n";
		$css .= "\t}\n";
		$css .= "\tbody.login:not(.wpps-login-split-layout) {\n";
		$css .= "\t\tpadding-left: 16px !important;\n";
		$css .= "\t\tpadding-right: 16px !important;\n";
		$css .= "\t}\n";
		$css .= "}\n";

		wp_add_inline_style( 'login', $css );
	}

	/**
	 * Enqueue CSS for classic layout page background (Color, Gradient, Image, or Video).
	 */
	public function enqueueClassicLayoutBackgroundStyles(): void {
		$settings   = $this->getSettings();
		$bg_type    = isset( $settings['classic_bg_type'] ) ? (string) $settings['classic_bg_type'] : '';
		if ( ! in_array( $bg_type, [ 'color', 'gradient', 'image', 'video' ], true ) ) {
			return;
		}
		$rules    = [];
		$css_extra = '';
		if ( $bg_type === 'color' ) {
			$color = isset( $settings['classic_bg_color'] ) ? trim( (string) $settings['classic_bg_color'] ) : '#f0f0f1';
			if ( $color === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $color ) ) {
				$color = '#f0f0f1';
			}
			$rules[] = 'background-color: ' . $color . ';';
		}
		if ( $bg_type === 'gradient' ) {
			$gradient_css = $this->buildGradientCss( $settings, 'classic_bg_gradient' );
			if ( $gradient_css !== '' ) {
				$rules[] = 'background: ' . $gradient_css . ';';
			}
		}
		if ( $bg_type === 'image' ) {
			$url = isset( $settings['classic_bg_image'] ) ? trim( (string) $settings['classic_bg_image'] ) : '';
			if ( $url !== '' ) {
				$url = esc_url( $url );
				$rules[] = 'background-image: url(' . $url . ');';
				$size = isset( $settings['classic_bg_image_size'] ) ? (string) $settings['classic_bg_image_size'] : 'cover';
				$rules[] = 'background-size: ' . ( in_array( $size, [ 'auto', 'cover', 'contain' ], true ) ? $size : 'cover' ) . ';';
				$pos = isset( $settings['classic_bg_image_position'] ) ? (string) $settings['classic_bg_image_position'] : 'center center';
				$valid_pos = [ 'left top', 'center top', 'right top', 'left center', 'center center', 'right center', 'left bottom', 'center bottom', 'right bottom' ];
				$rules[] = 'background-position: ' . ( in_array( $pos, $valid_pos, true ) ? $pos : 'center center' ) . ';';
				$repeat = isset( $settings['classic_bg_image_repeat'] ) ? (string) $settings['classic_bg_image_repeat'] : 'no-repeat';
				$rules[] = 'background-repeat: ' . ( in_array( $repeat, [ 'repeat', 'no-repeat', 'repeat-x', 'repeat-y' ], true ) ? $repeat : 'no-repeat' ) . ';';
			}
			$overlay_color   = isset( $settings['classic_bg_image_overlay_color'] ) ? trim( (string) $settings['classic_bg_image_overlay_color'] ) : '#000000';
			$overlay_opacity = isset( $settings['classic_bg_image_overlay_opacity'] ) ? trim( (string) $settings['classic_bg_image_overlay_opacity'] ) : '0';
			$overlay_opacity_val = $overlay_opacity !== '' && is_numeric( $overlay_opacity ) ? max( 0, min( 100, (int) $overlay_opacity ) ) : 0;
			if ( $overlay_opacity_val > 0 && preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $overlay_color ) ) {
				$css_extra .= "\nbody.login { position: relative; }\n";
				$css_extra .= "body.login::before { content: \"\"; position: absolute; inset: 0; background-color: " . $overlay_color . "; opacity: " . ( $overlay_opacity_val / 100 ) . "; pointer-events: none; z-index: 0; }\n";
				$css_extra .= "body.login > * { position: relative; z-index: 1; }\n";
			}
		}
		if ( $bg_type === 'video' ) {
			// Video is rendered as an element; body needs to allow positioning.
			$rules[] = 'position: relative;';
		}
		if ( empty( $rules ) && $bg_type !== 'video' ) {
			return;
		}
		$css = "\nbody.login { " . implode( ' ', $rules ) . ' ' . $this->viewportHeightCss() . " }\n";
		$css .= $css_extra;
		if ( $bg_type === 'video' ) {
			$css .= "body.login.wpps-classic-video-bg { background-color: #000; position: relative; }\n";
			$css .= ".wpps-classic-video-wrap { position: fixed; inset: 0; z-index: -1; overflow: hidden; }\n";
			$css .= ".wpps-classic-video-wrap video { width: 100%; height: 100%; object-fit: cover; }\n";
			/* Keep login content above video; do not give the video wrap z-index 1 */
			$css .= "body.login.wpps-classic-video-bg > *:not(.wpps-classic-video-wrap) { position: relative; z-index: 1; }\n";
			$overlay_color   = isset( $settings['classic_bg_video_overlay_color'] ) ? trim( (string) $settings['classic_bg_video_overlay_color'] ) : '#000000';
			$overlay_opacity = isset( $settings['classic_bg_video_overlay_opacity'] ) ? trim( (string) $settings['classic_bg_video_overlay_opacity'] ) : '0';
			$overlay_opacity_val = $overlay_opacity !== '' && is_numeric( $overlay_opacity ) ? max( 0, min( 100, (int) $overlay_opacity ) ) : 0;
			if ( $overlay_opacity_val > 0 && preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $overlay_color ) ) {
				$css .= "body.login.wpps-classic-video-bg::before { content: \"\"; position: fixed; inset: 0; background-color: " . $overlay_color . "; opacity: " . ( $overlay_opacity_val / 100 ) . "; pointer-events: none; z-index: 0; }\n";
			}
		}
		wp_add_inline_style( 'login', $css );
	}

	/**
	 * Add body class when classic layout uses video background.
	 *
	 * @param array<int, string> $classes Existing body classes.
	 * @param string             $action  Login action.
	 * @return array<int, string>
	 */
	public function addClassicVideoBodyClass( array $classes, string $action ): array {
		$classes[] = 'wpps-classic-video-bg';
		return $classes;
	}

	/**
	 * Output video element for classic layout video background.
	 */
	public function outputClassicVideoBackground(): void {
		$settings = $this->getSettings();
		$url = isset( $settings['classic_bg_video'] ) ? trim( (string) $settings['classic_bg_video'] ) : '';
		if ( $url === '' ) {
			return;
		}
		$url = esc_url( $url );
		echo '<div class="wpps-classic-video-wrap" aria-hidden="true"><video autoplay muted loop playsinline src="' . esc_attr( $url ) . '"></video></div>';
	}

	/**
	 * Output script to start video playback (helps when autoplay attribute is blocked).
	 */
	public function outputClassicVideoPlayScript(): void {
		?>
		<script>
		(function() {
			var v = document.querySelector('.wpps-classic-video-wrap video');
			if (v && v.pause) {
				v.muted = true;
				v.play().catch(function() {});
			}
		})();
		</script>
		<?php
	}

	/**
	 * Output CSS to hide "Back to Site" and/or "Lost Password" links on the login page.
	 */
	public function outputHideBackAndLostPasswordStyles(): void {
		$settings = $this->getSettings();
		$hide_back = ! empty( $settings['hide_back_link'] ) && $settings['hide_back_link'] !== '0';
		$hide_lost = ! empty( $settings['hide_lost_password'] ) && $settings['hide_lost_password'] !== '0';
		if ( ! $hide_back && ! $hide_lost ) {
			return;
		}
		$rules = [];
		if ( $hide_back ) {
			$rules[] = '.login:not(.interim-login) #backtoblog { display: none !important; }';
		}
		if ( $hide_lost ) {
			$rules[] = '.login:not(.interim-login) #nav { display: none !important; }';
		}
		$css = "\n" . implode( "\n", $rules ) . "\n";
		wp_add_inline_style( 'login', $css );
	}

	/**
	 * Output CSS to hide the "Remember Me" checkbox on the login page.
	 */
	public function outputHideRememberMeStyles(): void {
		$css = "\n.login:not(.interim-login) .forgetmenot { display: none !important; }\n";
		wp_add_inline_style( 'login', $css );
	}

	/**
	 * Output user-defined custom CSS on the login page.
	 */
	public function outputCustomCss(): void {
		$settings   = $this->getSettings();
		$custom_css = isset( $settings['custom_css'] ) ? (string) $settings['custom_css'] : '';
		$custom_css = $this->sanitizeCustomCss( $custom_css );
		if ( $custom_css === '' ) {
			return;
		}
		wp_add_inline_style( 'login', "\n" . $custom_css . "\n" );
	}

	/**
	 * Strip dangerous content from custom CSS while preserving valid rules.
	 */
	private function sanitizeCustomCss( string $css ): string {
		if ( $css === '' ) {
			return '';
		}
		$css = preg_replace( '/<script\b[^>]*>(.*?)<\/script>/is', '', $css );
		$css = wp_strip_all_tags( $css, true );
		$css = preg_replace( '/javascript\s*:/i', '', $css );
		$css = preg_replace( '/expression\s*\(/i', '', $css );
		$css = preg_replace( '/@import/i', '', $css );
		$css = preg_replace( '/behavior\s*:/i', '', $css );
		return trim( $css );
	}

	/**
	 * Output CSS for Logo tab: replace logo, alignment, responsive widths.
	 */
	public function applyFormCustomizationLogoStyles(): void {
		$settings = $this->getSettings();
		$hide_logo = ! empty( $settings['hide_logo'] ) && $settings['hide_logo'] !== '0';
		if ( $hide_logo ) {
			$css = "\n.login:not(.interim-login) h1 { display: none !important; }\n";
			wp_add_inline_style( 'login', $css );
			return;
		}

		$logo_url   = $this->resolveLogoUrl( $settings );
		$logo_source = isset( $settings['logo_source'] ) ? (string) $settings['logo_source'] : 'upload';
		$raw_align  = isset( $settings['logo_alignment'] ) ? (string) $settings['logo_alignment'] : 'center';
		$alignment  = in_array( $raw_align, [ 'left', 'center', 'right' ], true ) ? $raw_align : 'center';
		$logo_link  = isset( $settings['logo_link'] ) ? trim( (string) $settings['logo_link'] ) : '';
		$logo_margin_top    = isset( $settings['logo_margin_top'] ) ? trim( (string) $settings['logo_margin_top'] ) : '';
		$logo_margin_bottom = isset( $settings['logo_margin_bottom'] ) ? trim( (string) $settings['logo_margin_bottom'] ) : '';
		$w_desktop = $this->parseConfiguredLogoWidth( $settings, 'logo_width_desktop' );
		$w_tablet  = $this->parseConfiguredLogoWidth( $settings, 'logo_width_tablet' );
		$w_mobile  = $this->parseConfiguredLogoWidth( $settings, 'logo_width_mobile' );
		if ( $w_desktop === null ) {
			$w_desktop = $this->getDefaultLogoWidthPx( $settings );
		}

		$rules = [];
		$h1_parts = [];
		if ( $logo_margin_top !== '' && is_numeric( $logo_margin_top ) && (int) $logo_margin_top >= 0 ) {
			$h1_parts[] = 'margin-top: ' . (int) $logo_margin_top . 'px';
		}
		if ( $logo_margin_bottom !== '' && is_numeric( $logo_margin_bottom ) && (int) $logo_margin_bottom >= 0 ) {
			$h1_parts[] = 'margin-bottom: ' . (int) $logo_margin_bottom . 'px';
		} else {
			$h1_parts[] = 'margin-bottom: 1em';
		}
		$h1_parts[] = 'text-align: ' . $alignment;
		$rules[] = '.login:not(.interim-login) h1 { ' . implode( '; ', $h1_parts ) . '; }';
		$rules[] = '.login:not(.interim-login) h1 a { display: inline-block; }';

		if ( $logo_url !== '' && $logo_source === 'site_icon' ) {
			$rules[] = '.login:not(.interim-login) h1 a { background-image: url(' . $logo_url . ') !important; background-repeat: no-repeat !important; background-position: center center !important; }';
			$rules[] = '.login:not(.interim-login) h1 a { width: ' . $w_desktop . 'px !important; background-size: contain !important; }';
			if ( $w_tablet !== null ) {
				$rules[] = '@media (max-width: 1024px) { .login:not(.interim-login) h1 a { width: ' . $w_tablet . 'px !important; background-size: contain !important; } }';
			}
			if ( $w_mobile !== null ) {
				$rules[] = '@media (max-width: 600px) { .login:not(.interim-login) h1 a { width: ' . $w_mobile . 'px !important; background-size: contain !important; } }';
			}
		} elseif ( $logo_url !== '' ) {
			// Custom logo: img scales by aspect ratio, no crop. Container must not clip.
			$rules[] = '.login:not(.interim-login) h1 a { background-image: none !important; width: auto !important; height: auto !important; overflow: visible !important; padding: 0 !important; }';
			$rules[] = '.login h1 a .wpps-login-logo-img { max-width: ' . $w_desktop . 'px; width: auto; height: auto; display: block; object-fit: contain; vertical-align: middle; }';
			if ( $w_tablet !== null ) {
				$rules[] = '@media (max-width: 1024px) { .login h1 a .wpps-login-logo-img { max-width: ' . $w_tablet . 'px; } }';
			}
			if ( $w_mobile !== null ) {
				$rules[] = '@media (max-width: 600px) { .login h1 a .wpps-login-logo-img { max-width: ' . $w_mobile . 'px; } }';
			}
		} else {
			$rules[] = '.login h1 a { width: ' . $w_desktop . 'px !important; background-size: ' . $w_desktop . 'px auto !important; }';
			if ( $w_tablet !== null ) {
				$rules[] = '@media (max-width: 1024px) { .login h1 a { width: ' . $w_tablet . 'px !important; background-size: ' . $w_tablet . 'px auto !important; } }';
			}
			if ( $w_mobile !== null ) {
				$rules[] = '@media (max-width: 600px) { .login h1 a { width: ' . $w_mobile . 'px !important; background-size: ' . $w_mobile . 'px auto !important; } }';
			}
		}

		$css = "\n" . implode( "\n", $rules );
		wp_add_inline_style( 'login', $css );
	}

	/**
	 * Build CSS value for gradient from stored JSON.
	 *
	 * @param array<string, mixed> $settings Module settings.
	 * @param string               $key     Settings key (e.g. 'box_bg_gradient', 'form_bg_gradient').
	 * @return string Empty string if invalid, else e.g. linear-gradient(135deg, #fff 0%, #eee 100%).
	 */
	private function buildGradientCss( array $settings, string $key = 'box_bg_gradient' ): string {
		$raw = isset( $settings[ $key ] ) ? $settings[ $key ] : '';
		if ( ! is_string( $raw ) || $raw === '' ) {
			return '';
		}
		$data = json_decode( $raw, true );
		if ( ! is_array( $data ) || empty( $data['stops'] ) || ! is_array( $data['stops'] ) ) {
			return '';
		}
		$type  = isset( $data['type'] ) && $data['type'] === 'radial' ? 'radial' : 'linear';
		$angle = isset( $data['angle'] ) ? max( 0, min( 360, (int) $data['angle'] ) ) : 135;
		$stops = [];
		foreach ( $data['stops'] as $stop ) {
			if ( ! is_array( $stop ) || ! isset( $stop['color'] ) ) {
				continue;
			}
			$color = trim( (string) $stop['color'] );
			if ( $color === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $color ) ) {
				$color = '#ffffff';
			}
			$pos = isset( $stop['pos'] ) ? max( 0, min( 100, (int) $stop['pos'] ) ) : 0;
			$stops[] = $color . ' ' . $pos . '%';
		}
		if ( count( $stops ) < 2 ) {
			return '';
		}
		if ( $type === 'radial' ) {
			return 'radial-gradient(circle, ' . implode( ', ', $stops ) . ')';
		}
		return 'linear-gradient(' . $angle . 'deg, ' . implode( ', ', $stops ) . ')';
	}

	/**
	 * Normalize shadow color hex (including 8-digit alpha) for box-shadow CSS.
	 *
	 * @param string $color Hex color (#RGB, #RRGGBB, or #RRGGBBAA).
	 * @return string CSS color value (#RRGGBB or rgba()).
	 */
	private function formatShadowColorForCss( string $color ): string {
		$color = trim( $color );
		if ( $color === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $color, $m ) ) {
			return '#000000';
		}
		$hex = $m[1];
		if ( strlen( $hex ) === 3 ) {
			$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
		}
		$r = hexdec( substr( $hex, 0, 2 ) );
		$g = hexdec( substr( $hex, 2, 2 ) );
		$b = hexdec( substr( $hex, 4, 2 ) );
		if ( strlen( $hex ) === 8 ) {
			$a = hexdec( substr( $hex, 6, 2 ) ) / 255;
			if ( $a >= 0.999 ) {
				return sprintf( '#%02x%02x%02x', $r, $g, $b );
			}
			$a_str = rtrim( rtrim( sprintf( '%.2f', $a ), '0' ), '.' );
			return sprintf( 'rgba(%d,%d,%d,%s)', $r, $g, $b, $a_str );
		}
		return sprintf( '#%02x%02x%02x', $r, $g, $b );
	}

	/**
	 * Resolve button border radius for a state and device, with legacy single-value fallback on desktop.
	 *
	 * @param array<string, mixed> $settings Module settings.
	 * @param string               $state    'normal' or 'hover'.
	 * @param string               $device   'desktop', 'tablet', or 'mobile'.
	 * @return string Empty string when unset.
	 */
	private function resolveButtonBorderRadius( array $settings, string $state, string $device = 'desktop' ): string {
		$suffix = '_desktop';
		if ( $device === 'tablet' ) {
			$suffix = '_tablet';
		} elseif ( $device === 'mobile' ) {
			$suffix = '_mobile';
		}
		$key = 'button_' . $state . '_border_radius' . $suffix;
		$val = isset( $settings[ $key ] ) ? trim( (string) $settings[ $key ] ) : '';
		if ( $val !== '' ) {
			return $val;
		}
		if ( $device === 'desktop' ) {
			$legacy_key = 'button_' . $state . '_border_radius';
			return isset( $settings[ $legacy_key ] ) ? trim( (string) $settings[ $legacy_key ] ) : '';
		}
		return '';
	}

	/**
	 * Resolve button label color for normal or hover (hover mirrors normal when unset).
	 *
	 * @param array<string, mixed> $settings Module settings.
	 */
	private function resolveButtonTextColor( array $settings, string $state ): string {
		$key   = $state === 'hover' ? 'button_hover_text_color' : 'button_normal_text_color';
		$color = isset( $settings[ $key ] ) ? trim( (string) $settings[ $key ] ) : '';
		if ( $color !== '' && preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $color ) ) {
			if ( $state === 'hover' ) {
				$normal = $this->resolveButtonTextColor( $settings, 'normal' );
				// Stale saves: empty hover cleared in the form but color picker left #000000.
				if ( strtolower( $color ) === '#000000' && $normal !== '' && strtolower( $normal ) !== '#000000' ) {
					return $normal;
				}
			}
			return $color;
		}
		if ( $state === 'hover' ) {
			return $this->resolveButtonTextColor( $settings, 'normal' );
		}

		return '';
	}

	/**
	 * Build per-side padding CSS declarations for a breakpoint.
	 *
	 * @param array<string, mixed> $settings       Module settings.
	 * @param string               $prefix         Settings prefix ('box' or 'form').
	 * @param string               $device_suffix '' | '_tablet' | '_mobile'.
	 * @return array<int, string>
	 */
	private function buildPaddingCssRules( array $settings, string $prefix, string $device_suffix ): array {
		$legacy_key = $prefix . '_padding_desktop';
		if ( $device_suffix === '_tablet' ) {
			$legacy_key = $prefix . '_padding_tablet';
		} elseif ( $device_suffix === '_mobile' ) {
			$legacy_key = $prefix . '_padding_mobile';
		}
		$legacy = isset( $settings[ $legacy_key ] ) ? trim( (string) $settings[ $legacy_key ] ) : '';
		if ( $legacy === '' && $device_suffix === '' && $prefix === 'box' && isset( $settings['box_padding'] ) ) {
			$legacy = trim( (string) $settings['box_padding'] );
		}

		$rules = [];
		foreach ( [ 'top', 'right', 'bottom', 'left' ] as $side ) {
			$key = $prefix . '_padding_' . $side . $device_suffix;
			$val = isset( $settings[ $key ] ) ? trim( (string) $settings[ $key ] ) : '';
			if ( $val === '' && $legacy !== '' && is_numeric( $legacy ) && (int) $legacy >= 0 ) {
				$val = $legacy;
			}
			if ( $val !== '' && (int) $val >= 0 ) {
				$rules[] = 'padding-' . $side . ': ' . (int) $val . 'px;';
			}
		}

		return $rules;
	}

	/**
	 * Full-viewport height declarations (100dvh with 100vh fallback for mobile browsers).
	 *
	 * @param bool $fixed When true, sets height in addition to min-height.
	 */
	private function viewportHeightCss( bool $fixed = false ): string {
		$css = 'min-height: 100vh; min-height: 100dvh;';
		if ( $fixed ) {
			return 'height: 100vh; height: 100dvh; ' . $css;
		}

		return $css;
	}

	/**
	 * CSS selector for the login box (#login), split-aware for higher specificity.
	 *
	 * @param array<string, mixed> $settings Module settings.
	 */
	private function getLoginBoxCssSelector( array $settings ): string {
		$raw_type = $settings['template_type'] ?? 'classic-centered';
		$template = in_array( $raw_type, [ 'classic-centered', 'split-layout' ], true ) ? $raw_type : 'classic-centered';
		if ( $template === 'split-layout' ) {
			return 'body.wpps-login-split-layout .wpps-login-split-left #login';
		}

		return '#login';
	}

	/**
	 * CSS selector for the login form inside the box.
	 *
	 * @param array<string, mixed> $settings Module settings.
	 */
	private function getLoginFormCssSelector( array $settings ): string {
		return $this->getLoginBoxCssSelector( $settings ) . ' form';
	}

	/**
	 * CSS selector for text inputs on login and 2FA verification steps.
	 *
	 * @param array<string, mixed> $settings Module settings.
	 */
	private function getLoginFieldInputCssSelector( array $settings ): string {
		$box = $this->getLoginBoxCssSelector( $settings );

		return $box . ' form input[type="text"], ' . $box . ' form input[type="password"], ' . $box . ' form input[type="email"]';
	}

	/**
	 * CSS selector for login submit buttons and 2FA action buttons.
	 *
	 * @param array<string, mixed> $settings Module settings.
	 */
	private function getLoginButtonCssSelector( array $settings ): string {
		$box = $this->getLoginBoxCssSelector( $settings );

		return $box . ' form input[type="submit"], ' . $box . ' form .button:not(.wp-hide-pw):not(.show-password-input), ' . $box . ' .wpps-2fa-secondary-actions .button';
	}

	/**
	 * CSS selector for form labels and 2FA helper text.
	 *
	 * @param array<string, mixed> $settings Module settings.
	 */
	private function getLoginLabelCssSelector( array $settings ): string {
		$box = $this->getLoginBoxCssSelector( $settings );

		return $box . ' form label, ' . $box . ' .wpps-2fa-code-hint';
	}

	/**
	 * Output CSS for Box tab: background color or background image on #login.
	 * Only outputs when at least one box or form setting is non-default.
	 */
	public function applyFormCustomizationBoxStyles(): void {
		$settings     = $this->getSettings();
		$box_selector = $this->getLoginBoxCssSelector( $settings );
		$form_selector = $this->getLoginFormCssSelector( $settings );
		$box_inner = [];

		$box_width = isset( $settings['box_form_width'] ) ? trim( (string) $settings['box_form_width'] ) : '';
		if ( $box_width !== '' ) {
			$num = (int) $box_width;
			if ( $num > 0 ) {
				$unit = isset( $settings['box_form_width_unit'] ) && $settings['box_form_width_unit'] === '%' ? '%' : 'px';
				$box_inner[] = 'width: ' . $num . $unit . ';';
				$box_inner[] = 'max-width: 100%;';
				$box_inner[] = 'box-sizing: border-box;';
			}
		}

		$media_rules = [];
		$desktop_padding_rules = $this->buildPaddingCssRules( $settings, 'box', '' );
		if ( ! empty( $desktop_padding_rules ) ) {
			$box_inner = array_merge( $box_inner, $desktop_padding_rules );
			$box_inner[] = 'box-sizing: border-box;';
		}
		$tablet_padding_rules = $this->buildPaddingCssRules( $settings, 'box', '_tablet' );
		if ( ! empty( $tablet_padding_rules ) ) {
			$media_rules[] = '@media (max-width: 1024px) { ' . $box_selector . ' { ' . implode( ' ', $tablet_padding_rules ) . ' } }';
		}
		$mobile_padding_rules = $this->buildPaddingCssRules( $settings, 'box', '_mobile' );
		if ( ! empty( $mobile_padding_rules ) ) {
			$media_rules[] = '@media (max-width: 600px) { ' . $box_selector . ' { ' . implode( ' ', $mobile_padding_rules ) . ' } }';
		}

		$border_type = isset( $settings['box_border_type'] ) ? (string) $settings['box_border_type'] : 'none';
		if ( $border_type !== '' && $border_type !== 'none' ) {
			$bw   = isset( $settings['box_border_width'] ) ? trim( (string) $settings['box_border_width'] ) : '1';
			$bw_val = $bw !== '' && (int) $bw >= 0 ? (int) $bw : 1;
			$box_inner[] = 'border-width: ' . $bw_val . 'px;';
			$box_inner[] = 'border-style: ' . $border_type . ';';
			$color = isset( $settings['box_border_color'] ) ? trim( (string) $settings['box_border_color'] ) : '#cccccc';
			if ( $color === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $color ) ) {
				$color = '#cccccc';
			}
			$box_inner[] = 'border-color: ' . $color . ';';
		} else {
			$box_inner[] = 'border: none;';
		}
		$rad_d = isset( $settings['box_border_radius_desktop'] ) ? trim( (string) $settings['box_border_radius_desktop'] ) : ( isset( $settings['box_border_radius'] ) ? trim( (string) $settings['box_border_radius'] ) : '' );
		$rad_t = isset( $settings['box_border_radius_tablet'] ) ? trim( (string) $settings['box_border_radius_tablet'] ) : '';
		$rad_m = isset( $settings['box_border_radius_mobile'] ) ? trim( (string) $settings['box_border_radius_mobile'] ) : '';
		if ( $rad_d !== '' && (int) $rad_d >= 0 ) {
			$box_inner[] = 'border-radius: ' . (int) $rad_d . 'px;';
		}
		if ( $rad_t !== '' && (int) $rad_t >= 0 ) {
			$media_rules[] = '@media (max-width: 1024px) { ' . $box_selector . ' { border-radius: ' . (int) $rad_t . 'px; } }';
		}
		if ( $rad_m !== '' && (int) $rad_m >= 0 ) {
			$media_rules[] = '@media (max-width: 600px) { ' . $box_selector . ' { border-radius: ' . (int) $rad_m . 'px; } }';
		}

		$sx = isset( $settings['box_shadow_x'] ) ? (int) $settings['box_shadow_x'] : 0;
		$sy = isset( $settings['box_shadow_y'] ) ? (int) $settings['box_shadow_y'] : 0;
		$sb = isset( $settings['box_shadow_blur'] ) ? (int) $settings['box_shadow_blur'] : 0;
		$ss = isset( $settings['box_shadow_spread'] ) ? (int) $settings['box_shadow_spread'] : 0;
		if ( $sx !== 0 || $sy !== 0 || $sb !== 0 || $ss !== 0 ) {
			$sc = $this->formatShadowColorForCss( isset( $settings['box_shadow_color'] ) ? (string) $settings['box_shadow_color'] : '#000000' );
			$inset = ! empty( $settings['box_shadow_inset'] ) && $settings['box_shadow_inset'] !== '0';
			$box_inner[] = 'box-shadow: ' . $sx . 'px ' . $sy . 'px ' . $sb . 'px ' . $ss . 'px ' . $sc . ( $inset ? ' inset' : '' ) . ';';
		}

		$bg_type = isset( $settings['box_bg_type'] ) ? (string) $settings['box_bg_type'] : '';
		if ( $bg_type === 'color' ) {
			$color = isset( $settings['box_bg_color'] ) ? trim( (string) $settings['box_bg_color'] ) : '#ffffff';
			if ( $color === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $color ) ) {
				$color = '#ffffff';
			}
			$box_inner[] = 'background-color: ' . $color . ';';
		}
		if ( $bg_type === 'image' ) {
			$url = isset( $settings['box_bg_image'] ) ? trim( (string) $settings['box_bg_image'] ) : '';
			if ( $url !== '' ) {
				$url = esc_url( $url );
				$box_inner[] = 'position: relative;';
				$box_inner[] = 'background-image: url(' . $url . ');';
				$box_inner[] = 'background-size: cover;';
				$box_inner[] = 'background-position: center;';
				$box_inner[] = 'background-repeat: no-repeat;';
				$overlay_color = isset( $settings['box_bg_image_overlay_color'] ) ? trim( (string) $settings['box_bg_image_overlay_color'] ) : '#000000';
				if ( $overlay_color === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $overlay_color ) ) {
					$overlay_color = '#000000';
				}
				$overlay_opacity = isset( $settings['box_bg_image_overlay_opacity'] ) ? trim( (string) $settings['box_bg_image_overlay_opacity'] ) : '0';
				$overlay_opacity_val = $overlay_opacity !== '' && is_numeric( $overlay_opacity ) ? max( 0, min( 100, (int) $overlay_opacity ) ) : 0;
				if ( $overlay_opacity_val > 0 ) {
					$media_rules[] = $box_selector . '::before { content: ""; position: absolute; inset: 0; background-color: ' . $overlay_color . '; opacity: ' . ( $overlay_opacity_val / 100 ) . '; border-radius: inherit; pointer-events: none; z-index: 0; }';
					$media_rules[] = $box_selector . ' > * { position: relative; z-index: 1; }';
				}
			}
		}
		if ( $bg_type === 'gradient' ) {
			$gradient_css = $this->buildGradientCss( $settings, 'box_bg_gradient' );
			if ( $gradient_css !== '' ) {
				$box_inner[] = 'background: ' . $gradient_css . ';';
			}
		}

		// Form section: #login form
		$form_inner = [];
		$form_section_width = isset( $settings['form_width'] ) ? trim( (string) $settings['form_width'] ) : '';
		if ( $form_section_width !== '' ) {
			$form_num = (int) $form_section_width;
			if ( $form_num > 0 ) {
				$form_unit = isset( $settings['form_width_unit'] ) && $settings['form_width_unit'] === '%' ? '%' : 'px';
				$form_inner[] = 'width: ' . $form_num . $form_unit . ';';
				$form_inner[] = 'max-width: 100%;';
				$form_inner[] = 'box-sizing: border-box;';
			}
		}
		$desktop_form_padding_rules = $this->buildPaddingCssRules( $settings, 'form', '' );
		if ( ! empty( $desktop_form_padding_rules ) ) {
			$form_inner = array_merge( $form_inner, $desktop_form_padding_rules );
		}
		$tablet_form_padding_rules = $this->buildPaddingCssRules( $settings, 'form', '_tablet' );
		if ( ! empty( $tablet_form_padding_rules ) ) {
			$media_rules[] = '@media (max-width: 1024px) { ' . $form_selector . ' { ' . implode( ' ', $tablet_form_padding_rules ) . ' } }';
		}
		$mobile_form_padding_rules = $this->buildPaddingCssRules( $settings, 'form', '_mobile' );
		if ( ! empty( $mobile_form_padding_rules ) ) {
			$media_rules[] = '@media (max-width: 600px) { ' . $form_selector . ' { ' . implode( ' ', $mobile_form_padding_rules ) . ' } }';
		}
		$form_border_type = isset( $settings['form_border_type'] ) ? (string) $settings['form_border_type'] : 'none';
		$form_border_is_none = ( $form_border_type === '' || $form_border_type === 'none' );
		if ( ! $form_border_is_none ) {
			$fbw = isset( $settings['form_border_width'] ) ? trim( (string) $settings['form_border_width'] ) : '1';
			$fbw_val = $fbw !== '' && (int) $fbw >= 0 ? (int) $fbw : 1;
			$form_inner[] = 'border-width: ' . $fbw_val . 'px;';
			$form_inner[] = 'border-style: ' . $form_border_type . ';';
			$fcolor = isset( $settings['form_border_color'] ) ? trim( (string) $settings['form_border_color'] ) : '#cccccc';
			if ( $fcolor === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $fcolor ) ) {
				$fcolor = '#cccccc';
			}
			$form_inner[] = 'border-color: ' . $fcolor . ';';
			$frad_d = isset( $settings['form_border_radius_desktop'] ) ? trim( (string) $settings['form_border_radius_desktop'] ) : '';
			$frad_t = isset( $settings['form_border_radius_tablet'] ) ? trim( (string) $settings['form_border_radius_tablet'] ) : '';
			$frad_m = isset( $settings['form_border_radius_mobile'] ) ? trim( (string) $settings['form_border_radius_mobile'] ) : '';
			if ( $frad_d !== '' && (int) $frad_d >= 0 ) {
				$form_inner[] = 'border-radius: ' . (int) $frad_d . 'px;';
			}
			if ( $frad_t !== '' && (int) $frad_t >= 0 ) {
				$media_rules[] = '@media (max-width: 1024px) { ' . $form_selector . ' { border-radius: ' . (int) $frad_t . 'px; } }';
			}
			if ( $frad_m !== '' && (int) $frad_m >= 0 ) {
				$media_rules[] = '@media (max-width: 600px) { ' . $form_selector . ' { border-radius: ' . (int) $frad_m . 'px; } }';
			}
		} else {
			$form_inner[] = 'border: none;';
		}
		$form_bg_type = isset( $settings['form_bg_type'] ) ? (string) $settings['form_bg_type'] : '';
		if ( $form_bg_type === 'color' ) {
			$fcolor = isset( $settings['form_bg_color'] ) ? trim( (string) $settings['form_bg_color'] ) : '#ffffff';
			if ( $fcolor === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $fcolor ) ) {
				$fcolor = '#ffffff';
			}
			$form_inner[] = 'background-color: ' . $fcolor . ';';
		}
		if ( $form_bg_type === 'image' ) {
			$furl = isset( $settings['form_bg_image'] ) ? trim( (string) $settings['form_bg_image'] ) : '';
			if ( $furl !== '' ) {
				$furl = esc_url( $furl );
				$form_inner[] = 'position: relative;';
				$form_inner[] = 'background-image: url(' . $furl . ');';
				$form_inner[] = 'background-size: cover;';
				$form_inner[] = 'background-position: center;';
				$form_inner[] = 'background-repeat: no-repeat;';
				$form_overlay_color = isset( $settings['form_bg_image_overlay_color'] ) ? trim( (string) $settings['form_bg_image_overlay_color'] ) : '#000000';
				if ( $form_overlay_color === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $form_overlay_color ) ) {
					$form_overlay_color = '#000000';
				}
				$form_overlay_opacity = isset( $settings['form_bg_image_overlay_opacity'] ) ? trim( (string) $settings['form_bg_image_overlay_opacity'] ) : '0';
				$form_overlay_opacity_val = $form_overlay_opacity !== '' && is_numeric( $form_overlay_opacity ) ? max( 0, min( 100, (int) $form_overlay_opacity ) ) : 0;
				if ( $form_overlay_opacity_val > 0 ) {
					$media_rules[] = $form_selector . '::before { content: ""; position: absolute; inset: 0; background-color: ' . $form_overlay_color . '; opacity: ' . ( $form_overlay_opacity_val / 100 ) . '; border-radius: inherit; pointer-events: none; z-index: 0; }';
					$media_rules[] = $form_selector . ' > * { position: relative; z-index: 1; }';
				}
			}
		}
		if ( $form_bg_type === 'gradient' ) {
			$form_gradient_css = $this->buildGradientCss( $settings, 'form_bg_gradient' );
			if ( $form_gradient_css !== '' ) {
				$form_inner[] = 'background: ' . $form_gradient_css . ';';
			}
		}
		$fsx = isset( $settings['form_shadow_x'] ) ? (int) $settings['form_shadow_x'] : 0;
		$fsy = isset( $settings['form_shadow_y'] ) ? (int) $settings['form_shadow_y'] : 0;
		$fsb = isset( $settings['form_shadow_blur'] ) ? (int) $settings['form_shadow_blur'] : 0;
		$fss = isset( $settings['form_shadow_spread'] ) ? (int) $settings['form_shadow_spread'] : 0;
		if ( $fsx !== 0 || $fsy !== 0 || $fsb !== 0 || $fss !== 0 ) {
			$fsc = $this->formatShadowColorForCss( isset( $settings['form_shadow_color'] ) ? (string) $settings['form_shadow_color'] : '#000000' );
			$finset = ! empty( $settings['form_shadow_inset'] ) && $settings['form_shadow_inset'] !== '0';
			$form_inner[] = 'box-shadow: ' . $fsx . 'px ' . $fsy . 'px ' . $fsb . 'px ' . $fss . 'px ' . $fsc . ( $finset ? ' inset' : '' ) . ';';
		} elseif ( $form_border_is_none ) {
			$form_inner[] = 'box-shadow: none;';
		}

		if ( empty( $box_inner ) && empty( $form_inner ) && empty( $media_rules ) ) {
			return;
		}
		$css = '';
		if ( ! empty( $box_inner ) ) {
			$css .= "\n" . $box_selector . ' { ' . implode( ' ', $box_inner ) . " }\n";
		}
		if ( ! empty( $form_inner ) ) {
			$css .= $form_selector . ' { ' . implode( ' ', $form_inner ) . " }\n";
			// 2FA step: strip default card chrome so only #login / form settings apply.
			$css .= $box_selector . ' .wpps-2fa-card { border: none; box-shadow: none; background: transparent; padding: 0; max-width: 100%; width: 100%; }' . "\n";
		}
		if ( ! empty( $media_rules ) ) {
			$css .= "\n" . implode( "\n", $media_rules ) . "\n";
		}
		if ( $css !== '' ) {
			wp_add_inline_style( 'login', $css );
		}
	}

	/**
	 * Output CSS for Fields tab: typography and normal/focus state on #login form inputs.
	 * Only outputs when at least one field setting is non-default (no CSS when all defaults).
	 */
	public function applyFormCustomizationFieldStyles(): void {
		$settings = $this->getSettings();
		$selector = $this->getLoginFieldInputCssSelector( $settings );
		$rules    = [];

		$font_family = isset( $settings['field_font_family'] ) ? trim( (string) $settings['field_font_family'] ) : '';
		if ( $font_family !== '' ) {
			$rules[] = 'font-family: ' . $font_family . ';';
		}
		$font_size_d  = isset( $settings['field_font_size_desktop'] ) ? trim( (string) $settings['field_font_size_desktop'] ) : ( isset( $settings['field_font_size'] ) ? trim( (string) $settings['field_font_size'] ) : '' );
		$font_size_t  = isset( $settings['field_font_size_tablet'] ) ? trim( (string) $settings['field_font_size_tablet'] ) : '';
		$font_size_m  = isset( $settings['field_font_size_mobile'] ) ? trim( (string) $settings['field_font_size_mobile'] ) : '';
		if ( $font_size_d !== '' && (int) $font_size_d >= 10 && (int) $font_size_d <= 72 ) {
			$rules[] = 'font-size: ' . (int) $font_size_d . 'px;';
		}
		$font_weight = isset( $settings['field_font_weight'] ) ? trim( (string) $settings['field_font_weight'] ) : '';
		if ( $font_weight !== '' && in_array( $font_weight, [ '300', '400', '500', '600', '700' ], true ) ) {
			$rules[] = 'font-weight: ' . $font_weight . ';';
		}
		$line_height_d = isset( $settings['field_line_height_desktop'] ) ? trim( (string) $settings['field_line_height_desktop'] ) : ( isset( $settings['field_line_height'] ) ? trim( (string) $settings['field_line_height'] ) : '' );
		$line_height_t = isset( $settings['field_line_height_tablet'] ) ? trim( (string) $settings['field_line_height_tablet'] ) : '';
		$line_height_m = isset( $settings['field_line_height_mobile'] ) ? trim( (string) $settings['field_line_height_mobile'] ) : '';
		if ( $line_height_d !== '' && is_numeric( $line_height_d ) && (float) $line_height_d >= 0.5 && (float) $line_height_d <= 3 ) {
			$rules[] = 'line-height: ' . (float) $line_height_d . ';';
		}
		$letter_spacing_d = isset( $settings['field_letter_spacing_desktop'] ) ? trim( (string) $settings['field_letter_spacing_desktop'] ) : ( isset( $settings['field_letter_spacing'] ) ? trim( (string) $settings['field_letter_spacing'] ) : '' );
		$letter_spacing_t = isset( $settings['field_letter_spacing_tablet'] ) ? trim( (string) $settings['field_letter_spacing_tablet'] ) : '';
		$letter_spacing_m = isset( $settings['field_letter_spacing_mobile'] ) ? trim( (string) $settings['field_letter_spacing_mobile'] ) : '';
		if ( $letter_spacing_d !== '' && is_numeric( $letter_spacing_d ) ) {
			$rules[] = 'letter-spacing: ' . (float) $letter_spacing_d . 'px;';
		}
		$text_transform = isset( $settings['field_text_transform'] ) ? (string) $settings['field_text_transform'] : '';
		if ( $text_transform !== '' && in_array( $text_transform, [ 'none', 'lowercase', 'uppercase', 'capitalize' ], true ) ) {
			$rules[] = 'text-transform: ' . $text_transform . ';';
		}
		$font_style = isset( $settings['field_font_style'] ) ? (string) $settings['field_font_style'] : '';
		if ( $font_style !== '' && in_array( $font_style, [ 'normal', 'italic', 'oblique' ], true ) ) {
			$rules[] = 'font-style: ' . $font_style . ';';
		}

		// Normal state: text color, background, overlay, border, border radius
		$normal_color = isset( $settings['field_normal_text_color'] ) ? trim( (string) $settings['field_normal_text_color'] ) : '';
		if ( $normal_color !== '' && preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $normal_color ) ) {
			$rules[] = 'color: ' . $normal_color . ';';
		}
		$normal_bg_color = isset( $settings['field_normal_bg_color'] ) ? trim( (string) $settings['field_normal_bg_color'] ) : '';
		$normal_bg_opacity = isset( $settings['field_normal_bg_overlay_opacity'] ) ? trim( (string) $settings['field_normal_bg_overlay_opacity'] ) : '100';
		$normal_bg_opacity_val = $normal_bg_opacity !== '' && is_numeric( $normal_bg_opacity ) ? max( 0, min( 100, (int) $normal_bg_opacity ) ) : 100;
		if ( $normal_bg_color !== '' && preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $normal_bg_color ) ) {
			if ( $normal_bg_opacity_val >= 100 ) {
				$rules[] = 'background-color: ' . $normal_bg_color . ';';
			} else {
				$hex = strlen( $normal_bg_color ) === 4 ? '#' . $normal_bg_color[1] . $normal_bg_color[1] . $normal_bg_color[2] . $normal_bg_color[2] . $normal_bg_color[3] . $normal_bg_color[3] : $normal_bg_color;
				$r = (int) hexdec( substr( $hex, 1, 2 ) );
				$g = (int) hexdec( substr( $hex, 3, 2 ) );
				$b = (int) hexdec( substr( $hex, 5, 2 ) );
				$alpha = $normal_bg_opacity_val / 100;
				$rules[] = 'background-color: rgba(' . $r . ',' . $g . ',' . $b . ',' . $alpha . ');';
			}
		}
		$normal_border_type = isset( $settings['field_normal_border_type'] ) ? (string) $settings['field_normal_border_type'] : 'none';
		if ( $normal_border_type !== '' && $normal_border_type !== 'none' ) {
			$normal_border_color = isset( $settings['field_normal_border_color'] ) ? trim( (string) $settings['field_normal_border_color'] ) : '#cccccc';
			if ( $normal_border_color === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $normal_border_color ) ) {
				$normal_border_color = '#cccccc';
			}
			$normal_border_width = isset( $settings['field_normal_border_width'] ) ? max( 0, min( 50, (int) $settings['field_normal_border_width'] ) ) : 1;
			if ( $normal_border_width < 1 ) {
				$normal_border_width = 1;
			}
			$rules[] = 'border: ' . $normal_border_width . 'px ' . $normal_border_type . ' ' . $normal_border_color . ';';
		}
		$normal_radius_d = isset( $settings['field_normal_border_radius_desktop'] ) ? trim( (string) $settings['field_normal_border_radius_desktop'] ) : ( isset( $settings['field_normal_border_radius'] ) ? trim( (string) $settings['field_normal_border_radius'] ) : '' );
		if ( $normal_radius_d !== '' && (int) $normal_radius_d >= 0 && (int) $normal_radius_d <= 200 ) {
			$rules[] = 'border-radius: ' . (int) $normal_radius_d . 'px;';
		}
		$normal_sx = isset( $settings['field_normal_shadow_x'] ) ? (int) $settings['field_normal_shadow_x'] : 0;
		$normal_sy = isset( $settings['field_normal_shadow_y'] ) ? (int) $settings['field_normal_shadow_y'] : 0;
		$normal_sb = isset( $settings['field_normal_shadow_blur'] ) ? (int) $settings['field_normal_shadow_blur'] : 0;
		$normal_ss = isset( $settings['field_normal_shadow_spread'] ) ? (int) $settings['field_normal_shadow_spread'] : 0;
		if ( $normal_sx !== 0 || $normal_sy !== 0 || $normal_sb !== 0 || $normal_ss !== 0 ) {
			$normal_sc = $this->formatShadowColorForCss( isset( $settings['field_normal_shadow_color'] ) ? (string) $settings['field_normal_shadow_color'] : '#000000' );
			$normal_inset = ! empty( $settings['field_normal_shadow_inset'] ) && $settings['field_normal_shadow_inset'] !== '0';
			$rules[] = 'box-shadow: ' . $normal_sx . 'px ' . $normal_sy . 'px ' . $normal_sb . 'px ' . $normal_ss . 'px ' . $normal_sc . ( $normal_inset ? ' inset' : '' ) . ';';
		}
		$normal_pt = isset( $settings['field_normal_padding_top'] ) ? trim( (string) $settings['field_normal_padding_top'] ) : '';
		$normal_pr = isset( $settings['field_normal_padding_right'] ) ? trim( (string) $settings['field_normal_padding_right'] ) : '';
		$normal_pb = isset( $settings['field_normal_padding_bottom'] ) ? trim( (string) $settings['field_normal_padding_bottom'] ) : '';
		$normal_pl = isset( $settings['field_normal_padding_left'] ) ? trim( (string) $settings['field_normal_padding_left'] ) : '';
		if ( $normal_pt !== '' && (int) $normal_pt >= 0 ) {
			$rules[] = 'padding-top: ' . (int) $normal_pt . 'px;';
		}
		if ( $normal_pr !== '' && (int) $normal_pr >= 0 ) {
			$rules[] = 'padding-right: ' . (int) $normal_pr . 'px;';
		}
		if ( $normal_pb !== '' && (int) $normal_pb >= 0 ) {
			$rules[] = 'padding-bottom: ' . (int) $normal_pb . 'px;';
		}
		if ( $normal_pl !== '' && (int) $normal_pl >= 0 ) {
			$rules[] = 'padding-left: ' . (int) $normal_pl . 'px;';
		}

		$css = '';
		if ( ! empty( $rules ) ) {
			$css .= "\n" . $selector . ' { ' . implode( ' ', $rules ) . " }\n";
		}

		// Focus state: use same border color as normal when typing so the "focus border" matches; remove browser default outline/ring
		$focus_rules = [];
		$focus_border_type = isset( $settings['field_focus_border_type'] ) ? (string) $settings['field_focus_border_type'] : 'none';
		$focus_has_border = ( $focus_border_type !== '' && $focus_border_type !== 'none' );
		if ( $focus_has_border ) {
			$focus_border_color = isset( $settings['field_focus_border_color'] ) ? trim( (string) $settings['field_focus_border_color'] ) : '#2271b1';
			if ( $focus_border_color === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $focus_border_color ) ) {
				$focus_border_color = '#2271b1';
			}
			// Use normal border color on focus when user set field border color (so focus border matches)
			if ( $normal_border_type !== '' && $normal_border_type !== 'none' && isset( $normal_border_color ) ) {
				$focus_border_color = $normal_border_color;
			}
			$focus_border_width = isset( $settings['field_focus_border_width'] ) ? max( 0, min( 50, (int) $settings['field_focus_border_width'] ) ) : 1;
			if ( $focus_border_width < 1 ) {
				$focus_border_width = 1;
			}
			$focus_rules[] = 'border: ' . $focus_border_width . 'px ' . $focus_border_type . ' ' . $focus_border_color . ';';
		} elseif ( $normal_border_type !== '' && $normal_border_type !== 'none' && isset( $normal_border_color ) ) {
			// User set normal border only: apply same border on focus so the "other border" when typing uses their color
			$focus_rules[] = 'border: ' . $normal_border_width . 'px ' . $normal_border_type . ' ' . $normal_border_color . ';';
		}
		if ( $normal_border_type !== '' && $normal_border_type !== 'none' ) {
			$focus_rules[] = 'outline: none;';
			$focus_rules[] = 'box-shadow: none;';
		}
		$focus_radius = isset( $settings['field_focus_border_radius'] ) ? trim( (string) $settings['field_focus_border_radius'] ) : '';
		if ( $focus_radius !== '' && (int) $focus_radius >= 0 && (int) $focus_radius <= 50 ) {
			$focus_rules[] = 'border-radius: ' . (int) $focus_radius . 'px;';
		}
		$focus_pt = isset( $settings['field_focus_padding_top'] ) ? trim( (string) $settings['field_focus_padding_top'] ) : '';
		$focus_pr = isset( $settings['field_focus_padding_right'] ) ? trim( (string) $settings['field_focus_padding_right'] ) : '';
		$focus_pb = isset( $settings['field_focus_padding_bottom'] ) ? trim( (string) $settings['field_focus_padding_bottom'] ) : '';
		$focus_pl = isset( $settings['field_focus_padding_left'] ) ? trim( (string) $settings['field_focus_padding_left'] ) : '';
		if ( $focus_pt !== '' && (int) $focus_pt >= 0 ) {
			$focus_rules[] = 'padding-top: ' . (int) $focus_pt . 'px;';
		}
		if ( $focus_pr !== '' && (int) $focus_pr >= 0 ) {
			$focus_rules[] = 'padding-right: ' . (int) $focus_pr . 'px;';
		}
		if ( $focus_pb !== '' && (int) $focus_pb >= 0 ) {
			$focus_rules[] = 'padding-bottom: ' . (int) $focus_pb . 'px;';
		}
		if ( $focus_pl !== '' && (int) $focus_pl >= 0 ) {
			$focus_rules[] = 'padding-left: ' . (int) $focus_pl . 'px;';
		}
		if ( ! empty( $focus_rules ) ) {
			$css .= "\n" . $selector . ':focus { ' . implode( ' ', $focus_rules ) . " }\n";
		}

		// Responsive: border-radius, font-size, line-height, letter-spacing (tablet/mobile)
		$field_media = [];
		$normal_radius_t = isset( $settings['field_normal_border_radius_tablet'] ) ? trim( (string) $settings['field_normal_border_radius_tablet'] ) : '';
		$normal_radius_m = isset( $settings['field_normal_border_radius_mobile'] ) ? trim( (string) $settings['field_normal_border_radius_mobile'] ) : '';
		if ( $normal_radius_t !== '' && (int) $normal_radius_t >= 0 && (int) $normal_radius_t <= 200 ) {
			$field_media[] = '@media (max-width: 1024px) { ' . $selector . ' { border-radius: ' . (int) $normal_radius_t . 'px; } }';
		}
		if ( $normal_radius_m !== '' && (int) $normal_radius_m >= 0 && (int) $normal_radius_m <= 200 ) {
			$field_media[] = '@media (max-width: 600px) { ' . $selector . ' { border-radius: ' . (int) $normal_radius_m . 'px; } }';
		}
		if ( $font_size_t !== '' && (int) $font_size_t >= 10 && (int) $font_size_t <= 72 ) {
			$field_media[] = '@media (max-width: 1024px) { ' . $selector . ' { font-size: ' . (int) $font_size_t . 'px; } }';
		}
		if ( $font_size_m !== '' && (int) $font_size_m >= 10 && (int) $font_size_m <= 72 ) {
			$field_media[] = '@media (max-width: 600px) { ' . $selector . ' { font-size: ' . (int) $font_size_m . 'px; } }';
		}
		if ( $line_height_t !== '' && is_numeric( $line_height_t ) && (float) $line_height_t >= 0.5 && (float) $line_height_t <= 3 ) {
			$field_media[] = '@media (max-width: 1024px) { ' . $selector . ' { line-height: ' . (float) $line_height_t . '; } }';
		}
		if ( $line_height_m !== '' && is_numeric( $line_height_m ) && (float) $line_height_m >= 0.5 && (float) $line_height_m <= 3 ) {
			$field_media[] = '@media (max-width: 600px) { ' . $selector . ' { line-height: ' . (float) $line_height_m . '; } }';
		}
		if ( $letter_spacing_t !== '' && is_numeric( $letter_spacing_t ) ) {
			$field_media[] = '@media (max-width: 1024px) { ' . $selector . ' { letter-spacing: ' . (float) $letter_spacing_t . 'px; } }';
		}
		if ( $letter_spacing_m !== '' && is_numeric( $letter_spacing_m ) ) {
			$field_media[] = '@media (max-width: 600px) { ' . $selector . ' { letter-spacing: ' . (float) $letter_spacing_m . 'px; } }';
		}
		// Responsive padding (tablet/mobile)
		$normal_pt_t = isset( $settings['field_normal_padding_top_tablet'] ) ? trim( (string) $settings['field_normal_padding_top_tablet'] ) : '';
		$normal_pr_t = isset( $settings['field_normal_padding_right_tablet'] ) ? trim( (string) $settings['field_normal_padding_right_tablet'] ) : '';
		$normal_pb_t = isset( $settings['field_normal_padding_bottom_tablet'] ) ? trim( (string) $settings['field_normal_padding_bottom_tablet'] ) : '';
		$normal_pl_t = isset( $settings['field_normal_padding_left_tablet'] ) ? trim( (string) $settings['field_normal_padding_left_tablet'] ) : '';
		$tablet_padding_rules = [];
		if ( $normal_pt_t !== '' && (int) $normal_pt_t >= 0 ) {
			$tablet_padding_rules[] = 'padding-top: ' . (int) $normal_pt_t . 'px;';
		}
		if ( $normal_pr_t !== '' && (int) $normal_pr_t >= 0 ) {
			$tablet_padding_rules[] = 'padding-right: ' . (int) $normal_pr_t . 'px;';
		}
		if ( $normal_pb_t !== '' && (int) $normal_pb_t >= 0 ) {
			$tablet_padding_rules[] = 'padding-bottom: ' . (int) $normal_pb_t . 'px;';
		}
		if ( $normal_pl_t !== '' && (int) $normal_pl_t >= 0 ) {
			$tablet_padding_rules[] = 'padding-left: ' . (int) $normal_pl_t . 'px;';
		}
		if ( ! empty( $tablet_padding_rules ) ) {
			$field_media[] = '@media (max-width: 1024px) { ' . $selector . ' { ' . implode( ' ', $tablet_padding_rules ) . ' } }';
		}
		$normal_pt_m = isset( $settings['field_normal_padding_top_mobile'] ) ? trim( (string) $settings['field_normal_padding_top_mobile'] ) : '';
		$normal_pr_m = isset( $settings['field_normal_padding_right_mobile'] ) ? trim( (string) $settings['field_normal_padding_right_mobile'] ) : '';
		$normal_pb_m = isset( $settings['field_normal_padding_bottom_mobile'] ) ? trim( (string) $settings['field_normal_padding_bottom_mobile'] ) : '';
		$normal_pl_m = isset( $settings['field_normal_padding_left_mobile'] ) ? trim( (string) $settings['field_normal_padding_left_mobile'] ) : '';
		$mobile_padding_rules = [];
		if ( $normal_pt_m !== '' && (int) $normal_pt_m >= 0 ) {
			$mobile_padding_rules[] = 'padding-top: ' . (int) $normal_pt_m . 'px;';
		}
		if ( $normal_pr_m !== '' && (int) $normal_pr_m >= 0 ) {
			$mobile_padding_rules[] = 'padding-right: ' . (int) $normal_pr_m . 'px;';
		}
		if ( $normal_pb_m !== '' && (int) $normal_pb_m >= 0 ) {
			$mobile_padding_rules[] = 'padding-bottom: ' . (int) $normal_pb_m . 'px;';
		}
		if ( $normal_pl_m !== '' && (int) $normal_pl_m >= 0 ) {
			$mobile_padding_rules[] = 'padding-left: ' . (int) $normal_pl_m . 'px;';
		}
		if ( ! empty( $mobile_padding_rules ) ) {
			$field_media[] = '@media (max-width: 600px) { ' . $selector . ' { ' . implode( ' ', $mobile_padding_rules ) . ' } }';
		}
		if ( ! empty( $field_media ) ) {
			$css .= "\n" . implode( "\n", $field_media ) . "\n";
		}
		if ( $css !== '' ) {
			$eye_selector = '#login form .show-password-input, #login form .wp-hide-pw';
			$eye_color = '#50575e';
			$css .= "\n" . $eye_selector . ' { color: ' . $eye_color . ' !important; }' . "\n";
			$css .= $eye_selector . ' .dashicons { color: ' . $eye_color . ' !important; }' . "\n";
			// When field padding is applied, apply the same padding to the eye icon
			$eye_padding = [];
			if ( $normal_pt !== '' && (int) $normal_pt >= 0 ) {
				$eye_padding[] = 'padding-top: ' . (int) $normal_pt . 'px;';
			}
			if ( $normal_pr !== '' && (int) $normal_pr >= 0 ) {
				$eye_padding[] = 'padding-right: ' . (int) $normal_pr . 'px;';
			}
			if ( $normal_pb !== '' && (int) $normal_pb >= 0 ) {
				$eye_padding[] = 'padding-bottom: ' . (int) $normal_pb . 'px;';
			}
			if ( $normal_pl !== '' && (int) $normal_pl >= 0 ) {
				$eye_padding[] = 'padding-left: ' . (int) $normal_pl . 'px;';
			}
			if ( ! empty( $eye_padding ) ) {
				$css .= "\n" . $eye_selector . ' { ' . implode( ' ', $eye_padding ) . " }\n";
			}
			wp_add_inline_style( 'login', $css );
		}
	}

	/**
	 * Remove focus outline/border from default password toggle (eye) when form customization is enabled.
	 */
	public function applyFormCustomizationEyeFocusFix(): void {
		$css = "\n#login form .show-password-input:focus, #login form .wp-hide-pw:focus { outline: none !important; border: none !important; box-shadow: none !important; }\n";
		wp_add_inline_style( 'login', $css );
	}

	/**
	 * Output CSS for Button tab: typography and normal/hover state on login submit button.
	 * Only outputs when at least one button setting is non-default.
	 */
	public function applyFormCustomizationButtonStyles(): void {
		$settings = $this->getSettings();
		// Submit button only; exclude default password visibility toggle (eye icon has class .button too).
		$selector = $this->getLoginButtonCssSelector( $settings );
		$rules    = [];

		$font_family = isset( $settings['button_font_family'] ) ? trim( (string) $settings['button_font_family'] ) : '';
		if ( $font_family !== '' ) {
			$rules[] = 'font-family: ' . $font_family . ';';
		}
		$font_size_d = isset( $settings['button_font_size_desktop'] ) ? trim( (string) $settings['button_font_size_desktop'] ) : '';
		if ( $font_size_d !== '' && (int) $font_size_d >= 10 && (int) $font_size_d <= 72 ) {
			$rules[] = 'font-size: ' . (int) $font_size_d . 'px;';
		}
		$font_weight = isset( $settings['button_font_weight'] ) ? trim( (string) $settings['button_font_weight'] ) : '';
		if ( $font_weight !== '' && in_array( $font_weight, [ '300', '400', '500', '600', '700' ], true ) ) {
			$rules[] = 'font-weight: ' . $font_weight . ';';
		}
		$line_height_d = isset( $settings['button_line_height_desktop'] ) ? trim( (string) $settings['button_line_height_desktop'] ) : '';
		if ( $line_height_d !== '' && is_numeric( $line_height_d ) && (float) $line_height_d >= 0.5 && (float) $line_height_d <= 3 ) {
			$rules[] = 'line-height: ' . (float) $line_height_d . ';';
		}
		$letter_spacing_d = isset( $settings['button_letter_spacing_desktop'] ) ? trim( (string) $settings['button_letter_spacing_desktop'] ) : '';
		if ( $letter_spacing_d !== '' && is_numeric( $letter_spacing_d ) ) {
			$rules[] = 'letter-spacing: ' . (float) $letter_spacing_d . 'px;';
		}
		$text_transform = isset( $settings['button_text_transform'] ) ? (string) $settings['button_text_transform'] : '';
		if ( $text_transform !== '' && in_array( $text_transform, [ 'none', 'lowercase', 'uppercase', 'capitalize' ], true ) ) {
			$rules[] = 'text-transform: ' . $text_transform . ';';
		}
		$font_style = isset( $settings['button_font_style'] ) ? (string) $settings['button_font_style'] : '';
		if ( $font_style !== '' && in_array( $font_style, [ 'normal', 'italic', 'oblique' ], true ) ) {
			$rules[] = 'font-style: ' . $font_style . ';';
		}

		// Padding: 4 sides (old style, below typography).
		$normal_pt = isset( $settings['button_normal_padding_top'] ) ? trim( (string) $settings['button_normal_padding_top'] ) : '';
		$normal_pr = isset( $settings['button_normal_padding_right'] ) ? trim( (string) $settings['button_normal_padding_right'] ) : '';
		$normal_pb = isset( $settings['button_normal_padding_bottom'] ) ? trim( (string) $settings['button_normal_padding_bottom'] ) : '';
		$normal_pl = isset( $settings['button_normal_padding_left'] ) ? trim( (string) $settings['button_normal_padding_left'] ) : '';
		if ( $normal_pt !== '' && (int) $normal_pt >= 0 ) {
			$rules[] = 'padding-top: ' . (int) $normal_pt . 'px;';
		}
		if ( $normal_pr !== '' && (int) $normal_pr >= 0 ) {
			$rules[] = 'padding-right: ' . (int) $normal_pr . 'px;';
		}
		if ( $normal_pb !== '' && (int) $normal_pb >= 0 ) {
			$rules[] = 'padding-bottom: ' . (int) $normal_pb . 'px;';
		}
		if ( $normal_pl !== '' && (int) $normal_pl >= 0 ) {
			$rules[] = 'padding-left: ' . (int) $normal_pl . 'px;';
		}

		// Normal state: text color
		$btn_normal_color = $this->resolveButtonTextColor( $settings, 'normal' );
		if ( $btn_normal_color !== '' ) {
			$rules[] = 'color: ' . $btn_normal_color . ' !important;';
		}
		// Normal state: background (Color or Gradient)
		$btn_normal_bg_type = isset( $settings['button_normal_bg_type'] ) ? (string) $settings['button_normal_bg_type'] : '';
		$resolved_normal_bg_color = '';
		if ( $btn_normal_bg_type === 'color' ) {
			$c = isset( $settings['button_normal_bg_color'] ) ? trim( (string) $settings['button_normal_bg_color'] ) : '#2271b1';
			if ( $c === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $c ) ) {
				$c = '#2271b1';
			}
			$resolved_normal_bg_color = $c;
			$rules[] = 'background-color: ' . $c . ' !important;';
		}
		if ( $btn_normal_bg_type === 'gradient' ) {
			$grad = $this->buildGradientCss( $settings, 'button_normal_bg_gradient' );
			if ( $grad !== '' ) {
				$rules[] = 'background: ' . $grad . ' !important;';
			}
		}
		// Normal state: border
		$btn_normal_border_type = isset( $settings['button_normal_border_type'] ) ? (string) $settings['button_normal_border_type'] : 'none';
		if ( $btn_normal_border_type !== '' && $btn_normal_border_type !== 'none' ) {
			$btn_normal_border_color = isset( $settings['button_normal_border_color'] ) ? trim( (string) $settings['button_normal_border_color'] ) : '#cccccc';
			if ( $btn_normal_border_color === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $btn_normal_border_color ) ) {
				$btn_normal_border_color = '#cccccc';
			}
			$btn_normal_border_width = isset( $settings['button_normal_border_width'] ) ? max( 0, min( 50, (int) $settings['button_normal_border_width'] ) ) : 1;
			if ( $btn_normal_border_width < 1 ) {
				$btn_normal_border_width = 1;
			}
			$rules[] = 'border: ' . $btn_normal_border_width . 'px ' . $btn_normal_border_type . ' ' . $btn_normal_border_color . ' !important;';
		} else {
			$rules[] = 'border: none !important;';
		}
		$btn_normal_radius_d = $this->resolveButtonBorderRadius( $settings, 'normal', 'desktop' );
		if ( $btn_normal_radius_d !== '' && (int) $btn_normal_radius_d >= 0 && (int) $btn_normal_radius_d <= 200 ) {
			$rules[] = 'border-radius: ' . (int) $btn_normal_radius_d . 'px;';
		}
		// Normal state: box shadow
		$btn_normal_sx = isset( $settings['button_normal_shadow_x'] ) ? (int) $settings['button_normal_shadow_x'] : 0;
		$btn_normal_sy = isset( $settings['button_normal_shadow_y'] ) ? (int) $settings['button_normal_shadow_y'] : 0;
		$btn_normal_sb = isset( $settings['button_normal_shadow_blur'] ) ? (int) $settings['button_normal_shadow_blur'] : 0;
		$btn_normal_ss = isset( $settings['button_normal_shadow_spread'] ) ? (int) $settings['button_normal_shadow_spread'] : 0;
		if ( $btn_normal_sx !== 0 || $btn_normal_sy !== 0 || $btn_normal_sb !== 0 || $btn_normal_ss !== 0 ) {
			$btn_normal_sc = $this->formatShadowColorForCss( isset( $settings['button_normal_shadow_color'] ) ? (string) $settings['button_normal_shadow_color'] : '#000000' );
			$btn_normal_inset = ! empty( $settings['button_normal_shadow_inset'] ) && $settings['button_normal_shadow_inset'] !== '0';
			$rules[] = 'box-shadow: ' . $btn_normal_sx . 'px ' . $btn_normal_sy . 'px ' . $btn_normal_sb . 'px ' . $btn_normal_ss . 'px ' . $btn_normal_sc . ( $btn_normal_inset ? ' inset' : '' ) . ';';
		}

		$css = '';
		if ( ! empty( $rules ) ) {
			$css .= "\n" . $selector . ' { ' . implode( ' ', $rules ) . " }\n";
		}

		// Hover state (padding is shared with normal – single Padding row above Normal/Hover tabs)
		$hover_rules = [];
		if ( $normal_pt !== '' && (int) $normal_pt >= 0 ) {
			$hover_rules[] = 'padding-top: ' . (int) $normal_pt . 'px;';
		}
		if ( $normal_pr !== '' && (int) $normal_pr >= 0 ) {
			$hover_rules[] = 'padding-right: ' . (int) $normal_pr . 'px;';
		}
		if ( $normal_pb !== '' && (int) $normal_pb >= 0 ) {
			$hover_rules[] = 'padding-bottom: ' . (int) $normal_pb . 'px;';
		}
		if ( $normal_pl !== '' && (int) $normal_pl >= 0 ) {
			$hover_rules[] = 'padding-left: ' . (int) $normal_pl . 'px;';
		}
		$btn_hover_color = $this->resolveButtonTextColor( $settings, 'hover' );
		if ( $btn_hover_color !== '' ) {
			$hover_rules[] = 'color: ' . $btn_hover_color . ' !important;';
		} elseif ( $btn_normal_color !== '' ) {
			$hover_rules[] = 'color: ' . $btn_normal_color . ' !important;';
		}
		$btn_hover_bg_type = isset( $settings['button_hover_bg_type'] ) ? (string) $settings['button_hover_bg_type'] : '';
		if ( $btn_hover_bg_type === 'color' ) {
			$c = isset( $settings['button_hover_bg_color'] ) ? trim( (string) $settings['button_hover_bg_color'] ) : '#135e96';
			if ( $c === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $c ) ) {
				$c = $resolved_normal_bg_color !== '' ? $resolved_normal_bg_color : '#135e96';
			}
			$hover_rules[] = 'background-color: ' . $c . ' !important;';
		} elseif ( $btn_hover_bg_type === 'gradient' ) {
			$grad = $this->buildGradientCss( $settings, 'button_hover_bg_gradient' );
			if ( $grad !== '' ) {
				$hover_rules[] = 'background: ' . $grad . ' !important;';
			}
		} elseif ( $btn_normal_bg_type === 'color' ) {
			$c = isset( $settings['button_hover_bg_color'] ) ? trim( (string) $settings['button_hover_bg_color'] ) : '';
			if ( $c === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $c ) ) {
				$c = $resolved_normal_bg_color !== '' ? $resolved_normal_bg_color : '#2271b1';
			}
			$hover_rules[] = 'background-color: ' . $c . ' !important;';
		} elseif ( $btn_normal_bg_type === 'gradient' ) {
			$grad = $this->buildGradientCss( $settings, 'button_normal_bg_gradient' );
			if ( $grad !== '' ) {
				$hover_rules[] = 'background: ' . $grad . ' !important;';
			}
		}
		$btn_hover_border_type = isset( $settings['button_hover_border_type'] ) ? (string) $settings['button_hover_border_type'] : 'none';
		if ( $btn_hover_border_type !== '' && $btn_hover_border_type !== 'none' ) {
			$btn_hover_border_color = isset( $settings['button_hover_border_color'] ) ? trim( (string) $settings['button_hover_border_color'] ) : '#cccccc';
			if ( $btn_hover_border_color === '' || ! preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $btn_hover_border_color ) ) {
				$btn_hover_border_color = '#cccccc';
			}
			$btn_hover_border_width = isset( $settings['button_hover_border_width'] ) ? max( 0, min( 50, (int) $settings['button_hover_border_width'] ) ) : 1;
			if ( $btn_hover_border_width < 1 ) {
				$btn_hover_border_width = 1;
			}
			$hover_rules[] = 'border: ' . $btn_hover_border_width . 'px ' . $btn_hover_border_type . ' ' . $btn_hover_border_color . ' !important;';
		} else {
			$hover_rules[] = 'border: none !important;';
		}
		$btn_hover_radius_d = $this->resolveButtonBorderRadius( $settings, 'hover', 'desktop' );
		$effective_hover_radius = $btn_hover_radius_d !== '' ? $btn_hover_radius_d : $btn_normal_radius_d;
		if ( $effective_hover_radius !== '' && (int) $effective_hover_radius >= 0 && (int) $effective_hover_radius <= 200 ) {
			$hover_rules[] = 'border-radius: ' . (int) $effective_hover_radius . 'px !important;';
		}
		$btn_hover_sx = isset( $settings['button_hover_shadow_x'] ) ? (int) $settings['button_hover_shadow_x'] : 0;
		$btn_hover_sy = isset( $settings['button_hover_shadow_y'] ) ? (int) $settings['button_hover_shadow_y'] : 0;
		$btn_hover_sb = isset( $settings['button_hover_shadow_blur'] ) ? (int) $settings['button_hover_shadow_blur'] : 0;
		$btn_hover_ss = isset( $settings['button_hover_shadow_spread'] ) ? (int) $settings['button_hover_shadow_spread'] : 0;
		if ( $btn_hover_sx !== 0 || $btn_hover_sy !== 0 || $btn_hover_sb !== 0 || $btn_hover_ss !== 0 ) {
			$btn_hover_sc = $this->formatShadowColorForCss( isset( $settings['button_hover_shadow_color'] ) ? (string) $settings['button_hover_shadow_color'] : '#000000' );
			$btn_hover_inset = ! empty( $settings['button_hover_shadow_inset'] ) && $settings['button_hover_shadow_inset'] !== '0';
			$hover_rules[] = 'box-shadow: ' . $btn_hover_sx . 'px ' . $btn_hover_sy . 'px ' . $btn_hover_sb . 'px ' . $btn_hover_ss . 'px ' . $btn_hover_sc . ( $btn_hover_inset ? ' inset' : '' ) . ';';
		}
		if ( ! empty( $hover_rules ) || ! empty( $rules ) ) {
			if ( empty( $hover_rules ) ) {
				$hover_rules[] = 'border: none !important;';
			}
			$css .= "\n" . $selector . ':hover, ' . $selector . ':focus { ' . implode( ' ', $hover_rules ) . " }\n";
		}

		// Responsive typography
		$font_size_t = isset( $settings['button_font_size_tablet'] ) ? trim( (string) $settings['button_font_size_tablet'] ) : '';
		$font_size_m = isset( $settings['button_font_size_mobile'] ) ? trim( (string) $settings['button_font_size_mobile'] ) : '';
		$line_height_t = isset( $settings['button_line_height_tablet'] ) ? trim( (string) $settings['button_line_height_tablet'] ) : '';
		$line_height_m = isset( $settings['button_line_height_mobile'] ) ? trim( (string) $settings['button_line_height_mobile'] ) : '';
		$letter_spacing_t = isset( $settings['button_letter_spacing_tablet'] ) ? trim( (string) $settings['button_letter_spacing_tablet'] ) : '';
		$letter_spacing_m = isset( $settings['button_letter_spacing_mobile'] ) ? trim( (string) $settings['button_letter_spacing_mobile'] ) : '';
		$media = [];
		// Responsive padding: 4 sides per breakpoint (button + :hover use same values)
		$normal_pt_t = isset( $settings['button_normal_padding_top_tablet'] ) ? trim( (string) $settings['button_normal_padding_top_tablet'] ) : '';
		$normal_pr_t = isset( $settings['button_normal_padding_right_tablet'] ) ? trim( (string) $settings['button_normal_padding_right_tablet'] ) : '';
		$normal_pb_t = isset( $settings['button_normal_padding_bottom_tablet'] ) ? trim( (string) $settings['button_normal_padding_bottom_tablet'] ) : '';
		$normal_pl_t = isset( $settings['button_normal_padding_left_tablet'] ) ? trim( (string) $settings['button_normal_padding_left_tablet'] ) : '';
		$normal_pt_m = isset( $settings['button_normal_padding_top_mobile'] ) ? trim( (string) $settings['button_normal_padding_top_mobile'] ) : '';
		$normal_pr_m = isset( $settings['button_normal_padding_right_mobile'] ) ? trim( (string) $settings['button_normal_padding_right_mobile'] ) : '';
		$normal_pb_m = isset( $settings['button_normal_padding_bottom_mobile'] ) ? trim( (string) $settings['button_normal_padding_bottom_mobile'] ) : '';
		$normal_pl_m = isset( $settings['button_normal_padding_left_mobile'] ) ? trim( (string) $settings['button_normal_padding_left_mobile'] ) : '';
		$tablet_rules = [];
		if ( $normal_pt_t !== '' && (int) $normal_pt_t >= 0 ) {
			$tablet_rules[] = 'padding-top: ' . (int) $normal_pt_t . 'px;';
		}
		if ( $normal_pr_t !== '' && (int) $normal_pr_t >= 0 ) {
			$tablet_rules[] = 'padding-right: ' . (int) $normal_pr_t . 'px;';
		}
		if ( $normal_pb_t !== '' && (int) $normal_pb_t >= 0 ) {
			$tablet_rules[] = 'padding-bottom: ' . (int) $normal_pb_t . 'px;';
		}
		if ( $normal_pl_t !== '' && (int) $normal_pl_t >= 0 ) {
			$tablet_rules[] = 'padding-left: ' . (int) $normal_pl_t . 'px;';
		}
		if ( ! empty( $tablet_rules ) ) {
			$media[] = '@media (max-width: 1024px) { ' . $selector . ', ' . $selector . ':hover { ' . implode( ' ', $tablet_rules ) . ' } }';
		}
		$mobile_rules = [];
		if ( $normal_pt_m !== '' && (int) $normal_pt_m >= 0 ) {
			$mobile_rules[] = 'padding-top: ' . (int) $normal_pt_m . 'px;';
		}
		if ( $normal_pr_m !== '' && (int) $normal_pr_m >= 0 ) {
			$mobile_rules[] = 'padding-right: ' . (int) $normal_pr_m . 'px;';
		}
		if ( $normal_pb_m !== '' && (int) $normal_pb_m >= 0 ) {
			$mobile_rules[] = 'padding-bottom: ' . (int) $normal_pb_m . 'px;';
		}
		if ( $normal_pl_m !== '' && (int) $normal_pl_m >= 0 ) {
			$mobile_rules[] = 'padding-left: ' . (int) $normal_pl_m . 'px;';
		}
		if ( ! empty( $mobile_rules ) ) {
			$media[] = '@media (max-width: 600px) { ' . $selector . ', ' . $selector . ':hover { ' . implode( ' ', $mobile_rules ) . ' } }';
		}
		// Responsive border-radius (normal)
		$btn_normal_radius_t = $this->resolveButtonBorderRadius( $settings, 'normal', 'tablet' );
		$btn_normal_radius_m = $this->resolveButtonBorderRadius( $settings, 'normal', 'mobile' );
		if ( $btn_normal_radius_t !== '' && (int) $btn_normal_radius_t >= 0 && (int) $btn_normal_radius_t <= 200 ) {
			$media[] = '@media (max-width: 1024px) { ' . $selector . ' { border-radius: ' . (int) $btn_normal_radius_t . 'px; } }';
		}
		if ( $btn_normal_radius_m !== '' && (int) $btn_normal_radius_m >= 0 && (int) $btn_normal_radius_m <= 200 ) {
			$media[] = '@media (max-width: 600px) { ' . $selector . ' { border-radius: ' . (int) $btn_normal_radius_m . 'px; } }';
		}
		// Responsive border-radius (hover mirrors normal unless hover breakpoint is set)
		$btn_hover_radius_t = $this->resolveButtonBorderRadius( $settings, 'hover', 'tablet' );
		$btn_hover_radius_m = $this->resolveButtonBorderRadius( $settings, 'hover', 'mobile' );
		$effective_hover_radius_t = $btn_hover_radius_t !== '' ? $btn_hover_radius_t : $btn_normal_radius_t;
		$effective_hover_radius_m = $btn_hover_radius_m !== '' ? $btn_hover_radius_m : $btn_normal_radius_m;
		if ( $effective_hover_radius_t !== '' && (int) $effective_hover_radius_t >= 0 && (int) $effective_hover_radius_t <= 200 ) {
			$media[] = '@media (max-width: 1024px) { ' . $selector . ':hover, ' . $selector . ':focus { border-radius: ' . (int) $effective_hover_radius_t . 'px !important; } }';
		}
		if ( $effective_hover_radius_m !== '' && (int) $effective_hover_radius_m >= 0 && (int) $effective_hover_radius_m <= 200 ) {
			$media[] = '@media (max-width: 600px) { ' . $selector . ':hover, ' . $selector . ':focus { border-radius: ' . (int) $effective_hover_radius_m . 'px !important; } }';
		}
		if ( $font_size_t !== '' && (int) $font_size_t >= 10 && (int) $font_size_t <= 72 ) {
			$media[] = '@media (max-width: 1024px) { ' . $selector . ' { font-size: ' . (int) $font_size_t . 'px; } }';
		}
		if ( $font_size_m !== '' && (int) $font_size_m >= 10 && (int) $font_size_m <= 72 ) {
			$media[] = '@media (max-width: 600px) { ' . $selector . ' { font-size: ' . (int) $font_size_m . 'px; } }';
		}
		if ( $line_height_t !== '' && is_numeric( $line_height_t ) && (float) $line_height_t >= 0.5 && (float) $line_height_t <= 3 ) {
			$media[] = '@media (max-width: 1024px) { ' . $selector . ' { line-height: ' . (float) $line_height_t . '; } }';
		}
		if ( $line_height_m !== '' && is_numeric( $line_height_m ) && (float) $line_height_m >= 0.5 && (float) $line_height_m <= 3 ) {
			$media[] = '@media (max-width: 600px) { ' . $selector . ' { line-height: ' . (float) $line_height_m . '; } }';
		}
		if ( $letter_spacing_t !== '' && is_numeric( $letter_spacing_t ) ) {
			$media[] = '@media (max-width: 1024px) { ' . $selector . ' { letter-spacing: ' . (float) $letter_spacing_t . 'px; } }';
		}
		if ( $letter_spacing_m !== '' && is_numeric( $letter_spacing_m ) ) {
			$media[] = '@media (max-width: 600px) { ' . $selector . ' { letter-spacing: ' . (float) $letter_spacing_m . 'px; } }';
		}
		if ( ! empty( $media ) ) {
			$css .= "\n" . implode( "\n", $media ) . "\n";
		}

		if ( $css !== '' ) {
			wp_add_inline_style( 'login', $css );
		}
	}

	/**
	 * Output CSS for Footer tab: typography and normal/hover text color on #login #nav a, #login #backtoblog a.
	 * Only outputs when at least one footer setting is non-default.
	 */
	public function applyFormCustomizationFooterStyles(): void {
		$settings = $this->getSettings();
		$selector = '#login #nav a, #login #backtoblog a';
		$rules    = [];

		$font_family = isset( $settings['footer_font_family'] ) ? trim( (string) $settings['footer_font_family'] ) : '';
		if ( $font_family !== '' ) {
			$rules[] = 'font-family: ' . $font_family . ';';
		}
		$font_size_d = isset( $settings['footer_font_size_desktop'] ) ? trim( (string) $settings['footer_font_size_desktop'] ) : '';
		if ( $font_size_d !== '' && (int) $font_size_d >= 10 && (int) $font_size_d <= 72 ) {
			$rules[] = 'font-size: ' . (int) $font_size_d . 'px;';
		}
		$font_weight = isset( $settings['footer_font_weight'] ) ? trim( (string) $settings['footer_font_weight'] ) : '';
		if ( $font_weight !== '' && in_array( $font_weight, [ '300', '400', '500', '600', '700' ], true ) ) {
			$rules[] = 'font-weight: ' . $font_weight . ';';
		}
		$line_height_d = isset( $settings['footer_line_height_desktop'] ) ? trim( (string) $settings['footer_line_height_desktop'] ) : '';
		if ( $line_height_d !== '' && is_numeric( $line_height_d ) && (float) $line_height_d >= 0.5 && (float) $line_height_d <= 3 ) {
			$rules[] = 'line-height: ' . (float) $line_height_d . ';';
		}
		$letter_spacing_d = isset( $settings['footer_letter_spacing_desktop'] ) ? trim( (string) $settings['footer_letter_spacing_desktop'] ) : '';
		if ( $letter_spacing_d !== '' && is_numeric( $letter_spacing_d ) ) {
			$rules[] = 'letter-spacing: ' . (float) $letter_spacing_d . 'px;';
		}
		$text_transform = isset( $settings['footer_text_transform'] ) ? (string) $settings['footer_text_transform'] : '';
		if ( $text_transform !== '' && in_array( $text_transform, [ 'none', 'lowercase', 'uppercase', 'capitalize' ], true ) ) {
			$rules[] = 'text-transform: ' . $text_transform . ';';
		}
		$font_style = isset( $settings['footer_font_style'] ) ? (string) $settings['footer_font_style'] : '';
		if ( $font_style !== '' && in_array( $font_style, [ 'normal', 'italic', 'oblique' ], true ) ) {
			$rules[] = 'font-style: ' . $font_style . ';';
		}

		$normal_color = isset( $settings['footer_normal_text_color'] ) ? trim( (string) $settings['footer_normal_text_color'] ) : '';
		if ( $normal_color !== '' && preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $normal_color ) ) {
			$rules[] = 'color: ' . $normal_color . ';';
		}

		$css = '';
		if ( ! empty( $rules ) ) {
			$css .= "\n" . $selector . ' { ' . implode( ' ', $rules ) . " }\n";
		}

		$hover_color = isset( $settings['footer_hover_text_color'] ) ? trim( (string) $settings['footer_hover_text_color'] ) : '';
		if ( $hover_color !== '' && preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $hover_color ) ) {
			$selector_hover = '#login #nav a:hover, #login #backtoblog a:hover';
			$css .= "\n" . $selector_hover . ' { color: ' . $hover_color . "; }\n";
		}

		$font_size_t = isset( $settings['footer_font_size_tablet'] ) ? trim( (string) $settings['footer_font_size_tablet'] ) : '';
		$font_size_m = isset( $settings['footer_font_size_mobile'] ) ? trim( (string) $settings['footer_font_size_mobile'] ) : '';
		$line_height_t = isset( $settings['footer_line_height_tablet'] ) ? trim( (string) $settings['footer_line_height_tablet'] ) : '';
		$line_height_m = isset( $settings['footer_line_height_mobile'] ) ? trim( (string) $settings['footer_line_height_mobile'] ) : '';
		$letter_spacing_t = isset( $settings['footer_letter_spacing_tablet'] ) ? trim( (string) $settings['footer_letter_spacing_tablet'] ) : '';
		$letter_spacing_m = isset( $settings['footer_letter_spacing_mobile'] ) ? trim( (string) $settings['footer_letter_spacing_mobile'] ) : '';
		$media = [];
		if ( $font_size_t !== '' && (int) $font_size_t >= 10 && (int) $font_size_t <= 72 ) {
			$media[] = '@media (max-width: 1024px) { ' . $selector . ' { font-size: ' . (int) $font_size_t . 'px; } }';
		}
		if ( $font_size_m !== '' && (int) $font_size_m >= 10 && (int) $font_size_m <= 72 ) {
			$media[] = '@media (max-width: 600px) { ' . $selector . ' { font-size: ' . (int) $font_size_m . 'px; } }';
		}
		if ( $line_height_t !== '' && is_numeric( $line_height_t ) && (float) $line_height_t >= 0.5 && (float) $line_height_t <= 3 ) {
			$media[] = '@media (max-width: 1024px) { ' . $selector . ' { line-height: ' . (float) $line_height_t . '; } }';
		}
		if ( $line_height_m !== '' && is_numeric( $line_height_m ) && (float) $line_height_m >= 0.5 && (float) $line_height_m <= 3 ) {
			$media[] = '@media (max-width: 600px) { ' . $selector . ' { line-height: ' . (float) $line_height_m . '; } }';
		}
		if ( $letter_spacing_t !== '' && is_numeric( $letter_spacing_t ) ) {
			$media[] = '@media (max-width: 1024px) { ' . $selector . ' { letter-spacing: ' . (float) $letter_spacing_t . 'px; } }';
		}
		if ( $letter_spacing_m !== '' && is_numeric( $letter_spacing_m ) ) {
			$media[] = '@media (max-width: 600px) { ' . $selector . ' { letter-spacing: ' . (float) $letter_spacing_m . 'px; } }';
		}
		if ( ! empty( $media ) ) {
			$css .= "\n" . implode( "\n", $media ) . "\n";
		}

		if ( $css !== '' ) {
			wp_add_inline_style( 'login', $css );
		}
	}

	/**
	 * Output CSS for Label tab: typography and text color on #login form label.
	 * Only outputs when at least one label setting is non-default.
	 */
	public function applyFormCustomizationLabelStyles(): void {
		$settings = $this->getSettings();
		$selector = $this->getLoginLabelCssSelector( $settings );
		$rules    = [];

		$font_family = isset( $settings['label_font_family'] ) ? trim( (string) $settings['label_font_family'] ) : '';
		if ( $font_family !== '' ) {
			$rules[] = 'font-family: ' . $font_family . ';';
		}
		$font_size_d = isset( $settings['label_font_size_desktop'] ) ? trim( (string) $settings['label_font_size_desktop'] ) : '';
		if ( $font_size_d !== '' && (int) $font_size_d >= 10 && (int) $font_size_d <= 72 ) {
			$rules[] = 'font-size: ' . (int) $font_size_d . 'px;';
		}
		$font_weight = isset( $settings['label_font_weight'] ) ? trim( (string) $settings['label_font_weight'] ) : '';
		if ( $font_weight !== '' && in_array( $font_weight, [ '300', '400', '500', '600', '700' ], true ) ) {
			$rules[] = 'font-weight: ' . $font_weight . ';';
		}
		$line_height_d = isset( $settings['label_line_height_desktop'] ) ? trim( (string) $settings['label_line_height_desktop'] ) : '';
		if ( $line_height_d !== '' && is_numeric( $line_height_d ) && (float) $line_height_d >= 0.5 && (float) $line_height_d <= 3 ) {
			$rules[] = 'line-height: ' . (float) $line_height_d . ';';
		}
		$letter_spacing_d = isset( $settings['label_letter_spacing_desktop'] ) ? trim( (string) $settings['label_letter_spacing_desktop'] ) : '';
		if ( $letter_spacing_d !== '' && is_numeric( $letter_spacing_d ) ) {
			$rules[] = 'letter-spacing: ' . (float) $letter_spacing_d . 'px;';
		}
		$text_transform = isset( $settings['label_text_transform'] ) ? (string) $settings['label_text_transform'] : '';
		if ( $text_transform !== '' && in_array( $text_transform, [ 'none', 'lowercase', 'uppercase', 'capitalize' ], true ) ) {
			$rules[] = 'text-transform: ' . $text_transform . ';';
		}
		$font_style = isset( $settings['label_font_style'] ) ? (string) $settings['label_font_style'] : '';
		if ( $font_style !== '' && in_array( $font_style, [ 'normal', 'italic', 'oblique' ], true ) ) {
			$rules[] = 'font-style: ' . $font_style . ';';
		}

		$text_color = isset( $settings['label_text_color'] ) ? trim( (string) $settings['label_text_color'] ) : '';
		if ( $text_color !== '' && preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/', $text_color ) ) {
			$rules[] = 'color: ' . $text_color . ';';
		}

		$spacing_bottom = isset( $settings['label_spacing_bottom'] ) ? trim( (string) $settings['label_spacing_bottom'] ) : '';
		if ( $spacing_bottom !== '' && (int) $spacing_bottom >= 0 && (int) $spacing_bottom <= 100 ) {
			$rules[] = 'margin-bottom: ' . (int) $spacing_bottom . 'px;';
		}

		$css = '';
		if ( ! empty( $rules ) ) {
			$css .= "\n" . $selector . ' { ' . implode( ' ', $rules ) . " }\n";
		}

		$font_size_t = isset( $settings['label_font_size_tablet'] ) ? trim( (string) $settings['label_font_size_tablet'] ) : '';
		$font_size_m = isset( $settings['label_font_size_mobile'] ) ? trim( (string) $settings['label_font_size_mobile'] ) : '';
		$line_height_t = isset( $settings['label_line_height_tablet'] ) ? trim( (string) $settings['label_line_height_tablet'] ) : '';
		$line_height_m = isset( $settings['label_line_height_mobile'] ) ? trim( (string) $settings['label_line_height_mobile'] ) : '';
		$letter_spacing_t = isset( $settings['label_letter_spacing_tablet'] ) ? trim( (string) $settings['label_letter_spacing_tablet'] ) : '';
		$letter_spacing_m = isset( $settings['label_letter_spacing_mobile'] ) ? trim( (string) $settings['label_letter_spacing_mobile'] ) : '';
		$media = [];
		if ( $font_size_t !== '' && (int) $font_size_t >= 10 && (int) $font_size_t <= 72 ) {
			$media[] = '@media (max-width: 1024px) { ' . $selector . ' { font-size: ' . (int) $font_size_t . 'px; } }';
		}
		if ( $font_size_m !== '' && (int) $font_size_m >= 10 && (int) $font_size_m <= 72 ) {
			$media[] = '@media (max-width: 600px) { ' . $selector . ' { font-size: ' . (int) $font_size_m . 'px; } }';
		}
		if ( $line_height_t !== '' && is_numeric( $line_height_t ) && (float) $line_height_t >= 0.5 && (float) $line_height_t <= 3 ) {
			$media[] = '@media (max-width: 1024px) { ' . $selector . ' { line-height: ' . (float) $line_height_t . '; } }';
		}
		if ( $line_height_m !== '' && is_numeric( $line_height_m ) && (float) $line_height_m >= 0.5 && (float) $line_height_m <= 3 ) {
			$media[] = '@media (max-width: 600px) { ' . $selector . ' { line-height: ' . (float) $line_height_m . '; } }';
		}
		if ( $letter_spacing_t !== '' && is_numeric( $letter_spacing_t ) ) {
			$media[] = '@media (max-width: 1024px) { ' . $selector . ' { letter-spacing: ' . (float) $letter_spacing_t . 'px; } }';
		}
		if ( $letter_spacing_m !== '' && is_numeric( $letter_spacing_m ) ) {
			$media[] = '@media (max-width: 600px) { ' . $selector . ' { letter-spacing: ' . (float) $letter_spacing_m . 'px; } }';
		}
		if ( ! empty( $media ) ) {
			$css .= "\n" . implode( "\n", $media ) . "\n";
		}

		if ( $css !== '' ) {
			wp_add_inline_style( 'login', $css );
		}
	}

	/**
	 * Inject img tag for custom logo so height is auto (no crop). Runs on login_footer.
	 */
	public function injectLoginLogoImg(): void {
		$settings = $this->getSettings();
		if ( ( $settings['logo_source'] ?? 'upload' ) === 'site_icon' ) {
			return;
		}
		$logo_url = $this->resolveLogoUrl( $settings );
		if ( $logo_url === '' ) {
			return;
		}
		?>
		<script>
		(function() {
			var link = document.querySelector('.login:not(.interim-login) h1.wp-login-logo a') || document.querySelector('.login:not(.interim-login) h1 a');
			if (!link) return;
			var img = document.createElement('img');
			img.src = <?php echo wp_json_encode( $logo_url ); ?>;
			img.alt = '';
			img.className = 'wpps-login-logo-img';
			link.innerHTML = '';
			link.appendChild(img);
		})();
		</script>
		<?php
	}

	/**
	 * Filter the login logo link URL when form logo customization is active.
	 *
	 * @param string $url Current logo URL.
	 * @return string
	 */
	public function filterLoginLogoUrl( string $url ): string {
		$settings  = $this->getSettings();
		$logo_link = isset( $settings['logo_link'] ) ? trim( (string) $settings['logo_link'] ) : '';

		if ( $logo_link !== '' ) {
			$sanitized = esc_url_raw( $logo_link );
			return $sanitized !== '' ? esc_url( $sanitized ) : home_url( '/' );
		}

		if ( ! empty( $settings['hide_logo'] ) && $settings['hide_logo'] !== '0' ) {
			return $url;
		}

		if ( $this->resolveLogoUrl( $settings ) !== '' ) {
			return home_url( '/' );
		}

		return $url;
	}

	/**
	 * Use the site name as accessible login logo link text.
	 *
	 * @param string $text Current logo link text.
	 * @return string
	 */
	public function filterLoginLogoText( string $text ): string {
		$name = get_bloginfo( 'name', 'display' );

		return is_string( $name ) && $name !== '' ? $name : $text;
	}

}
