<?php
/**
 * Template Builder — AJAX handlers for save / load / delete / activate custom templates,
 * plus preset (ready-made) template definitions and block rendering.
 *
 * @package PrivexPasswordProtect
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class PPMSTR_Template_Builder
 */
class PPMSTR_Template_Builder {

	const OPTION_KEY = 'ppmstr_custom_templates';

	/**
	 * Register AJAX hooks.
	 */
	public function init() {
		add_action( 'wp_ajax_ppmstr_save_template', array( $this, 'ajax_save' ) );
		add_action( 'wp_ajax_ppmstr_load_template', array( $this, 'ajax_load' ) );
		add_action( 'wp_ajax_ppmstr_delete_template', array( $this, 'ajax_delete' ) );
		add_action( 'wp_ajax_ppmstr_list_templates', array( $this, 'ajax_list' ) );
		add_action( 'wp_ajax_ppmstr_activate_template', array( $this, 'ajax_activate' ) );
	}

	/* ================================================================
	 * Preset (ready-made) templates
	 * ================================================================ */
	/**
	 * Get built-in preset templates.
	 *
	 * @return array
	 */
	public static function get_presets() {
		return array(
			/* ============================================================
			 * FREE / BASIC
			 * ============================================================ */
			'preset_minimal' => array(
				'id'     => 'preset_minimal',
				'name'   => __( 'Minimal', 'privex-password-protect' ),
				'preset' => true,
				'pro'    => false,
				'global' => array( 'bgColor' => '#FFFFFF', 'bgImage' => '', 'cardBg' => '#FFFFFF', 'cardRadius' => 0, 'cardShadow' => false, 'cardPadding' => 52, 'maxWidth' => 380 ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'heading', 'zone' => 'card', 'content' => 'Protected', 'settings' => array( 'fontsize' => 34, 'fontweight' => 800, 'color' => '#111111', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b2', 'type' => 'description', 'zone' => 'card', 'content' => 'Please enter the password to continue.', 'settings' => array( 'fontsize' => 15, 'fontweight' => 400, 'color' => '#888888', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 30 ) ),
					array( 'id' => 'b3', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#C0392B', 'errbg' => 'rgba(192,57,43,0.08)', 'borderradius' => 4, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b4', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Password', 'settings' => array( 'fontsize' => 14, 'borderradius' => 4, 'bordercolor' => '#CCCCCC', 'inputbg' => '#FFFFFF', 'textcolor' => '#111111', 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b5', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'Continue', 'settings' => array( 'bgcolor' => '#111111', 'textcolor' => '#FFFFFF', 'borderradius' => 4, 'fontsize' => 14, 'fontweight' => 600, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_minimal_dark' => array(
				'id'     => 'preset_minimal_dark',
				'name'   => __( 'Minimal Dark', 'privex-password-protect' ),
				'preset' => true,
				'pro'    => false,
				'global' => array( 'bgColor' => '#000000', 'bgImage' => '', 'cardBg' => '#000000', 'cardRadius' => 0, 'cardShadow' => false, 'cardPadding' => 52, 'maxWidth' => 380 ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'heading', 'zone' => 'card', 'content' => 'Protected', 'settings' => array( 'fontsize' => 34, 'fontweight' => 800, 'color' => '#FFFFFF', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b2', 'type' => 'description', 'zone' => 'card', 'content' => 'Please enter the password to continue.', 'settings' => array( 'fontsize' => 15, 'fontweight' => 400, 'color' => '#AAAAAA', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 30 ) ),
					array( 'id' => 'b3', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#FF5555', 'errbg' => 'rgba(255,85,85,0.1)', 'borderradius' => 4, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b4', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Password', 'settings' => array( 'fontsize' => 14, 'borderradius' => 4, 'bordercolor' => '#333333', 'inputbg' => '#111111', 'textcolor' => '#FFFFFF', 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b5', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'Continue', 'settings' => array( 'bgcolor' => '#FFFFFF', 'textcolor' => '#000000', 'borderradius' => 4, 'fontsize' => 14, 'fontweight' => 600, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),

			/* ============================================================
			 * PRO — ANIMATED
			 * ============================================================ */
			'preset_aurora' => array(
				'id'     => 'preset_aurora',
				'name'   => __( 'Aurora Glow (animated)', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#1a0f3d', 'bgImage' => '', 'bgGradient' => true, 'bgGradFrom' => '#6a11cb', 'bgGradTo' => '#2575fc', 'bgGradAngle' => 135, 'cardBg' => '#ffffff', 'cardRadius' => 24, 'cardShadow' => true, 'cardPadding' => 48, 'maxWidth' => 440, 'animation' => 'aurora' ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'lock', 'size' => 56, 'color' => '#6a11cb', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 20 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'Welcome Back', 'settings' => array( 'fontsize' => 28, 'fontweight' => 700, 'color' => '#1A1A2E', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'Enter the password to unlock this beautiful experience.', 'settings' => array( 'fontsize' => 14, 'fontweight' => 400, 'color' => '#6C7293', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 28 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#C0392B', 'errbg' => 'rgba(225,112,85,0.1)', 'borderradius' => 10, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Enter access code…', 'settings' => array( 'fontsize' => 14, 'borderradius' => 14, 'bordercolor' => '#E4E6EF', 'inputbg' => '#F9F9FC', 'textcolor' => '#1A1A2E', 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'Continue Access', 'settings' => array( 'bgcolor' => '#6a11cb', 'textcolor' => '#FFFFFF', 'borderradius' => 14, 'fontsize' => 15, 'fontweight' => 600, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_neon' => array(
				'id'     => 'preset_neon',
				'name'   => __( 'Neon Cyber (animated)', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#05050f', 'bgImage' => '', 'cardBg' => '#0d0d1f', 'cardRadius' => 16, 'cardShadow' => true, 'cardPadding' => 48, 'maxWidth' => 440, 'cardBorderWidth' => 1, 'cardBorderColor' => '#1a1a3e', 'animation' => 'neon' ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'shield', 'size' => 52, 'color' => '#00f0ff', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 20 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'ACCESS REQUIRED', 'settings' => array( 'fontsize' => 24, 'fontweight' => 800, 'color' => '#ffffff', 'alignment' => 'center', 'letterspacing' => 2, 'texttransform' => 'uppercase', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'Authenticate to enter the secure zone.', 'settings' => array( 'fontsize' => 13, 'fontweight' => 400, 'color' => '#7a7aab', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 28 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#ff3b6c', 'errbg' => 'rgba(255,59,108,0.1)', 'borderradius' => 8, 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => '••••••••', 'settings' => array( 'fontsize' => 14, 'borderradius' => 8, 'bordercolor' => '#1a1a3e', 'inputbg' => '#05051a', 'textcolor' => '#00f0ff', 'margintop' => 0, 'marginbottom' => 18 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'AUTHENTICATE', 'settings' => array( 'bgcolor' => '#00f0ff', 'textcolor' => '#05050f', 'borderradius' => 8, 'fontsize' => 14, 'fontweight' => 700, 'letterspacing' => 2, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_bounce' => array(
				'id'     => 'preset_bounce',
				'name'   => __( 'Soft Bounce (animated)', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#fff5f7', 'bgImage' => '', 'bgGradient' => true, 'bgGradFrom' => '#ffecd2', 'bgGradTo' => '#fcb69f', 'bgGradAngle' => 160, 'cardBg' => '#ffffff', 'cardRadius' => 28, 'cardShadow' => true, 'cardPadding' => 44, 'maxWidth' => 420, 'animation' => 'bounce' ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'key', 'size' => 56, 'color' => '#ff6b9d', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 20 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'Hey there!', 'settings' => array( 'fontsize' => 30, 'fontweight' => 700, 'color' => '#2d2d4d', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'Got the secret? Drop it in below ✨', 'settings' => array( 'fontsize' => 15, 'fontweight' => 400, 'color' => '#8a8aa3', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 26 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#e74c3c', 'errbg' => 'rgba(231,76,60,0.1)', 'borderradius' => 12, 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Your secret password', 'settings' => array( 'fontsize' => 14, 'borderradius' => 16, 'bordercolor' => '#ffe0eb', 'inputbg' => '#fff9fb', 'textcolor' => '#2d2d4d', 'margintop' => 0, 'marginbottom' => 18 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => "Let's Go →", 'settings' => array( 'bgcolor' => '#ff6b9d', 'textcolor' => '#FFFFFF', 'borderradius' => 16, 'fontsize' => 15, 'fontweight' => 700, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_orbs' => array(
				'id'     => 'preset_orbs',
				'name'   => __( 'Cosmic Orbs (animated)', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#07071A', 'bgImage' => '', 'bgGradient' => true, 'bgGradFrom' => '#6C22E0', 'bgGradTo' => '#00C9E0', 'bgGradAngle' => 135, 'cardBg' => '#FFFFFF', 'cardRadius' => 28, 'cardShadow' => true, 'cardPadding' => 52, 'maxWidth' => 440, 'animation' => 'orbs' ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'fingerprint', 'size' => 52, 'color' => '#A855F7', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 18 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'Members Only', 'settings' => array( 'fontsize' => 27, 'fontweight' => 800, 'color' => '#FFFFFF', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'This area is password protected. Enter your key to continue.', 'settings' => array( 'fontsize' => 14, 'fontweight' => 400, 'color' => 'rgba(255,255,255,0.58)', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 28 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#FF8080', 'errbg' => 'rgba(255,100,100,0.12)', 'borderradius' => 8, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Enter password…', 'settings' => array( 'fontsize' => 14, 'borderradius' => 14, 'bordercolor' => 'rgba(255,255,255,0.15)', 'inputbg' => 'rgba(255,255,255,0.08)', 'textcolor' => '#FFFFFF', 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'Enter', 'settings' => array( 'bgcolor' => '#6C22E0', 'textcolor' => '#FFFFFF', 'borderradius' => 14, 'fontsize' => 15, 'fontweight' => 700, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_pulse' => array(
				'id'     => 'preset_pulse',
				'name'   => __( 'Pulse Shield (animated)', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#030D1A', 'bgImage' => '', 'bgGradient' => true, 'bgGradFrom' => '#00C875', 'bgGradTo' => '#007AFF', 'bgGradAngle' => 135, 'cardBg' => '#0A1628', 'cardRadius' => 20, 'cardShadow' => true, 'cardPadding' => 48, 'maxWidth' => 420, 'cardBorderWidth' => 1, 'cardBorderColor' => '#1A3A5A', 'animation' => 'pulse' ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'shield', 'size' => 54, 'color' => '#00C875', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 20 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'Secure Access', 'settings' => array( 'fontsize' => 28, 'fontweight' => 700, 'color' => '#E8F4FD', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'Authentication required. Enter your password to proceed.', 'settings' => array( 'fontsize' => 14, 'fontweight' => 400, 'color' => '#5B9CBD', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 28 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#FF8080', 'errbg' => 'rgba(255,100,100,0.1)', 'borderradius' => 8, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Enter password…', 'settings' => array( 'fontsize' => 14, 'borderradius' => 10, 'bordercolor' => '#0E3460', 'inputbg' => '#040F22', 'textcolor' => '#E8F4FD', 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'Authenticate', 'settings' => array( 'bgcolor' => '#00C875', 'textcolor' => '#030D1A', 'borderradius' => 10, 'fontsize' => 15, 'fontweight' => 700, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_cyber' => array(
				'id'     => 'preset_cyber',
				'name'   => __( 'Cyber Grid (animated)', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#040812', 'bgImage' => '', 'bgGradient' => true, 'bgGradFrom' => '#00FF88', 'bgGradTo' => '#00BFFF', 'bgGradAngle' => 135, 'cardBg' => '#080F1E', 'cardRadius' => 8, 'cardShadow' => true, 'cardPadding' => 48, 'maxWidth' => 420, 'cardBorderWidth' => 1, 'cardBorderColor' => '#0E2A1A', 'animation' => 'cyber' ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'userlock', 'size' => 46, 'color' => '#00FF88', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 20 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'ACCESS DENIED', 'settings' => array( 'fontsize' => 24, 'fontweight' => 800, 'color' => '#00FF88', 'alignment' => 'center', 'letterspacing' => 3, 'texttransform' => 'uppercase', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'Authorization key required to continue.', 'settings' => array( 'fontsize' => 13, 'fontweight' => 400, 'color' => 'rgba(0,255,136,0.45)', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 28, 'fontfamily' => '"Courier New", monospace' ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#FF4444', 'errbg' => 'rgba(255,68,68,0.1)', 'borderradius' => 4, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => '_ _ _ _ _ _ _ _', 'settings' => array( 'fontsize' => 14, 'borderradius' => 4, 'bordercolor' => 'rgba(0,255,136,0.2)', 'inputbg' => '#040D1A', 'textcolor' => '#00FF88', 'margintop' => 0, 'marginbottom' => 16, 'fontfamily' => '"Courier New", monospace' ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'AUTHORIZE', 'settings' => array( 'bgcolor' => '#00FF88', 'textcolor' => '#040812', 'borderradius' => 4, 'fontsize' => 14, 'fontweight' => 800, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),

			/* ============================================================
			 * PRO — STANDARD
			 * ============================================================ */
			'preset_dark' => array(
				'id'     => 'preset_dark',
				'name'   => __( 'Dark Mode', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#0D0D0D', 'bgImage' => '', 'cardBg' => '#1A1A1A', 'cardRadius' => 12, 'cardShadow' => true, 'cardPadding' => 48, 'maxWidth' => 420, 'cardBorderWidth' => 1, 'cardBorderColor' => '#2A2A2A' ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'lock', 'size' => 44, 'color' => '#FFFFFF', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 20 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'Access Required', 'settings' => array( 'fontsize' => 24, 'fontweight' => 700, 'color' => '#FFFFFF', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'Enter the password to continue.', 'settings' => array( 'fontsize' => 14, 'fontweight' => 400, 'color' => '#777777', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 24 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#FF6B6B', 'errbg' => 'rgba(255,107,107,0.1)', 'borderradius' => 6, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Enter password…', 'settings' => array( 'fontsize' => 14, 'borderradius' => 8, 'bordercolor' => '#333333', 'inputbg' => '#111111', 'textcolor' => '#FFFFFF', 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'Unlock', 'settings' => array( 'bgcolor' => '#FFFFFF', 'textcolor' => '#0D0D0D', 'borderradius' => 8, 'fontsize' => 14, 'fontweight' => 700, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_flat' => array(
				'id'     => 'preset_flat',
				'name'   => __( 'Clean Light', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#F4F6FA', 'bgImage' => '', 'cardBg' => '#FFFFFF', 'cardRadius' => 14, 'cardShadow' => true, 'cardPadding' => 44, 'maxWidth' => 420 ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'lock', 'size' => 44, 'color' => '#4F46E5', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 18 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'Content Protected', 'settings' => array( 'fontsize' => 24, 'fontweight' => 700, 'color' => '#1E1E2D', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 6 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'Enter your password below to get access.', 'settings' => array( 'fontsize' => 14, 'fontweight' => 400, 'color' => '#9096A8', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 24 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#C0392B', 'errbg' => 'rgba(192,57,43,0.08)', 'borderradius' => 8, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Enter password…', 'settings' => array( 'fontsize' => 14, 'borderradius' => 10, 'bordercolor' => '#E8EAF0', 'inputbg' => '#F8F9FC', 'textcolor' => '#1E1E2D', 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'Unlock', 'settings' => array( 'bgcolor' => '#4F46E5', 'textcolor' => '#FFFFFF', 'borderradius' => 10, 'fontsize' => 14, 'fontweight' => 600, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_elegant' => array(
				'id'     => 'preset_elegant',
				'name'   => __( 'Elegant', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#F0F2F8', 'bgImage' => '', 'cardBg' => '#FFFFFF', 'cardRadius' => 20, 'cardShadow' => true, 'cardPadding' => 48, 'maxWidth' => 440 ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'lock', 'size' => 52, 'color' => '#6C5CE7', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 20 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'This Content is Protected', 'settings' => array( 'fontsize' => 26, 'fontweight' => 700, 'color' => '#1A1A2E', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'Enter the password below to unlock and view this content.', 'settings' => array( 'fontsize' => 14, 'fontweight' => 400, 'color' => '#6C7293', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 24 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#C0392B', 'errbg' => 'rgba(225,112,85,0.1)', 'borderradius' => 8, 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Enter password…', 'settings' => array( 'fontsize' => 14, 'borderradius' => 12, 'bordercolor' => '#E4E6EF', 'inputbg' => '#F9F9FC', 'textcolor' => '#1A1A2E', 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'Unlock', 'settings' => array( 'bgcolor' => '#6C5CE7', 'textcolor' => '#FFFFFF', 'borderradius' => 12, 'fontsize' => 15, 'fontweight' => 600, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_corporate' => array(
				'id'     => 'preset_corporate',
				'name'   => __( 'Corporate', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#EEF2FF', 'bgImage' => '', 'bgGradient' => true, 'bgGradFrom' => '#EEF2FF', 'bgGradTo' => '#DBEAFE', 'bgGradAngle' => 150, 'cardBg' => '#FFFFFF', 'cardRadius' => 16, 'cardShadow' => true, 'cardPadding' => 48, 'maxWidth' => 440, 'cardBorderWidth' => 1, 'cardBorderColor' => '#E0E7FF' ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'shield', 'size' => 48, 'color' => '#2563EB', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 20 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'Secure Area', 'settings' => array( 'fontsize' => 26, 'fontweight' => 700, 'color' => '#1E3A5F', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'This area is restricted. Please enter your access password.', 'settings' => array( 'fontsize' => 14, 'fontweight' => 400, 'color' => '#64748B', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 24 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#DC2626', 'errbg' => 'rgba(220,38,38,0.08)', 'borderradius' => 8, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Enter access password…', 'settings' => array( 'fontsize' => 14, 'borderradius' => 10, 'bordercolor' => '#BFDBFE', 'inputbg' => '#F8FAFF', 'textcolor' => '#1E3A5F', 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'Access Now', 'settings' => array( 'bgcolor' => '#2563EB', 'textcolor' => '#FFFFFF', 'borderradius' => 10, 'fontsize' => 15, 'fontweight' => 600, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_soft' => array(
				'id'     => 'preset_soft',
				'name'   => __( 'Soft Pastel', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#FDF4FF', 'bgImage' => '', 'bgGradient' => true, 'bgGradFrom' => '#FDF4FF', 'bgGradTo' => '#F0ABFC', 'bgGradAngle' => 135, 'cardBg' => '#FFFFFF', 'cardRadius' => 24, 'cardShadow' => true, 'cardPadding' => 48, 'maxWidth' => 420 ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'key', 'size' => 50, 'color' => '#A855F7', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 18 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'Password Required', 'settings' => array( 'fontsize' => 26, 'fontweight' => 700, 'color' => '#3B1F6A', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'This page is private. Enter the password to view.', 'settings' => array( 'fontsize' => 14, 'fontweight' => 400, 'color' => '#A78BC0', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 26 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#C0392B', 'errbg' => 'rgba(192,57,43,0.07)', 'borderradius' => 10, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Enter password…', 'settings' => array( 'fontsize' => 14, 'borderradius' => 14, 'bordercolor' => '#E9D5FF', 'inputbg' => '#FDF8FF', 'textcolor' => '#3B1F6A', 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'Unlock Access', 'settings' => array( 'bgcolor' => '#A855F7', 'textcolor' => '#FFFFFF', 'borderradius' => 14, 'fontsize' => 15, 'fontweight' => 600, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_glass' => array(
				'id'     => 'preset_glass',
				'name'   => __( 'Glassmorphism', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#3A0CA3', 'bgImage' => '', 'bgGradient' => true, 'bgGradFrom' => '#7209B7', 'bgGradTo' => '#3A86FF', 'bgGradAngle' => 135, 'cardBg' => '#FFFFFF', 'cardRadius' => 24, 'cardShadow' => true, 'cardPadding' => 48, 'maxWidth' => 440, 'animation' => 'glass' ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'fingerprint', 'size' => 52, 'color' => '#FFFFFF', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 20 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'Private Access', 'settings' => array( 'fontsize' => 28, 'fontweight' => 700, 'color' => '#FFFFFF', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'Enter the password to unlock this content.', 'settings' => array( 'fontsize' => 14, 'fontweight' => 400, 'color' => 'rgba(255,255,255,0.7)', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 28 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#FFB3B3', 'errbg' => 'rgba(255,100,100,0.15)', 'borderradius' => 10, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Enter password…', 'settings' => array( 'fontsize' => 14, 'borderradius' => 12, 'bordercolor' => 'rgba(255,255,255,0.25)', 'inputbg' => 'rgba(255,255,255,0.1)', 'textcolor' => '#FFFFFF', 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'Unlock', 'settings' => array( 'bgcolor' => '#FFFFFF', 'textcolor' => '#3A0CA3', 'borderradius' => 12, 'fontsize' => 15, 'fontweight' => 700, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_luxury' => array(
				'id'     => 'preset_luxury',
				'name'   => __( 'Luxury Dark Gold', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#080810', 'bgImage' => '', 'cardBg' => '#0F0F1C', 'cardRadius' => 20, 'cardShadow' => true, 'cardPadding' => 52, 'maxWidth' => 440, 'cardBorderWidth' => 1, 'cardBorderColor' => '#2A2A3E', 'animation' => 'luxury' ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'key', 'size' => 48, 'color' => '#C9A84C', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 24 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'EXCLUSIVE ACCESS', 'settings' => array( 'fontsize' => 22, 'fontweight' => 800, 'color' => '#F0D080', 'alignment' => 'center', 'letterspacing' => 3, 'texttransform' => 'uppercase', 'margintop' => 0, 'marginbottom' => 10 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'This is a private area. Enter your password to gain access.', 'settings' => array( 'fontsize' => 13, 'fontweight' => 400, 'color' => '#6B6B8A', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 30 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#FF8080', 'errbg' => 'rgba(255,100,100,0.1)', 'borderradius' => 8, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Enter access code…', 'settings' => array( 'fontsize' => 14, 'borderradius' => 10, 'bordercolor' => '#2A2A3E', 'inputbg' => '#07070F', 'textcolor' => '#F0D080', 'margintop' => 0, 'marginbottom' => 18 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'ENTER', 'settings' => array( 'bgcolor' => '#C9A84C', 'textcolor' => '#080810', 'borderradius' => 10, 'fontsize' => 14, 'fontweight' => 800, 'letterspacing' => 2, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_ocean' => array(
				'id'     => 'preset_ocean',
				'name'   => __( 'Ocean Depth', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#020D1F', 'bgImage' => '', 'bgGradient' => true, 'bgGradFrom' => '#020D1F', 'bgGradTo' => '#0A3D62', 'bgGradAngle' => 160, 'cardBg' => '#071D3A', 'cardRadius' => 20, 'cardShadow' => true, 'cardPadding' => 48, 'maxWidth' => 440, 'cardBorderWidth' => 1, 'cardBorderColor' => '#0E3460', 'animation' => 'ocean' ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'shield', 'size' => 50, 'color' => '#00C9FF', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 20 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'Secure Zone', 'settings' => array( 'fontsize' => 28, 'fontweight' => 700, 'color' => '#E8F4FD', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'Password protected content. Enter your key to dive in.', 'settings' => array( 'fontsize' => 14, 'fontweight' => 400, 'color' => '#5B9CBD', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 28 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#FF8080', 'errbg' => 'rgba(255,100,100,0.1)', 'borderradius' => 8, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Enter password…', 'settings' => array( 'fontsize' => 14, 'borderradius' => 12, 'bordercolor' => '#0E3460', 'inputbg' => '#030F22', 'textcolor' => '#E8F4FD', 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'Dive In', 'settings' => array( 'bgcolor' => '#00C9FF', 'textcolor' => '#020D1F', 'borderradius' => 12, 'fontsize' => 15, 'fontweight' => 700, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
			'preset_sunset' => array(
				'id'     => 'preset_sunset',
				'name'   => __( 'Sunset Warm', 'privex-password-protect' ),
				'preset' => true, 'pro' => true,
				'global' => array( 'bgColor' => '#FFF3E0', 'bgImage' => '', 'bgGradient' => true, 'bgGradFrom' => '#FF6B35', 'bgGradTo' => '#F7C59F', 'bgGradAngle' => 145, 'cardBg' => '#FFFFFF', 'cardRadius' => 24, 'cardShadow' => true, 'cardPadding' => 48, 'maxWidth' => 430, 'animation' => 'sunrise' ),
				'blocks' => array(
					array( 'id' => 'b1', 'type' => 'icon', 'zone' => 'card', 'content' => '', 'settings' => array( 'icontype' => 'lock', 'size' => 50, 'color' => '#FF6B35', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 18 ) ),
					array( 'id' => 'b2', 'type' => 'heading', 'zone' => 'card', 'content' => 'Private Content', 'settings' => array( 'fontsize' => 27, 'fontweight' => 700, 'color' => '#2D1B00', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 8 ) ),
					array( 'id' => 'b3', 'type' => 'description', 'zone' => 'card', 'content' => 'Enter the password to access this content.', 'settings' => array( 'fontsize' => 14, 'fontweight' => 400, 'color' => '#B07040', 'alignment' => 'center', 'margintop' => 0, 'marginbottom' => 26 ) ),
					array( 'id' => 'b4', 'type' => 'error_message', 'zone' => 'card', 'content' => '', 'settings' => array( 'fontsize' => 13, 'textcolor' => '#C0392B', 'errbg' => 'rgba(192,57,43,0.08)', 'borderradius' => 10, 'margintop' => 0, 'marginbottom' => 14 ) ),
					array( 'id' => 'b5', 'type' => 'password_input', 'zone' => 'card', 'content' => 'Enter password…', 'settings' => array( 'fontsize' => 14, 'borderradius' => 14, 'bordercolor' => '#FFD5B8', 'inputbg' => '#FFF8F3', 'textcolor' => '#2D1B00', 'margintop' => 0, 'marginbottom' => 16 ) ),
					array( 'id' => 'b6', 'type' => 'submit_button', 'zone' => 'card', 'content' => 'Unlock →', 'settings' => array( 'bgcolor' => '#FF6B35', 'textcolor' => '#FFFFFF', 'borderradius' => 14, 'fontsize' => 15, 'fontweight' => 700, 'margintop' => 0, 'marginbottom' => 0 ) ),
				),
			),
		);
	}

	/**
	 * Get a single template by ID — checks both customs and presets.
	 *
	 * @param string $template_id Template ID.
	 * @return array|null
	 */
	public static function get_template( $template_id ) {
		$customs = self::get_all();
		if ( isset( $customs[ $template_id ] ) ) {
			return $customs[ $template_id ];
		}
		$presets = self::get_presets();
		if ( isset( $presets[ $template_id ] ) ) {
			return $presets[ $template_id ];
		}
		return null;
	}

	/**
	 * Check if a template ID is a builder template (preset or custom).
	 *
	 * @param string $template_id Template ID.
	 * @return bool
	 */
	public static function is_builder_template( $template_id ) {
		return strpos( $template_id, 'custom_' ) === 0 || strpos( $template_id, 'preset_' ) === 0;
	}

	/**
	 * Get all custom templates.
	 *
	 * @return array
	 */
	public static function get_all() {
		return get_option( self::OPTION_KEY, array() );
	}

	/* ================================================================
	 * AJAX Handlers
	 * ================================================================ */

	/**
	 * Save / update a custom template.
	 */
	public function ajax_save() {
		check_ajax_referer( 'ppmstr_admin_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( __( 'Permission denied.', 'privex-password-protect' ) );
		}

		$template_id = isset( $_POST['template_id'] ) ? sanitize_text_field( wp_unslash( $_POST['template_id'] ) ) : '';
		$name        = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$blocks_json = isset( $_POST['blocks'] ) ? wp_unslash( $_POST['blocks'] ) : '[]';
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$global_json = isset( $_POST['global_styles'] ) ? wp_unslash( $_POST['global_styles'] ) : '{}';

		if ( empty( $name ) ) {
			wp_send_json_error( __( 'Template name is required.', 'privex-password-protect' ) );
		}

		$blocks = json_decode( $blocks_json, true );
		$global = json_decode( $global_json, true );

		if ( ! is_array( $blocks ) || ! is_array( $global ) ) {
			wp_send_json_error( __( 'Invalid template data.', 'privex-password-protect' ) );
		}

		$clean_blocks = $this->sanitize_blocks( $blocks );
		$clean_global = $this->sanitize_global( $global );

		$templates = self::get_all();

		if ( empty( $template_id ) || strpos( $template_id, 'preset_' ) === 0 ) {
			// Use strtolower to ensure ID is always lowercase — sanitize_key() lowercases on retrieval
			// so a mixed-case generated ID would cause lookup failures.
			$template_id = 'custom_' . strtolower( wp_generate_password( 8, false ) );
		}

		$templates[ $template_id ] = array(
			'id'      => $template_id,
			'name'    => $name,
			'created' => isset( $templates[ $template_id ]['created'] ) ? $templates[ $template_id ]['created'] : time(),
			'updated' => time(),
			'global'  => $clean_global,
			'blocks'  => $clean_blocks,
		);

		update_option( self::OPTION_KEY, $templates );

		wp_send_json_success( array(
			'message'     => __( 'Template saved!', 'privex-password-protect' ),
			'template_id' => $template_id,
		) );
	}

	/**
	 * Load a single custom template.
	 */
	public function ajax_load() {
		check_ajax_referer( 'ppmstr_admin_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( __( 'Permission denied.', 'privex-password-protect' ) );
		}

		$template_id = isset( $_POST['template_id'] ) ? sanitize_text_field( wp_unslash( $_POST['template_id'] ) ) : '';
		$template    = self::get_template( $template_id );

		if ( ! $template ) {
			wp_send_json_error( __( 'Template not found.', 'privex-password-protect' ) );
		}

		wp_send_json_success( $template );
	}

	/**
	 * Delete a custom template.
	 */
	public function ajax_delete() {
		check_ajax_referer( 'ppmstr_admin_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( __( 'Permission denied.', 'privex-password-protect' ) );
		}

		$template_id = isset( $_POST['template_id'] ) ? sanitize_text_field( wp_unslash( $_POST['template_id'] ) ) : '';
		$templates   = self::get_all();

		if ( empty( $template_id ) || ! isset( $templates[ $template_id ] ) ) {
			$keys = is_array( $templates ) ? implode( ',', array_keys( $templates ) ) : 'not an array';
			wp_send_json_error( 'Template not found. Passed ID: ' . $template_id . ' | Available Keys: ' . $keys );
		}

		unset( $templates[ $template_id ] );
		update_option( self::OPTION_KEY, $templates );

		$settings = get_option( 'ppmstr_settings', array() );
		if ( isset( $settings['active_template'] ) && $settings['active_template'] === $template_id ) {
			$settings['active_template'] = 'preset_elegant';
			update_option( 'ppmstr_settings', $settings );
		}

		wp_send_json_success( array(
			'message' => __( 'Template deleted.', 'privex-password-protect' ),
		) );
	}

	/**
	 * List all custom templates (summary).
	 */
	public function ajax_list() {
		check_ajax_referer( 'ppmstr_admin_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( __( 'Permission denied.', 'privex-password-protect' ) );
		}

		$templates = self::get_all();
		$list      = array();

		foreach ( $templates as $id => $tpl ) {
			$list[] = array(
				'id'      => $id,
				'name'    => $tpl['name'],
				'updated' => gmdate( 'M j, Y', $tpl['updated'] ?? $tpl['created'] ?? time() ),
				'blocks'  => count( $tpl['blocks'] ?? array() ),
			);
		}

		wp_send_json_success( $list );
	}

	/**
	 * Activate a template (preset or custom).
	 */
	public function ajax_activate() {
		check_ajax_referer( 'ppmstr_admin_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( __( 'Permission denied.', 'privex-password-protect' ) );
		}

		$template_id = isset( $_POST['template_id'] ) ? sanitize_key( $_POST['template_id'] ) : '';
		if ( empty( $template_id ) ) {
			wp_send_json_error( __( 'Missing template ID.', 'privex-password-protect' ) );
		}

		$template = self::get_template( $template_id );
		if ( ! $template ) {
			wp_send_json_error( __( 'Template not found.', 'privex-password-protect' ) );
		}

		$settings                    = get_option( 'ppmstr_settings', array() );
		$settings['active_template'] = $template_id;
		update_option( 'ppmstr_settings', $settings );

		wp_send_json_success( array(
			'message' => __( 'Template activated!', 'privex-password-protect' ),
		) );
	}

	/* ================================================================
	 * Sanitization
	 * ================================================================ */

	/**
	 * Sanitize global styles.
	 *
	 * @param array $global Raw global styles.
	 * @return array
	 */
	private function sanitize_global( $global ) {
		return array(
			'bgColor'          => sanitize_hex_color( $global['bgColor'] ?? '#F0F2F8' ),
			'bgImage'          => esc_url_raw( $global['bgImage'] ?? '' ),
			'bgGradient'       => ! empty( $global['bgGradient'] ),
			'bgGradFrom'       => sanitize_hex_color( $global['bgGradFrom'] ?? '#F0F2F8' ),
			'bgGradTo'         => sanitize_hex_color( $global['bgGradTo'] ?? '#FFFFFF' ),
			'bgGradAngle'      => absint( $global['bgGradAngle'] ?? 135 ),
			'bgOverlay'        => ! empty( $global['bgOverlay'] ),
			'bgOverlayColor'   => sanitize_hex_color( $global['bgOverlayColor'] ?? '#000000' ),
			'bgOverlayOpacity' => absint( $global['bgOverlayOpacity'] ?? 50 ),
			'cardBg'           => sanitize_hex_color( $global['cardBg'] ?? '#FFFFFF' ),
			'cardRadius'       => absint( $global['cardRadius'] ?? 16 ),
			'cardShadow'       => ! empty( $global['cardShadow'] ),
			'cardPadding'      => absint( $global['cardPadding'] ?? 40 ),
			'maxWidth'         => absint( $global['maxWidth'] ?? 420 ),
			'cardGradient'     => ! empty( $global['cardGradient'] ),
			'cardGradFrom'     => sanitize_hex_color( $global['cardGradFrom'] ?? '#FFFFFF' ),
			'cardGradTo'       => sanitize_hex_color( $global['cardGradTo'] ?? '#F0F2F8' ),
			'cardGradAngle'    => absint( $global['cardGradAngle'] ?? 135 ),
			'cardBorderWidth'  => absint( $global['cardBorderWidth'] ?? 0 ),
			'cardBorderColor'  => sanitize_hex_color( $global['cardBorderColor'] ?? '#E4E6EF' ),
			'cardMinHeight'    => absint( $global['cardMinHeight'] ?? 0 ),
			'cardVisible'      => isset( $global['cardVisible'] ) ? (bool) $global['cardVisible'] : true,
			'cardAlign'        => in_array( $global['cardAlign'] ?? 'center', array( 'left', 'center', 'right' ), true ) ? $global['cardAlign'] : 'center',
			'animation'        => in_array( $global['animation'] ?? 'none', array( 'none', 'aurora', 'neon', 'bounce', 'glass', 'luxury', 'ocean', 'sunrise', 'orbs', 'pulse', 'cyber' ), true ) ? $global['animation'] : 'none',
		);
	}

	/**
	 * Get inline CSS for animation preset.
	 *
	 * @param string $animation Animation slug.
	 * @param array  $global    Global template settings (used for brand color adaptation).
	 * @param string $accent    Primary accent color (usually button bgcolor).
	 * @return string CSS (no <style> tags).
	 */
	public static function get_animation_css( $animation, $global = array(), $accent = '' ) {
		switch ( $animation ) {
			case 'aurora':
				return '
				.ppmstr-anim-aurora{background-size:300% 300%!important;animation:ppmstr-aurora-shift 14s ease-in-out infinite;}
				.ppmstr-anim-aurora .ppmstr-form-card{animation:ppmstr-fade-up .8s cubic-bezier(.2,.8,.2,1) both;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);}
				.ppmstr-anim-aurora .ppmstr-form-icon{animation:ppmstr-soft-pulse 3s ease-in-out infinite;}
				.ppmstr-anim-aurora .ppmstr-form-submit{position:relative;overflow:hidden;transition:transform .25s ease,box-shadow .25s ease;}
				.ppmstr-anim-aurora .ppmstr-form-submit:hover{transform:translateY(-2px);box-shadow:0 12px 28px rgba(106,17,203,.35);}
				.ppmstr-anim-aurora .ppmstr-form-submit::after{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(120deg,transparent,rgba(255,255,255,.4),transparent);transition:left .6s ease;}
				.ppmstr-anim-aurora .ppmstr-form-submit:hover::after{left:100%;}
				.ppmstr-anim-aurora .ppmstr-form-input:focus{box-shadow:0 0 0 4px rgba(106,17,203,.15);outline:none;}
				@keyframes ppmstr-aurora-shift{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
				@keyframes ppmstr-fade-up{from{opacity:0;transform:translateY(24px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}
				@keyframes ppmstr-soft-pulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.08);opacity:.85}}
				';

			case 'neon':
				return '
				.ppmstr-anim-neon{position:relative;overflow:hidden;}
				.ppmstr-anim-neon::before{content:"";position:absolute;inset:0;background-image:linear-gradient(rgba(0,240,255,.04) 1px,transparent 1px),linear-gradient(90deg,rgba(0,240,255,.04) 1px,transparent 1px);background-size:40px 40px;pointer-events:none;animation:ppmstr-grid-drift 20s linear infinite;}
				.ppmstr-anim-neon .ppmstr-form-card{animation:ppmstr-neon-in .6s ease both;position:relative;}
				.ppmstr-anim-neon .ppmstr-form-card::before{content:"";position:absolute;inset:-1px;border-radius:inherit;padding:1px;background:linear-gradient(135deg,#00f0ff,#ff00e5,#00f0ff);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;background-size:200% 200%;animation:ppmstr-neon-border 4s linear infinite;opacity:.6;pointer-events:none;}
				.ppmstr-anim-neon .ppmstr-form-icon{animation:ppmstr-neon-glow 2.4s ease-in-out infinite;filter:drop-shadow(0 0 12px currentColor);}
				.ppmstr-anim-neon .ppmstr-form-submit{transition:all .2s ease;box-shadow:0 0 20px rgba(0,240,255,.4);animation:ppmstr-neon-btn-pulse 2.5s ease-in-out infinite;}
				.ppmstr-anim-neon .ppmstr-form-submit:hover{transform:translateY(-1px);box-shadow:0 0 30px rgba(0,240,255,.7),0 0 60px rgba(0,240,255,.3);}
				.ppmstr-anim-neon .ppmstr-form-input{transition:all .25s ease;}
				.ppmstr-anim-neon .ppmstr-form-input:focus{outline:none;border-color:#00f0ff!important;box-shadow:0 0 0 3px rgba(0,240,255,.2),inset 0 0 12px rgba(0,240,255,.1);}
				@keyframes ppmstr-grid-drift{from{background-position:0 0}to{background-position:40px 40px}}
				@keyframes ppmstr-neon-in{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
				@keyframes ppmstr-neon-border{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
				@keyframes ppmstr-neon-glow{0%,100%{filter:drop-shadow(0 0 8px currentColor)}50%{filter:drop-shadow(0 0 20px currentColor) drop-shadow(0 0 40px currentColor)}}
				@keyframes ppmstr-neon-btn-pulse{0%,100%{box-shadow:0 0 20px rgba(0,240,255,.4)}50%{box-shadow:0 0 35px rgba(0,240,255,.65),0 0 60px rgba(0,240,255,.25)}}
				';

			case 'bounce':
				return '
				.ppmstr-anim-bounce .ppmstr-form-card{animation:ppmstr-bounce-in .9s cubic-bezier(.34,1.56,.64,1) both;}
				.ppmstr-anim-bounce .ppmstr-form-icon{animation:ppmstr-float 3.5s ease-in-out infinite;display:inline-block;}
				.ppmstr-anim-bounce .ppmstr-form-headline{animation:ppmstr-fade-down .6s ease .2s both;}
				.ppmstr-anim-bounce .ppmstr-form-desc{animation:ppmstr-fade-down .6s ease .35s both;}
				.ppmstr-anim-bounce .ppmstr-form-input-wrap{animation:ppmstr-fade-down .6s ease .5s both;}
				.ppmstr-anim-bounce .ppmstr-form-submit{animation:ppmstr-fade-down .6s ease .65s both;transition:all .25s cubic-bezier(.34,1.56,.64,1);}
				.ppmstr-anim-bounce .ppmstr-form-submit:hover{transform:scale(1.04) translateY(-2px);}
				.ppmstr-anim-bounce .ppmstr-form-submit:active{transform:scale(.97);}
				.ppmstr-anim-bounce .ppmstr-form-input{transition:all .25s ease;}
				.ppmstr-anim-bounce .ppmstr-form-input:focus{transform:translateY(-1px);box-shadow:0 6px 18px rgba(255,107,157,.18);outline:none;}
				@keyframes ppmstr-bounce-in{0%{opacity:0;transform:scale(.7) translateY(40px)}60%{opacity:1;transform:scale(1.04) translateY(-6px)}100%{transform:scale(1) translateY(0)}}
				@keyframes ppmstr-float{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}
				@keyframes ppmstr-fade-down{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}
				';

			case 'glass':
				return '
				.ppmstr-anim-glass .ppmstr-form-card{
					background:rgba(255,255,255,0.12)!important;
					backdrop-filter:blur(28px) saturate(180%);
					-webkit-backdrop-filter:blur(28px) saturate(180%);
					border:1px solid rgba(255,255,255,0.22)!important;
					box-shadow:0 8px 48px rgba(0,0,0,0.35),inset 0 1px 0 rgba(255,255,255,0.3)!important;
					animation:ppmstr-glass-in .8s cubic-bezier(.2,.8,.2,1) both;
				}
				.ppmstr-anim-glass .ppmstr-form-icon{animation:ppmstr-soft-pulse 4s ease-in-out infinite;}
				.ppmstr-anim-glass .ppmstr-form-input{background:rgba(255,255,255,0.1)!important;border-color:rgba(255,255,255,0.22)!important;color:#fff!important;transition:all .25s ease;}
				.ppmstr-anim-glass .ppmstr-form-input::placeholder{color:rgba(255,255,255,0.45)!important;}
				.ppmstr-anim-glass .ppmstr-form-input:focus{background:rgba(255,255,255,0.18)!important;border-color:rgba(255,255,255,0.5)!important;outline:none;box-shadow:0 0 0 3px rgba(255,255,255,0.12);}
				.ppmstr-anim-glass .ppmstr-eye-toggle{color:rgba(255,255,255,0.6)!important;}
				.ppmstr-anim-glass .ppmstr-form-submit{transition:all .25s ease;box-shadow:0 4px 20px rgba(255,255,255,0.2);}
				.ppmstr-anim-glass .ppmstr-form-submit:hover{transform:translateY(-2px);box-shadow:0 8px 32px rgba(255,255,255,0.3);}
				@keyframes ppmstr-glass-in{from{opacity:0;transform:translateY(20px) scale(.97)}to{opacity:1;transform:translateY(0) scale(1)}}
				@keyframes ppmstr-soft-pulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.06);opacity:.85}}
				';

			case 'luxury':
				return '
				.ppmstr-anim-luxury .ppmstr-form-card{
					animation:ppmstr-luxury-in 1s cubic-bezier(.16,1,.3,1) both;
					box-shadow:0 0 0 1px rgba(201,168,76,0.2),0 20px 60px rgba(0,0,0,0.6),0 0 80px rgba(201,168,76,0.05)!important;
				}
				.ppmstr-anim-luxury .ppmstr-form-icon{animation:ppmstr-luxury-glow 3s ease-in-out infinite;filter:drop-shadow(0 0 10px rgba(201,168,76,0.6));}
				.ppmstr-anim-luxury .ppmstr-form-submit{
					position:relative;overflow:hidden;transition:all .3s ease;
					box-shadow:0 4px 20px rgba(201,168,76,0.35);
				}
				.ppmstr-anim-luxury .ppmstr-form-submit::before{
					content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;
					background:linear-gradient(120deg,transparent 0%,rgba(255,255,255,0.3) 50%,transparent 100%);
					animation:ppmstr-shimmer 3s ease-in-out 1.5s infinite;
				}
				.ppmstr-anim-luxury .ppmstr-form-submit:hover{transform:translateY(-2px);box-shadow:0 8px 30px rgba(201,168,76,0.5);}
				.ppmstr-anim-luxury .ppmstr-form-input{transition:all .3s ease;}
				.ppmstr-anim-luxury .ppmstr-form-input:focus{border-color:rgba(201,168,76,0.5)!important;box-shadow:0 0 0 3px rgba(201,168,76,0.12);outline:none;}
				@keyframes ppmstr-luxury-in{from{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)}}
				@keyframes ppmstr-luxury-glow{0%,100%{filter:drop-shadow(0 0 6px rgba(201,168,76,0.5))}50%{filter:drop-shadow(0 0 18px rgba(201,168,76,0.9))}}
				@keyframes ppmstr-shimmer{0%{left:-100%}100%{left:200%}}
				';

			case 'ocean':
				return '
				.ppmstr-anim-ocean .ppmstr-form-card{animation:ppmstr-ocean-in .9s cubic-bezier(.2,.8,.2,1) both;}
				.ppmstr-anim-ocean::before{
					content:"";position:absolute;bottom:0;left:0;right:0;height:120px;
					background:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 1440 120\'%3E%3Cpath fill=\'rgba(0,201,255,0.06)\' d=\'M0,60 C240,100 480,20 720,60 C960,100 1200,20 1440,60 L1440,120 L0,120 Z\'/%3E%3C/svg%3E") center/cover no-repeat;
					animation:ppmstr-wave 6s ease-in-out infinite;pointer-events:none;
				}
				.ppmstr-anim-ocean .ppmstr-form-icon{animation:ppmstr-ocean-bob 4s ease-in-out infinite;filter:drop-shadow(0 0 12px rgba(0,201,255,0.6));}
				.ppmstr-anim-ocean .ppmstr-form-submit{transition:all .25s ease;box-shadow:0 4px 20px rgba(0,201,255,0.3);}
				.ppmstr-anim-ocean .ppmstr-form-submit:hover{transform:translateY(-2px);box-shadow:0 8px 32px rgba(0,201,255,0.5);}
				.ppmstr-anim-ocean .ppmstr-form-input{transition:all .25s ease;}
				.ppmstr-anim-ocean .ppmstr-form-input:focus{border-color:rgba(0,201,255,0.4)!important;box-shadow:0 0 0 3px rgba(0,201,255,0.12);outline:none;}
				@keyframes ppmstr-ocean-in{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:translateY(0)}}
				@keyframes ppmstr-ocean-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}
				@keyframes ppmstr-wave{0%,100%{transform:translateX(0)}50%{transform:translateX(-20px)}}
				';

			case 'sunrise':
				return '
				.ppmstr-anim-sunrise .ppmstr-form-card{animation:ppmstr-sunrise-in .8s cubic-bezier(.2,.8,.2,1) both;}
				.ppmstr-anim-sunrise .ppmstr-form-icon{animation:ppmstr-sunrise-pulse 3s ease-in-out infinite;}
				.ppmstr-anim-sunrise .ppmstr-form-submit{
					position:relative;overflow:hidden;transition:all .25s ease;
					box-shadow:0 4px 20px rgba(255,107,53,0.3);
				}
				.ppmstr-anim-sunrise .ppmstr-form-submit::after{
					content:"";position:absolute;top:0;left:-150%;width:150%;height:100%;
					background:linear-gradient(120deg,transparent,rgba(255,255,255,0.35),transparent);
					transition:left .5s ease;
				}
				.ppmstr-anim-sunrise .ppmstr-form-submit:hover::after{left:150%;}
				.ppmstr-anim-sunrise .ppmstr-form-submit:hover{transform:translateY(-2px);box-shadow:0 10px 30px rgba(255,107,53,0.45);}
				.ppmstr-anim-sunrise .ppmstr-form-input{transition:all .25s ease;}
				.ppmstr-anim-sunrise .ppmstr-form-input:focus{border-color:#FFB08A!important;box-shadow:0 0 0 3px rgba(255,107,53,0.12);outline:none;}
				@keyframes ppmstr-sunrise-in{from{opacity:0;transform:scale(.96) translateY(16px)}to{opacity:1;transform:scale(1) translateY(0)}}
				@keyframes ppmstr-sunrise-pulse{0%,100%{transform:scale(1) rotate(0deg)}50%{transform:scale(1.08) rotate(3deg)}}
				';

			case 'orbs':
				// Orb colors come from injected inline styles on .ppmstr-orb elements.
				// bgGradFrom/bgGradTo in the builder control the orb accent colors.
				return '
				.ppmstr-anim-orbs{background:#07071A!important;overflow:hidden;}
				.ppmstr-orb{position:absolute;border-radius:50%;filter:blur(100px);pointer-events:none;will-change:transform;}
				.ppmstr-orb--1{width:580px;height:580px;top:-180px;left:-180px;animation:ppmstr-orb1 24s ease-in-out infinite alternate;}
				.ppmstr-orb--2{width:520px;height:520px;bottom:-180px;right:-180px;animation:ppmstr-orb2 30s ease-in-out infinite alternate;}
				.ppmstr-orb--3{width:420px;height:420px;top:38%;left:52%;transform:translate(-50%,-50%);animation:ppmstr-orb3 19s ease-in-out infinite alternate;filter:blur(80px);}
				.ppmstr-anim-orbs .ppmstr-form-card{animation:ppmstr-orb-in 1s cubic-bezier(.2,.8,.2,1) both;position:relative;z-index:2;}
				.ppmstr-anim-orbs .ppmstr-form-icon{animation:ppmstr-orb-float 4.5s ease-in-out infinite;filter:drop-shadow(0 0 18px currentColor);}
				.ppmstr-anim-orbs .ppmstr-form-submit{transition:all .3s cubic-bezier(.2,.8,.2,1);}
				.ppmstr-anim-orbs .ppmstr-form-submit:hover{transform:translateY(-3px);box-shadow:0 20px 50px rgba(0,0,0,.4);}
				.ppmstr-anim-orbs .ppmstr-form-input{transition:all .25s ease;}
				.ppmstr-anim-orbs .ppmstr-form-input:focus{outline:none;box-shadow:0 0 0 3px rgba(255,255,255,.1);border-color:rgba(255,255,255,.35)!important;}
				.ppmstr-anim-orbs .ppmstr-form-input::placeholder{color:rgba(255,255,255,.38)!important;}
				.ppmstr-anim-orbs .ppmstr-eye-toggle{color:rgba(255,255,255,.45)!important;}
				@keyframes ppmstr-orb1{0%{transform:translate(0,0) scale(1)}40%{transform:translate(100px,80px) scale(1.1)}100%{transform:translate(50px,150px) scale(.93)}}
				@keyframes ppmstr-orb2{0%{transform:translate(0,0) scale(1)}40%{transform:translate(-90px,-75px) scale(1.16)}100%{transform:translate(-70px,-130px) scale(.87)}}
				@keyframes ppmstr-orb3{0%{transform:translate(-50%,-50%) scale(1)}50%{transform:translate(-62%,-35%) scale(1.22)}100%{transform:translate(-38%,-64%) scale(.8)}}
				@keyframes ppmstr-orb-in{from{opacity:0;transform:translateY(36px) scale(.94)}to{opacity:1;transform:translateY(0) scale(1)}}
				@keyframes ppmstr-orb-float{0%,100%{transform:translateY(0);filter:drop-shadow(0 0 10px currentColor)}50%{transform:translateY(-9px);filter:drop-shadow(0 0 26px currentColor)}}
				';

			case 'pulse':
				// Ring border-color is set via inline style on each .ppmstr-ring element.
				// Accent color used for focus/hover — derived from button bgcolor.
				$pa = esc_attr( $accent ?: ( $global['bgGradFrom'] ?? '#00C875' ) );
				return '
				.ppmstr-anim-pulse{overflow:hidden;}
				.ppmstr-pulse-glow{position:absolute;width:100%;height:100%;top:0;left:0;pointer-events:none;z-index:0;}
				.ppmstr-radar-rings{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);pointer-events:none;z-index:0;}
				.ppmstr-ring{position:absolute;border-radius:50%;border-width:1.5px;border-style:solid;top:50%;left:50%;transform:translate(-50%,-50%) scale(.3);opacity:0;animation:ppmstr-ring-expand 3.8s cubic-bezier(0,0,.5,1) infinite;}
				.ppmstr-ring--1{width:260px;height:260px;animation-delay:0s;}
				.ppmstr-ring--2{width:260px;height:260px;animation-delay:.95s;}
				.ppmstr-ring--3{width:260px;height:260px;animation-delay:1.9s;}
				.ppmstr-ring--4{width:260px;height:260px;animation-delay:2.85s;}
				.ppmstr-anim-pulse .ppmstr-form-card{animation:ppmstr-pulse-in .85s cubic-bezier(.2,.8,.2,1) both;position:relative;z-index:1;}
				.ppmstr-anim-pulse .ppmstr-form-icon{animation:ppmstr-pulse-icon 2.4s ease-in-out infinite;}
				.ppmstr-anim-pulse .ppmstr-form-submit{transition:all .25s ease;}
				.ppmstr-anim-pulse .ppmstr-form-submit:hover{transform:translateY(-2px);box-shadow:0 10px 36px ' . $pa . '55;}
				.ppmstr-anim-pulse .ppmstr-form-input{transition:all .25s ease;}
				.ppmstr-anim-pulse .ppmstr-form-input:focus{outline:none;border-color:' . $pa . '!important;box-shadow:0 0 0 3px ' . $pa . '30;}
				@keyframes ppmstr-ring-expand{0%{transform:translate(-50%,-50%) scale(.3);opacity:.85}100%{transform:translate(-50%,-50%) scale(5.5);opacity:0}}
				@keyframes ppmstr-pulse-in{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:translateY(0)}}
				@keyframes ppmstr-pulse-icon{0%,100%{transform:scale(1);filter:drop-shadow(0 0 5px currentColor)}50%{transform:scale(1.12);filter:drop-shadow(0 0 20px currentColor)}}
				';

			case 'cyber':
				// Scanline uses .ppmstr-cyber-scan injected div for pointer-events-safe layering.
				// Grid color uses bgGradFrom; focus/glow uses button accent color.
				$ca    = esc_attr( $accent ?: ( $global['bgGradFrom'] ?? '#00FF88' ) );
				$cg    = esc_attr( $global['bgGradFrom'] ?? '#00FF88' );
				return '
				.ppmstr-anim-cyber{overflow:hidden;}
				.ppmstr-anim-cyber::before{content:"";position:absolute;inset:0;background-image:linear-gradient(' . $cg . '10 1px,transparent 1px),linear-gradient(90deg,' . $cg . '10 1px,transparent 1px);background-size:34px 34px;pointer-events:none;z-index:0;}
				.ppmstr-cyber-scan{position:absolute;left:0;right:0;height:160px;background:linear-gradient(180deg,transparent,' . $cg . '07,transparent);animation:ppmstr-scanline 7s linear infinite;pointer-events:none;z-index:0;}
				.ppmstr-anim-cyber .ppmstr-form-card{animation:ppmstr-cyber-in .7s cubic-bezier(.2,.8,.2,1) both;position:relative;z-index:1;}
				.ppmstr-anim-cyber .ppmstr-form-icon{animation:ppmstr-cyber-icon 2s ease-in-out infinite;}
				.ppmstr-anim-cyber .ppmstr-form-submit{transition:all .2s ease;position:relative;overflow:hidden;}
				.ppmstr-anim-cyber .ppmstr-form-submit::after{content:"";position:absolute;top:0;left:-100%;width:60%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.25),transparent);animation:ppmstr-cyber-shine 4s ease-in-out 1.5s infinite;}
				.ppmstr-anim-cyber .ppmstr-form-submit:hover{transform:translateY(-2px);box-shadow:0 8px 28px ' . $ca . '55;}
				.ppmstr-anim-cyber .ppmstr-form-input{transition:all .25s ease;}
				.ppmstr-anim-cyber .ppmstr-form-input:focus{outline:none;border-color:' . $ca . '!important;box-shadow:0 0 0 3px ' . $ca . '28,inset 0 0 10px ' . $ca . '0a;}
				.ppmstr-anim-cyber .ppmstr-form-input::placeholder{color:' . $ca . '50!important;}
				@keyframes ppmstr-scanline{0%{top:-160px}100%{top:calc(100% + 160px)}}
				@keyframes ppmstr-cyber-in{from{opacity:0;transform:translateY(18px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}
				@keyframes ppmstr-cyber-shine{0%,100%{left:-100%}45%,55%{left:150%}}
				@keyframes ppmstr-cyber-icon{0%,100%{filter:drop-shadow(0 0 5px ' . $ca . ')}50%{filter:drop-shadow(0 0 16px ' . $ca . ') drop-shadow(0 0 30px ' . $ca . '60)}}
				';
		}
		return '';
	}

	/**
	 * Sanitize blocks array.
	 *
	 * @param array $blocks Raw blocks.
	 * @return array Sanitized blocks.
	 */
	private function sanitize_blocks( $blocks ) {
		$allowed_zones = array( 'before_card', 'card', 'after_card' );
		$clean         = array();

		foreach ( $blocks as $block ) {
			if ( ! isset( $block['type'] ) ) {
				continue;
			}

			$zone = sanitize_key( $block['zone'] ?? 'card' );
			if ( ! in_array( $zone, $allowed_zones, true ) ) {
				$zone = 'card';
			}

			$clean_block = array(
				'id'       => sanitize_key( $block['id'] ?? wp_generate_password( 6, false ) ),
				'type'     => sanitize_key( $block['type'] ),
				'zone'     => $zone,
				'content'  => wp_kses_post( $block['content'] ?? '' ),
				'settings' => array(),
			);

			if ( isset( $block['settings'] ) && is_array( $block['settings'] ) ) {
				foreach ( $block['settings'] as $key => $value ) {
					$skey = sanitize_key( $key );
					if ( is_bool( $value ) ) {
						$clean_block['settings'][ $skey ] = $value;
					} elseif ( is_numeric( $value ) ) {
						$clean_block['settings'][ $skey ] = floatval( $value );
					} elseif ( preg_match( '/^#[0-9A-Fa-f]{3,8}$/', $value ) ) {
						$clean_block['settings'][ $skey ] = sanitize_hex_color( $value );
					} elseif ( 'transparent' === $value ) {
						$clean_block['settings'][ $skey ] = 'transparent';
					} elseif ( preg_match( '/^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(,\s*[0-9.]+\s*)?\)$/', $value ) ) {
						$clean_block['settings'][ $skey ] = $value;
					} else {
						$clean_block['settings'][ $skey ] = sanitize_text_field( $value );
					}
				}
			}
			$clean[] = $clean_block;
		}
		return $clean;
	}

	/* ================================================================
	 * Rendering
	 * ================================================================ */

	/**
	 * Icon SVG paths keyed by type.
	 *
	 * @return array
	 */
	private static function icon_paths() {
		return array(
			'lock'        => '<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/><circle cx="12" cy="16" r="1.5"/>',
			'shield'      => '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',
			'key'         => '<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 0-7.778 7.778 5.5 5.5 0 0 0 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/>',
			'eye'         => '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>',
			'lockopen'    => '<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/>',
			'fingerprint' => '<path d="M2 12C2 6.5 6.5 2 12 2a10 10 0 0 1 8 4"/><path d="M5 19.5C5.5 18 6 15 6 12c0-3.3 2.7-6 6-6 1 0 2 .3 2.8.7"/><path d="M12 12c0 4-1 8-3 10"/><path d="M12 12c0 5 1.5 8.5 3 10"/><path d="M18 12a6 6 0 0 0-6-6"/>',
			'userlock'    => '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><rect x="16" y="13" width="6" height="8" rx="1"/><path d="M18 13v-1.5a1.5 1.5 0 1 1 3 0V13"/>',
		);
	}

	/**
	 * Render a template's blocks as HTML (zone-aware).
	 *
	 * @param array  $template  Template data.
	 * @param array  $args      Form args (headline, description, placeholder, etc.).
	 * @param string $nonce     Nonce value.
	 * @param bool   $fullpage  True when used as a standalone full-page wall (fills viewport).
	 * @return string HTML output.
	 */
	public static function render( $template, $args, $nonce, $fullpage = false ) {
		$global = $template['global'] ?? array();
		$blocks = $template['blocks'] ?? array();

		$valid_animations = array( 'aurora', 'neon', 'bounce', 'glass', 'luxury', 'ocean', 'sunrise', 'orbs', 'pulse', 'cyber' );

		$before_blocks = array();
		$card_blocks   = array();
		$after_blocks  = array();

		foreach ( $blocks as $block ) {
			$zone = $block['zone'] ?? 'card';
			if ( 'before_card' === $zone ) {
				$before_blocks[] = $block;
			} elseif ( 'after_card' === $zone ) {
				$after_blocks[] = $block;
			} else {
				$card_blocks[] = $block;
			}
		}

		$max_w     = absint( $global['maxWidth'] ?? 420 );
		$animation = ( isset( $global['animation'] ) && in_array( $global['animation'], $valid_animations, true ) ) ? $global['animation'] : 'none';

		// Card style — glass/orbs get special inline treatment so backdrop-filter works reliably.
		if ( 'glass' === $animation || 'orbs' === $animation ) {
			$card_style = 'max-width:' . $max_w . 'px;';
			if ( 'orbs' === $animation ) {
				$card_style .= 'background:rgba(255,255,255,0.07);';
				$card_style .= 'backdrop-filter:blur(40px) saturate(200%);-webkit-backdrop-filter:blur(40px) saturate(200%);';
				$card_style .= 'border:1px solid rgba(255,255,255,0.12);';
				$card_style .= 'box-shadow:0 24px 80px rgba(0,0,0,0.6),inset 0 1px 0 rgba(255,255,255,0.14);';
			} else {
				$card_style .= 'background:rgba(255,255,255,0.13);';
				$card_style .= 'backdrop-filter:blur(28px) saturate(180%);-webkit-backdrop-filter:blur(28px) saturate(180%);';
				$card_style .= 'border:1px solid rgba(255,255,255,0.25);';
				$card_style .= 'box-shadow:0 8px 48px rgba(0,0,0,0.35),inset 0 1px 0 rgba(255,255,255,0.3);';
			}
			$card_style .= 'border-radius:' . absint( $global['cardRadius'] ?? 24 ) . 'px;';
			$card_style .= 'padding:' . absint( $global['cardPadding'] ?? 48 ) . 'px;';
		} else {
			$card_style = 'max-width:' . $max_w . 'px;';

			if ( ! empty( $global['cardGradient'] ) ) {
				$angle = absint( $global['cardGradAngle'] ?? 135 );
				$from  = esc_attr( $global['cardGradFrom'] ?? '#FFFFFF' );
				$to    = esc_attr( $global['cardGradTo'] ?? '#F0F2F8' );
				$card_style .= 'background:linear-gradient(' . $angle . 'deg,' . $from . ',' . $to . ');';
			} else {
				$card_style .= 'background:' . esc_attr( $global['cardBg'] ?? '#FFFFFF' ) . ';';
			}

			$card_style .= 'border-radius:' . absint( $global['cardRadius'] ?? 16 ) . 'px;';
			$card_style .= 'padding:' . absint( $global['cardPadding'] ?? 40 ) . 'px;';

			if ( ! empty( $global['cardShadow'] ) ) {
				$card_style .= 'box-shadow:0 8px 40px rgba(0,0,0,0.1);';
			}

			$min_h = absint( $global['cardMinHeight'] ?? 0 );
			if ( $min_h > 0 ) {
				$card_style .= 'min-height:' . $min_h . 'px;';
			}

			$bw = absint( $global['cardBorderWidth'] ?? 0 );
			if ( $bw > 0 ) {
				$card_style .= 'border:' . $bw . 'px solid ' . esc_attr( $global['cardBorderColor'] ?? '#E4E6EF' ) . ';';
			}

			$card_visible = isset( $global['cardVisible'] ) ? (bool) $global['cardVisible'] : true;
			if ( ! $card_visible ) {
				$card_style = 'max-width:' . $max_w . 'px;background:transparent;border:none;box-shadow:none;padding:' . absint( $global['cardPadding'] ?? 40 ) . 'px;';
			}
		}

		// Alignment.
		$raw_align  = $global['cardAlign'] ?? 'center';
		$card_align = in_array( $raw_align, array( 'left', 'center', 'right' ), true ) ? $raw_align : 'center';
		$card_margin = 'center' === $card_align ? '0 auto' : ( 'right' === $card_align ? '0 0 0 auto' : '0 auto 0 0' );

		// Page background style.
		$page_bg_style = '';
		if ( ! empty( $global['bgImage'] ) ) {
			$page_bg_style .= 'background:url(' . esc_url( $global['bgImage'] ) . ') center/cover no-repeat fixed;';
		} elseif ( ! empty( $global['bgGradient'] ) ) {
			$pg_angle = absint( $global['bgGradAngle'] ?? 135 );
			$pg_from  = esc_attr( $global['bgGradFrom'] ?? '#F0F2F8' );
			$pg_to    = esc_attr( $global['bgGradTo'] ?? '#FFFFFF' );
			$page_bg_style .= 'background:linear-gradient(' . $pg_angle . 'deg,' . $pg_from . ',' . $pg_to . ') fixed;';
		} elseif ( ! empty( $global['bgColor'] ) ) {
			$page_bg_style .= 'background:' . esc_attr( $global['bgColor'] ) . ';';
		}

		// Extract button accent color for animation CSS (used by pulse, cyber).
		$anim_accent = '';
		foreach ( $blocks as $_b ) {
			if ( 'submit_button' === ( $_b['type'] ?? '' ) ) {
				$anim_accent = $_b['settings']['bgcolor'] ?? '';
				break;
			}
		}

		$anim_class = 'none' !== $animation ? ' ppmstr-anim-' . $animation : '';
		$anim_css   = self::get_animation_css( $animation, $global, $anim_accent );

		// Full-page: form-wrap fills viewport so gradient spans 100vh without seam.
		$wrap_extra = $fullpage
			? 'min-height:100vh;display:flex;align-items:center;justify-content:center;'
			: 'padding:40px 16px;';

		$html  = '';
		if ( $anim_css ) {
			$html .= '<style id="ppmstr-anim-' . esc_attr( $animation ) . '">' . $anim_css . '</style>';
		}
		$html .= '<div class="ppmstr-form-wrap ppmstr-template-custom' . esc_attr( $anim_class ) . '" style="' . $wrap_extra . 'text-align:center;position:relative;' . $page_bg_style . '">';

		// Overlay.
		if ( ! empty( $global['bgOverlay'] ) ) {
			$ov_color   = esc_attr( $global['bgOverlayColor'] ?? '#000000' );
			$ov_opacity = absint( $global['bgOverlayOpacity'] ?? 50 ) / 100;
			$ov_r = hexdec( substr( $ov_color, 1, 2 ) );
			$ov_b = hexdec( substr( $ov_color, 5, 2 ) );
			$html .= '<div class="ppmstr-page-overlay" style="position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(' . $ov_r . ',' . $ov_g . ',' . $ov_b . ',' . $ov_opacity . ');pointer-events:none;z-index:0;"></div>';
		}

		// Animation-specific decorative elements.
		if ( 'orbs' === $animation ) {
			$oc1 = esc_attr( $global['bgGradFrom'] ?? '#6C22E0' );
			$oc2 = esc_attr( $global['bgGradTo']   ?? '#00C9E0' );
			$html .= '<div class="ppmstr-orb ppmstr-orb--1" style="background:' . $oc1 . ';" aria-hidden="true"></div>';
			$html .= '<div class="ppmstr-orb ppmstr-orb--2" style="background:' . $oc2 . ';" aria-hidden="true"></div>';
			$html .= '<div class="ppmstr-orb ppmstr-orb--3" style="background:' . $oc1 . ';opacity:.28;" aria-hidden="true"></div>';
		} elseif ( 'pulse' === $animation ) {
			$pc = esc_attr( $anim_accent ?: ( $global['bgGradFrom'] ?? '#00C875' ) );
			$html .= '<div class="ppmstr-pulse-glow" style="background:radial-gradient(circle at center,' . $pc . '18,transparent 65%);" aria-hidden="true"></div>';
			$html .= '<div class="ppmstr-radar-rings" aria-hidden="true">';
			$html .= '<div class="ppmstr-ring ppmstr-ring--1" style="border-color:' . $pc . ';"></div>';
			$html .= '<div class="ppmstr-ring ppmstr-ring--2" style="border-color:' . $pc . ';"></div>';
			$html .= '<div class="ppmstr-ring ppmstr-ring--3" style="border-color:' . $pc . ';"></div>';
			$html .= '<div class="ppmstr-ring ppmstr-ring--4" style="border-color:' . $pc . ';"></div>';
			$html .= '</div>';
		} elseif ( 'cyber' === $animation ) {
			$html .= '<div class="ppmstr-cyber-scan" aria-hidden="true"></div>';
		}

		$html .= '<form method="post" action="" class="ppmstr-password-form" style="position:relative;z-index:1;">';

		$html .= '<input type="hidden" name="ppmstr_nonce" value="' . esc_attr( $nonce ) . '" />';
		$html .= '<input type="hidden" name="ppmstr_type" value="' . esc_attr( $args['type'] ) . '" />';
		$html .= '<input type="hidden" name="ppmstr_post_id" value="' . absint( $args['post_id'] ) . '" />';

		if ( 'category' === $args['type'] && ! empty( $args['term_id'] ) ) {
			$html .= '<input type="hidden" name="ppmstr_term_id" value="' . absint( $args['term_id'] ) . '" />';
		}
		if ( 'child' === $args['type'] && ! empty( $args['parent_id'] ) ) {
			$html .= '<input type="hidden" name="ppmstr_parent_id" value="' . absint( $args['parent_id'] ) . '" />';
		}
		if ( 'post_type' === $args['type'] && ! empty( $args['post_type'] ) ) {
			$html .= '<input type="hidden" name="ppmstr_post_type" value="' . esc_attr( $args['post_type'] ) . '" />';
		}

		foreach ( $before_blocks as $block ) {
			$html .= '<div style="max-width:' . $max_w . 'px;width:100%;margin:0 auto;">';
			$html .= self::render_block( $block, $args );
			$html .= '</div>';
		}

		$html .= '<div class="ppmstr-form-card" style="' . esc_attr( $card_style ) . 'text-align:center;width:100%;margin:' . esc_attr( $card_margin ) . ';">';
		foreach ( $card_blocks as $block ) {
			$html .= self::render_block( $block, $args );
		}
		$html .= '</div>';

		foreach ( $after_blocks as $block ) {
			$html .= '<div style="max-width:' . $max_w . 'px;width:100%;margin:0 auto;">';
			$html .= self::render_block( $block, $args );
			$html .= '</div>';
		}

		$html .= '</form></div>';

		return $html;
	}

	/**
	 * Render a single block.
	 *
	 * @param array $block Block data.
	 * @param array $args  Form args.
	 * @return string HTML.
	 */
	private static function render_block( $block, $args ) {
		$s    = $block['settings'] ?? array();
		$type = $block['type'];
		$html = '';

		$margin = 'margin-top:' . absint( $s['margintop'] ?? 0 ) . 'px;margin-bottom:' . absint( $s['marginbottom'] ?? 16 ) . 'px;';
		$align  = 'text-align:' . esc_attr( $s['alignment'] ?? 'center' ) . ';';

		$block_wrap_style = $margin;
		if ( ! empty( $s['bgcolor'] ) && 'transparent' !== $s['bgcolor'] ) {
			$block_wrap_style .= 'background:' . esc_attr( $s['bgcolor'] ) . ';';
		}
		if ( ! empty( $s['blockpadding'] ) ) {
			$block_wrap_style .= 'padding:' . absint( $s['blockpadding'] ) . 'px;';
		}
		if ( ! empty( $s['blockradius'] ) ) {
			$block_wrap_style .= 'border-radius:' . absint( $s['blockradius'] ) . 'px;';
		}

		switch ( $type ) {
			case 'logo':
				$url = esc_url( $block['content'] ?? '' );
				if ( $url ) {
					$html .= '<div style="' . $block_wrap_style . $align . '">';
					$html .= '<img src="' . $url . '" alt="" style="max-height:' . absint( $s['maxheight'] ?? 48 ) . 'px;" class="ppmstr-form-logo" />';
					$html .= '</div>';
				}
				break;

			case 'icon':
				$color     = esc_attr( $s['color'] ?? '#6C5CE7' );
				$size      = absint( $s['size'] ?? 48 );
				$icon_type = sanitize_key( $s['icontype'] ?? 'lock' );
				$icons     = self::icon_paths();
				$svg_inner = isset( $icons[ $icon_type ] ) ? $icons[ $icon_type ] : $icons['lock'];

				$html .= '<div class="ppmstr-form-icon" style="color:' . $color . ';' . $block_wrap_style . $align . '">';
				$html .= '<svg width="' . $size . '" height="' . $size . '" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">';
				$html .= $svg_inner;
				$html .= '</svg></div>';
				break;

			case 'heading':
				$text   = esc_html( $block['content'] ?? $args['headline'] );
				$fsize  = absint( $s['fontsize'] ?? 24 );
				$weight = absint( $s['fontweight'] ?? 700 );
				$color  = esc_attr( $s['color'] ?? '#1A1A2E' );
				$typo_style = '';
				if ( ! empty( $s['fontfamily'] ) && 'inherit' !== $s['fontfamily'] ) {
					$typo_style .= 'font-family:' . esc_attr( $s['fontfamily'] ) . ';';
				}
				if ( ! empty( $s['texttransform'] ) && 'none' !== $s['texttransform'] ) {
					$typo_style .= 'text-transform:' . esc_attr( $s['texttransform'] ) . ';';
				}
				if ( ! empty( $s['letterspacing'] ) ) {
					$typo_style .= 'letter-spacing:' . floatval( $s['letterspacing'] ) . 'px;';
				}
				if ( ! empty( $s['lineheight'] ) ) {
					$typo_style .= 'line-height:' . absint( $s['lineheight'] ) . '%;';
				}
				$html  .= '<h2 class="ppmstr-form-headline" style="font-size:' . $fsize . 'px;font-weight:' . $weight . ';color:' . $color . ';' . $typo_style . $block_wrap_style . $align . '">' . $text . '</h2>';
				break;

			case 'description':
				$text   = esc_html( $block['content'] ?? $args['description'] );
				$fsize  = absint( $s['fontsize'] ?? 14 );
				$weight = absint( $s['fontweight'] ?? 400 );
				$color  = esc_attr( $s['color'] ?? '#6C7293' );
				$typo_style = '';
				if ( ! empty( $s['fontfamily'] ) && 'inherit' !== $s['fontfamily'] ) {
					$typo_style .= 'font-family:' . esc_attr( $s['fontfamily'] ) . ';';
				}
				if ( ! empty( $s['texttransform'] ) && 'none' !== $s['texttransform'] ) {
					$typo_style .= 'text-transform:' . esc_attr( $s['texttransform'] ) . ';';
				}
				if ( ! empty( $s['letterspacing'] ) ) {
					$typo_style .= 'letter-spacing:' . floatval( $s['letterspacing'] ) . 'px;';
				}
				if ( ! empty( $s['lineheight'] ) ) {
					$typo_style .= 'line-height:' . absint( $s['lineheight'] ) . '%;';
				}
				$html  .= '<p class="ppmstr-form-desc" style="font-size:' . $fsize . 'px;font-weight:' . $weight . ';color:' . $color . ';' . $typo_style . $block_wrap_style . $align . '">' . $text . '</p>';
				break;

			case 'error_message':
				if ( $args['error'] ) {
					$err_size = absint( $s['fontsize'] ?? 13 );
					$err_clr  = esc_attr( $s['textcolor'] ?? '#C0392B' );
					$err_bg   = esc_attr( $s['errbg'] ?? 'rgba(225,112,85,0.1)' );
					$err_rad  = absint( $s['borderradius'] ?? 8 );
					$html    .= '<div class="ppmstr-form-error" role="alert" style="background:' . $err_bg . ';color:' . $err_clr . ';font-size:' . $err_size . 'px;border-radius:' . $err_rad . 'px;padding:10px 14px;' . $block_wrap_style . '">' . esc_html( $args['error_msg'] ) . '</div>';
				}
				break;

			case 'password_input':
				$fsize     = absint( $s['fontsize'] ?? 14 );
				$radius    = absint( $s['borderradius'] ?? 10 );
				$border    = esc_attr( $s['bordercolor'] ?? '#E4E6EF' );
				$bw        = absint( $s['borderwidth'] ?? 2 );
				$input_bg  = esc_attr( $s['inputbg'] ?? '#F9F9FC' );
				$txt_clr   = esc_attr( $s['textcolor'] ?? '#1A1A2E' );
				$ip_pad    = absint( $s['inputpadding'] ?? 12 );
				$ip_w      = ( isset( $s['fullwidth'] ) && ! $s['fullwidth'] ) ? 'width:auto;min-width:200px;' : 'width:100%;';
				$html    .= '<div class="ppmstr-form-input-wrap" style="' . $block_wrap_style . '">';
				$html    .= '<input type="password" name="ppmstr_password" class="ppmstr-form-input" ';
				$html    .= 'placeholder="' . esc_attr( $block['content'] ?? $args['placeholder'] ) . '" ';
				$html    .= 'style="' . $ip_w . 'border-radius:' . $radius . 'px;border:' . $bw . 'px solid ' . $border . ';background:' . $input_bg . ';color:' . $txt_clr . ';font-size:' . $fsize . 'px;padding:' . $ip_pad . 'px ' . ( $ip_pad + 2 ) . 'px;" required autocomplete="off" />';
				$html    .= '<button type="button" class="ppmstr-eye-toggle" aria-label="' . esc_attr__( 'Show password', 'privex-password-protect' ) . '">';
				$html    .= '<svg class="ppmstr-eye-open" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>';
				$html    .= '<svg class="ppmstr-eye-closed" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:none"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>';
				$html    .= '</button></div>';
				break;

			case 'submit_button':
				if ( ! empty( $args['captcha_html'] ) ) {
					$html .= $args['captcha_html'];
				}
				$bg      = esc_attr( $s['bgcolor'] ?? '#6C5CE7' );
				$hover   = esc_attr( $s['hovercolor'] ?? '#5A4BD1' );
				$color   = esc_attr( $s['textcolor'] ?? '#FFFFFF' );
				$radius  = absint( $s['borderradius'] ?? 10 );
				$fsize   = absint( $s['fontsize'] ?? 15 );
				$weight  = absint( $s['fontweight'] ?? 600 );
				$paddv   = absint( $s['paddingv'] ?? 13 );
				$paddh   = absint( $s['paddingh'] ?? 24 );
				$text    = esc_html( $block['content'] ?? $args['button_text'] );
				$btn_w   = ( isset( $s['fullwidth'] ) && ! $s['fullwidth'] ) ? 'width:auto;' : 'width:100%;';
				$btn_bdr = 'border:none;';
				if ( ! empty( $s['borderstyle'] ) && 'none' !== $s['borderstyle'] ) {
					$btn_bdr = 'border:' . absint( $s['borderwidth'] ?? 1 ) . 'px ' . esc_attr( $s['borderstyle'] ) . ' ' . esc_attr( $s['bordercolor'] ?? '#6C5CE7' ) . ';';
				}
				$btn_style = $btn_w . 'padding:' . $paddv . 'px ' . $paddh . 'px;' . $btn_bdr . 'background:' . $bg . ';color:' . $color . ';border-radius:' . $radius . 'px;font-size:' . $fsize . 'px;font-weight:' . $weight . ';';
				$html  .= '<button type="submit" class="ppmstr-form-submit" style="' . $btn_style . $block_wrap_style . '" data-hover-bg="' . $hover . '">' . $text . '</button>';
				break;

			case 'spacer':
				$height = absint( $s['height'] ?? 20 );
				$html  .= '<div style="height:' . $height . 'px;"></div>';
				break;

			case 'divider':
				$color = esc_attr( $s['color'] ?? '#E4E6EF' );
				$width = absint( $s['width'] ?? 1 );
				$html .= '<hr style="border:none;border-top:' . $width . 'px solid ' . $color . ';' . $block_wrap_style . '" />';
				break;

			case 'custom_text':
				$color  = esc_attr( $s['color'] ?? '#6C7293' );
				$fsize  = absint( $s['fontsize'] ?? 13 );
				$weight = absint( $s['fontweight'] ?? 400 );
				$typo_style = '';
				if ( ! empty( $s['fontfamily'] ) && 'inherit' !== $s['fontfamily'] ) {
					$typo_style .= 'font-family:' . esc_attr( $s['fontfamily'] ) . ';';
				}
				if ( ! empty( $s['texttransform'] ) && 'none' !== $s['texttransform'] ) {
					$typo_style .= 'text-transform:' . esc_attr( $s['texttransform'] ) . ';';
				}
				if ( ! empty( $s['letterspacing'] ) ) {
					$typo_style .= 'letter-spacing:' . floatval( $s['letterspacing'] ) . 'px;';
				}
				if ( ! empty( $s['lineheight'] ) ) {
					$typo_style .= 'line-height:' . absint( $s['lineheight'] ) . '%;';
				}
				$html  .= '<div style="font-size:' . $fsize . 'px;font-weight:' . $weight . ';color:' . $color . ';' . $typo_style . $block_wrap_style . $align . '">' . esc_html( $block['content'] ) . '</div>';
				break;
		}

		return $html;
	}
}
