<?php
/**
 * Rewards widget view for advocates.
 *
 * @package Awesomemotive\Rewardswp\Views\Panel\Advocate
 * @since 1.0.0
 */

namespace Awesomemotive\Rewardswp\Views\Panel\Advocate;

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

use Awesomemotive\Rewardswp\Abstracts\AbstractView;
use Awesomemotive\Rewardswp\Controllers\AdvocateController;
use Awesomemotive\Rewardswp\Controllers\ClaimController;
use Awesomemotive\Rewardswp\Controllers\PointsController;
use Awesomemotive\Rewardswp\Helpers\IconHelper;
use Awesomemotive\Rewardswp\Interfaces\ViewInterface;
use Awesomemotive\Rewardswp\Interfaces\IntegrationInterface;
use Awesomemotive\Rewardswp\Factories\ViewsFactory;
use Awesomemotive\Rewardswp\Factories\RewardTypesFactory;
use Awesomemotive\Rewardswp\Services\AntiSpamService;
use Awesomemotive\Rewardswp\Services\BirthdayService;
use Awesomemotive\Rewardswp\Services\PanelService;
use Awesomemotive\Rewardswp\Services\PendingRewardService;
use Awesomemotive\Rewardswp\Services\ReferralCodeService;
use Awesomemotive\Rewardswp\Services\RewardsService;
use Awesomemotive\Rewardswp\Services\PointsService;
use Awesomemotive\Rewardswp\Settings\Options\RewardsWidget\LauncherDisplayOption;
use Awesomemotive\Rewardswp\Settings\Options\RewardsWidget\LauncherPositionOption;
use Awesomemotive\Rewardswp\Settings\Options\RewardsWidget\LauncherTextOption;
use Awesomemotive\Rewardswp\Settings\Options\RewardsWidget\ProgramNameOption;
use Awesomemotive\Rewardswp\Settings\Options\RewardsWidget\WelcomeHeadingOption;
use Awesomemotive\Rewardswp\Settings\Options\Referrals\RewardsOption;
use Awesomemotive\Rewardswp\Settings\Options\Referrals\SharingOption;
use function Awesomemotive\Rewardswp\Helpers\get_current_member;
use function Awesomemotive\Rewardswp\Helpers\get_member_by;
use function Awesomemotive\Rewardswp\Helpers\get_option_value;
use function Awesomemotive\Rewardswp\Helpers\get_user_first_name;
use function Awesomemotive\Rewardswp\Helpers\get_user_last_name;
use function Awesomemotive\Rewardswp\Helpers\is_current_member_a_guest;
use function Awesomemotive\Rewardswp\Helpers\is_current_user_a_member;
use function Awesomemotive\Rewardswp\Helpers\is_current_user_logged_in_but_not_member;
use function Awesomemotive\Rewardswp\Helpers\is_guest_participation_enabled;
use function Awesomemotive\Rewardswp\Helpers\kses;
use function Awesomemotive\Rewardswp\Helpers\array_to_json;
use function Awesomemotive\Rewardswp\Helpers\replace_placeholders;
use function Awesomemotive\Rewardswp\Helpers\get_human_readable_date;
use Awesomemotive\Rewardswp\Settings\Options\Points\EarnActionsOption;
use Awesomemotive\Rewardswp\Settings\Options\Points\SpendActionsOption;
use function Awesomemotive\Rewardswp\Helpers\get_option_object;
use function Awesomemotive\Rewardswp\Helpers\get_spend_action_display_title;
use function Awesomemotive\Rewardswp\Helpers\get_points_currency_singular;
use function Awesomemotive\Rewardswp\Helpers\get_points_currency_plural;
use function Awesomemotive\Rewardswp\Helpers\get_referral_reward_display_title;
use function Awesomemotive\Rewardswp\Helpers\get_reward_config_from_model;
use function Awesomemotive\Rewardswp\Helpers\get_reward_display_title_from_values;
use function Awesomemotive\Rewardswp\Helpers\get_site_currency_symbol;
use function Awesomemotive\Rewardswp\Helpers\get_currency;
use function Awesomemotive\Rewardswp\Helpers\is_rewardswp_lite_plan;
use function Awesomemotive\Rewardswp\Helpers\can_access_rewardswp_feature;
use function Awesomemotive\Rewardswp\Helpers\frontend_ajax_url;

/**
 * Rewards Widget View - migrated from rewardswp-widget.
 *
 * This is a modern, app-like interface with integrated launcher and advanced animations.
 *
 * @since 1.1.0
 */
class RewardsWidgetView extends AbstractView implements ViewInterface {

	/**
	 * Icon helper instance.
	 *
	 * @since 1.1.0
	 * @var   IconHelper
	 */
	private IconHelper $icon_helper;

	/**
	 * Views factory instance.
	 *
	 * @since 1.1.0
	 * @var ViewsFactory
	 */
	private ViewsFactory $views_factory;

	/**
	 * Panel service instance.
	 *
	 * @since 1.1.0
	 * @var PanelService
	 */
	private PanelService $panel_service;

	/**
	 * Rewards service instance.
	 *
	 * @since 1.1.0
	 * @var RewardsService
	 */
	private RewardsService $rewards_service;

	/**
	 * Integration instance.
	 *
	 * @since 1.1.0
	 * @var IntegrationInterface
	 */
	private IntegrationInterface $integration;

	/**
	 * Reward types factory instance.
	 *
	 * @since 1.1.0
	 * @var RewardTypesFactory
	 */
	private RewardTypesFactory $reward_types_factory;

	/**
	 * Points service instance.
	 *
	 * @since 1.1.0
	 * @var PointsService
	 */
	private PointsService $points_service;

	/**
	 * Anti-spam service instance.
	 *
	 * @since 1.2.0
	 * @var AntiSpamService
	 */
	private AntiSpamService $anti_spam_service;

	/**
	 * Pending reward service instance.
	 *
	 * @since 1.2.0
	 * @var PendingRewardService
	 */
	private PendingRewardService $pending_reward_service;

	/**
	 * Referral code service instance.
	 *
	 * @since 1.4.1
	 * @var ReferralCodeService
	 */
	private ReferralCodeService $referral_code_service;

	/**
	 * CSS classes for card elements.
	 *
	 * @since 1.1.0
	 * @var string
	 */
	private string $card_classes;

	/**
	 * CSS classes for navigation items (menu-style buttons).
	 *
	 * @since 1.1.0
	 * @var string
	 */
	private string $nav_item_classes;



	/**
	 * CSS classes for secondary buttons.
	 *
	 * @since 1.1.0
	 * @var string
	 */
	private string $secondary_button_classes;

	/**
	 * CSS classes for icon elements.
	 *
	 * @since 1.1.0
	 * @var string
	 */
	private string $icon_classes;

	/**
	 * CSS classes for sub-panel heading elements.
	 *
	 * @since 1.1.0
	 * @var string
	 */
	private string $sub_panel_heading_classes;

	/**
	 * CSS classes for input field elements.
	 *
	 * @since 1.1.0
	 * @var string
	 */
	private string $input_classes;

	/**
	 * CSS classes for focus styling (keyboard accessibility).
	 *
	 * @since 1.1.0
	 * @var string
	 */
	private string $focus_classes;

	/**
	 * Whether the widget is being rendered in admin preview mode.
	 *
	 * @since 1.1.0
	 * @var bool
	 */
	private bool $is_admin_preview = false;

	/**
	 * Whether the widget is being rendered in demo mode.
	 *
	 * @since 1.2.0
	 * @var bool
	 */
	private bool $is_demo = false;

	/**
	 * Birthday service instance.
	 *
	 * @since 1.4.0
	 * @var BirthdayService
	 */
	private BirthdayService $birthday_service;

	/**
	 * Demo URL overrides for cart, checkout, and store pages.
	 *
	 * @since 1.2.0
	 * @var array
	 */
	private array $demo_urls = [];

	/**
	 * Constructor.
	 *
	 * @since 1.1.0
	 *
	 * @param IconHelper           $icon_helper            Icon helper service.
	 * @param ViewsFactory         $views_factory          Views factory service.
	 * @param PanelService         $panel_service          Panel service.
	 * @param RewardsService       $rewards_service        Rewards service.
	 * @param IntegrationInterface $integration           Integration service.
	 * @param RewardTypesFactory   $reward_types_factory  Reward types factory.
	 * @param PointsService        $points_service         Points service.
	 * @param AntiSpamService      $anti_spam_service     Anti-spam service.
	 * @param PendingRewardService $pending_reward_service Pending reward service.
	 * @param BirthdayService      $birthday_service      Birthday service.
	 */
	public function __construct(
		IconHelper $icon_helper,
		ViewsFactory $views_factory,
		PanelService $panel_service,
		RewardsService $rewards_service,
		IntegrationInterface $integration,
		RewardTypesFactory $reward_types_factory,
		PointsService $points_service,
		AntiSpamService $anti_spam_service,
		PendingRewardService $pending_reward_service,
		BirthdayService $birthday_service,
		ReferralCodeService $referral_code_service
	) {
		$this->icon_helper            = $icon_helper;
		$this->views_factory          = $views_factory;
		$this->panel_service          = $panel_service;
		$this->rewards_service        = $rewards_service;
		$this->integration            = $integration;
		$this->reward_types_factory   = $reward_types_factory;
		$this->points_service         = $points_service;
		$this->anti_spam_service      = $anti_spam_service;
		$this->pending_reward_service = $pending_reward_service;
		$this->birthday_service       = $birthday_service;
		$this->referral_code_service  = $referral_code_service;

		// Initialize CSS class properties
		$this->card_classes = 'bg-white rounded-lg px-4 py-6 relative text-rwp-widget-text shadow-card';

		$this->icon_classes = 'size-8 flex items-center justify-center flex-shrink-0 text-rwp-widget-icon-text';

		$this->sub_panel_heading_classes = 'text-lg font-semibold text-rwp-widget-heading mb-3';

		$this->input_classes = 'bg-white text-base w-full px-3 py-3 rounded-lg outline-1 -outline-offset-1 outline-gray-200 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-blue-500';

		// Reusable focus styling for keyboard accessibility
		$this->focus_classes = 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500';

		// Now update class properties that use focus styling
		$this->nav_item_classes         = 'w-full hover:bg-gray-50 text-left px-4 py-3 rounded-lg flex items-center justify-between transition-colors cursor-pointer ' . $this->focus_classes;
		$this->secondary_button_classes = 'text-sm inline-block w-full bg-gray-100 text-gray-700 font-semibold py-3 px-8 rounded-lg hover:bg-gray-200 transition-colors text-center cursor-pointer ' . $this->focus_classes;
	}

	/**
	 * Get button classes based on size and whether it has a spinner.
	 *
	 * @since 1.1.0
	 *
	 * @param string $size        Button size: 'lg' for large, 'sm' for small.
	 * @param bool   $has_spinner Whether the button has a spinner overlay. If true, uses grid layout; if false, uses flex layout.
	 * @return string Button classes.
	 */
	private function get_button_classes( string $size = 'lg', bool $has_spinner = false ): string {
		// Core classes shared by all buttons.
		$core_classes = 'bg-rwp-brand text-rwp-widget-button-text text-sm font-semibold rounded-lg cursor-pointer transition-colors hover:bg-rwp-brand-hover disabled:hover:bg-rwp-brand disabled:opacity-50 disabled:cursor-not-allowed ' . $this->focus_classes;

		// Size-specific classes
		$size_classes = [];
		if ( 'lg' === $size ) {
			$size_classes[] = 'w-full py-3 px-8 min-h-[48px] mx-auto';
		} elseif ( 'sm' === $size ) {
			$size_classes[] = 'py-2 px-4';
		}

		// Layout classes: grid for spinners (to stack text and spinner), flex for regular buttons
		$layout_classes = [];
		if ( $has_spinner ) {
			$layout_classes[] = 'grid grid-cols-1 grid-rows-1 [grid-template-areas:\'stack\'] place-items-center';
		} else {
			$layout_classes[] = 'flex items-center justify-center';
		}

		// Combine all classes
		return trim( implode( ' ', array_filter( [ $core_classes, implode( ' ', $size_classes ), implode( ' ', $layout_classes ) ] ) ) );
	}

	/**
	 * Get product price from the integration.
	 *
	 * @since 1.2.0
	 *
	 * @param int|string $product_id The product ID.
	 * @return float The product price.
	 */
	private function get_product_price( $product_id ): float {
		if ( empty( $product_id ) || ! method_exists( $this->integration, 'get_product' ) ) {
			return 0.0;
		}

		$product_data = $this->integration->get_product( $product_id );
		if ( is_array( $product_data ) && isset( $product_data['price'] ) ) {
			return (float) $product_data['price'];
		}

		return 0.0;
	}

	/**
	 * Get the currently pending reward ID from the session.
	 *
	 * @since 1.2.0
	 *
	 * @return int The pending reward ID, or 0 if none.
	 */
	private function get_pending_reward_id(): int {
		$pending = $this->pending_reward_service->get_pending_reward();
		if ( empty( $pending ) || ! isset( $pending['reward_id'] ) ) {
			return 0;
		}

		return (int) $pending['reward_id'];
	}

	/**
	 * Get the dismissed reward ID from session.
	 *
	 * When a user manually removes a coupon from the cart, the reward becomes
	 * "dismissed" and won't auto-apply until the user explicitly re-applies it.
	 *
	 * @since 1.2.0
	 *
	 * @return int The dismissed reward ID, or 0 if none.
	 */
	private function get_dismissed_reward_id(): int {
		$dismissed = $this->pending_reward_service->get_dismissed_reward();
		return $dismissed ? (int) $dismissed : 0;
	}

	/**
	 * Get friend claim data if we're in friend claim mode.
	 *
	 * @since 1.1.0
	 * @return array Friend claim data with enabled flag and related info.
	 */
	private function get_friend_claim_data(): array {

		$referral_code = $this->referral_code_service->get_active_referral_code();

		$has_referral_and_not_member = ! empty( $referral_code );

		// Use PanelService to check if friends can claim rewards
		$can_claim_rewards = $this->panel_service->can_friends_claim_rewards();

		// Only enable friend claim mode if both conditions are met
		$is_enabled = $has_referral_and_not_member && $can_claim_rewards;

		$friend_claim = [
			'enabled'         => $is_enabled,
			'referral_code'   => $referral_code,
			'advocate'        => null,
			'advocate_name'   => '',
			'advocate_email'  => '',
			'reward_info'     => null,
			'already_claimed' => false,
			'existing_reward' => null,
			'existing_coupon' => '',
		];

		if ( $is_enabled ) {
			// Get advocate info
			$advocate = get_member_by( 'referral_code', $referral_code );
			if ( $advocate ) {
				$friend_claim['advocate']       = $advocate;
				$friend_claim['advocate_name']  = $advocate->get_first_name();
				$friend_claim['advocate_email'] = $advocate->email;
			}

			// Get reward info
			$rewards_option              = get_option_value( \Awesomemotive\Rewardswp\Settings\Options\Referrals\RewardsOption::class );
			$friend_claim['reward_info'] = $rewards_option['friend'] ?? null;

			// Check if this IP already claimed a reward for this referral code
			$existing_reward = $this->check_existing_reward_for_ip( $referral_code );
			if ( $existing_reward && ! empty( $existing_reward->id ) ) {
				$friend_claim['already_claimed'] = true;
				$friend_claim['existing_reward'] = $existing_reward;
				$friend_claim['existing_coupon'] = $existing_reward->get_meta( 'coupon_code' ) ?? '';

				// Override reward_info with actual reward data so the UI shows
				// what the friend received, not the current admin config.
				// Falls back to current config if the reward model has no type.
				$reward_config = get_reward_config_from_model( $existing_reward );
				if ( ! empty( $reward_config['type'] ) ) {
					$friend_claim['reward_info'] = $reward_config;
				}
			}
		}

		return $friend_claim;
	}

	/**
	 * Check if current IP already has a reward for the given referral code.
	 *
	 * @since 1.1.0
	 * @param string $referral_code The referral code to check.
	 * @return \Awesomemotive\Rewardswp\Models\Reward|null The existing reward or null.
	 */
	private function check_existing_reward_for_ip( string $referral_code ): ?\Awesomemotive\Rewardswp\Models\Reward {
		// Get referrals from current IP
		$referrals_from_ip = \Awesomemotive\Rewardswp\Helpers\get_referrals_from_current_user_ip();

		if ( empty( $referrals_from_ip ) ) {
			return null;
		}

		// Get advocate for this referral code
		$advocate = get_member_by( 'referral_code', $referral_code );
		if ( ! $advocate ) {
			return null;
		}

		// Check if any referral from this IP is for the same advocate
		foreach ( $referrals_from_ip as $referral ) {
			if ( $referral->advocate_id === $advocate->id ) {
				// Get friend rewards for this referral (not advocate rewards)
				$rewards = \Awesomemotive\Rewardswp\Helpers\get_rewards(
					[
						'referral_id'    => $referral->id,
						'recipient_type' => 'friend',
						'status'         => 'issued', // Only issued rewards
					]
				);

				if ( ! empty( $rewards ) ) {
					return $rewards[0]; // Return the first issued friend reward
				}
			}
		}

		return null;
	}

	/**
	 * Get the list of enabled "Ways to Earn" actions.
	 *
	 * @since 1.1.0
	 * @param string|null $currency_singular Optional singular currency label override.
	 * @param string|null $currency_plural   Optional plural currency label override.
	 * @return array
	 */
	private function get_earn_actions( ?string $currency_singular = null, ?string $currency_plural = null ): array {
		$earn_actions_option = get_option_object( EarnActionsOption::class );
		$actions             = $earn_actions_option->get_enabled_actions();
		$formatted_actions   = [];
		$current_member      = get_current_member();

		// Get currency labels for formatting.
		$currency_singular = $currency_singular ?? get_points_currency_singular();
		$currency_plural   = $currency_plural ?? get_points_currency_plural();

		// Get referral mirror order from settings.
		$all_settings        = (array) get_option( 'rewardswp_settings', [] );
		$referral_mirror_order = absint( $all_settings['points']['referral_mirror_order'] ?? 0 );

		// Add referral action if referrals program is active and advocate reward type is "points".
		if ( $this->panel_service->is_referrals_program_active() ) {
			$rewards_option  = get_option_value( RewardsOption::class );
			$advocate_reward = $rewards_option['advocate'] ?? null;

			if ( $advocate_reward && isset( $advocate_reward['type'] ) && 'points' === $advocate_reward['type'] ) {
				$referral_points      = absint( $advocate_reward['reward_amount'] ?? 0 );
				$referral_description = sprintf(
					/* translators: %1$d points amount, %2$s currency label (Points/Point) */
					'%1$d %2$s',
					$referral_points,
					1 === $referral_points ? $currency_singular : $currency_plural
				);

				$formatted_actions[] = [
					'id'          => 'referral',
					'title'       => __( 'Refer a friend', 'rewardswp' ),
					'description' => $referral_description,
					'icon'        => 'earn-referral',
					'points'      => $referral_points,
					'order'       => $referral_mirror_order,
					'cta_label'   => __( 'Share', 'rewardswp' ),
					'cta_onclick' => "navigateToPanel('main', { resetScroll: true })",
				];
			}
		}

		foreach ( $actions as $action ) {
			// Skip referral action if referrals program is not active.
			if ( isset( $action['id'] ) && $action['id'] === 'referral' && ! $this->panel_service->is_referrals_program_active() ) {
				continue;
			}

			// Handle first order action for members.
			if ( isset( $action['id'] ) && 'first_order' === $action['id'] && $current_member && ! empty( $current_member->id ) ) {
				$first_order_awarded = $current_member->get_meta( 'first_order_awarded' );

				// If member received the bonus through the program, we'll mark it as completed below.
				// If member has orders but never received the bonus, hide it (pre-existing customer).
				if ( ! $first_order_awarded ) {
					$customer_email = $current_member->email ?? '';
					if ( ! empty( $customer_email ) ) {
						$existing_orders = $this->integration->get_customer_orders(
							$customer_email,
							[
								'status' => [ 'wc-processing', 'wc-completed' ],
								'limit'  => 1,
							]
						);

						// Hide from pre-existing customers who never earned the bonus.
						if ( ! empty( $existing_orders ) ) {
							continue;
						}
					}
				}
			}

			// Use custom_title for customer-facing display, fall back to title.
			$display_title = ! empty( $action['custom_title'] ) ? $action['custom_title'] : ( $action['title'] ?? '' );

			$formatted_action = [
				'id'          => $action['id'],
				'title'       => $display_title,
				'description' => $earn_actions_option->get_formatted_description( $action, $currency_singular, $currency_plural ),
				'icon'        => $action['icon'] ?? 'gift-free',
				'points'      => $action['points'] ?? 0, // Configured points value.
				'order'       => $action['order'] ?? 0,
			];

			// Add completion status for signup action.
			// Only mark completed if they have a WordPress account (not guest members).
			if ( isset( $action['id'] ) && 'signup' === $action['id'] && $current_member && ! empty( $current_member->user_id ) ) {
				// Check if user actually earned signup points.
				$signup_points_entries = $this->points_service->get_points_entries(
					[
						'member_id'   => $current_member->id,
						'source_type' => 'signup',
					],
					1,
					1
				);

				if ( ! empty( $signup_points_entries ) ) {
					// User earned signup points - show as completed with actual data.
					$formatted_action['is_completed']   = true;
					$formatted_action['completed_date'] = get_human_readable_date( $signup_points_entries[0]->created_at, false, false );
					$formatted_action['points_earned']  = $signup_points_entries[0]->points;
				} else {
					// No signup points - check if user registered before the action was enabled.
					$enabled_at = $action['enabled_at'] ?? null;
					if ( $enabled_at && $current_member->created_at < $enabled_at ) {
						// User registered before signup points were enabled - hide action entirely.
						continue;
					}

					// User registered after action was enabled - show as completed.
					$formatted_action['is_completed']   = true;
					$formatted_action['completed_date'] = get_human_readable_date( $current_member->created_at, false, false );
					$formatted_action['points_earned']  = $action['points'] ?? 0;
				}
			}

			// Add completion status for first order action.
			if ( isset( $action['id'] ) && 'first_order' === $action['id'] && $current_member && ! empty( $current_member->id ) ) {
				$first_order_awarded = $current_member->get_meta( 'first_order_awarded' );

				if ( $first_order_awarded ) {
					// Find the points entry to get the date.
					$points_entries = $this->points_service->get_points_entries(
						[
							'member_id'   => $current_member->id,
							'source_type' => 'first_order',
						],
						1,
						1
					);

					if ( ! empty( $points_entries ) ) {
						$formatted_action['is_completed']   = true;
						$formatted_action['completed_date'] = get_human_readable_date( $points_entries[0]->created_at, false, false );
						$formatted_action['points_earned']  = $action['points'] ?? 0;
					}
				}
			}

			// Add completion status for birthday action.
			if ( isset( $action['id'] ) && 'birthday' === $action['id'] && $current_member && ! empty( $current_member->id ) ) {
				$birthday = $current_member->get_meta( 'birthday' );

				if ( ! empty( $birthday ) ) {
					// Birthday is set - check if points were awarded this year.
					$current_year = (int) current_time( 'Y' );
					$points_entries = $this->points_service->get_points_entries(
						[
							'member_id'   => $current_member->id,
							'source_type' => 'birthday',
							'source_id'   => $current_year,
						],
						1,
						1
					);

					if ( ! empty( $points_entries ) ) {
						// Points awarded this year.
						$formatted_action['is_completed']   = true;
						$formatted_action['completed_date'] = get_human_readable_date( $points_entries[0]->created_at, false, false );
						$formatted_action['points_earned']  = $points_entries[0]->points;
						$formatted_action['birthday_date']  = $birthday; // Keep date available for display.
					} else {
						// Birthday set but not yet awarded this year - show the birthday date.
						$formatted_action['birthday_date'] = $birthday;
					}
				}
			}

			// Add CTA data for actions.
			$this->add_action_cta_data( $formatted_action, $action, $current_member );

			$formatted_actions[] = $formatted_action;
		}

		// Sort actions by order.
		usort(
			$formatted_actions,
			function ( $a, $b ) {
				return ( $a['order'] ?? 0 ) <=> ( $b['order'] ?? 0 );
			}
		);

		return $formatted_actions;
	}

	/**
	 * Add CTA data to an earn action.
	 *
	 * Determines the appropriate CTA label and URL based on action type and customer status.
	 *
	 * @since 1.1.0
	 *
	 * @param array              $formatted_action Reference to the formatted action array.
	 * @param array              $action           Original action data.
	 * @param Models\Member|null $current_member   Current member object or null.
	 * @return void
	 */
	private function add_action_cta_data( array &$formatted_action, array $action, $current_member ): void {
		$action_id = $action['id'] ?? '';

		// Make a purchase action - always render, Alpine controls visibility.
		if ( 'purchase' === $action_id ) {
			$formatted_action['cta_label'] = __( 'Shop', 'rewardswp' );
			$formatted_action['cta_url']   = $this->integration->get_store_url();
			return;
		}

		// First order bonus action - always render if not completed, Alpine controls visibility.
		if ( 'first_order' === $action_id ) {
			if ( empty( $formatted_action['is_completed'] ) ) {
				$formatted_action['cta_label'] = __( 'Shop', 'rewardswp' );
				$formatted_action['cta_url']   = $this->integration->get_store_url();
			}
			return;
		}

		// Product review action.
		if ( 'product_review' === $action_id ) {
			// Only show CTA for logged-in members with purchases.
			if ( is_null( $current_member ) || $current_member->is_guest() ) {
				// Guest members cannot leave reviews - no CTA.
				return;
			}

			$customer_email = $current_member->email ?? '';

			if ( empty( $customer_email ) ) {
				return;
			}

			// Get purchased product/download IDs.
			$integration_source = $this->integration->get_source();

			if ( 'woocommerce' === $integration_source ) {
				$product_ids = $this->integration->get_customer_purchased_product_ids( $customer_email );

				if ( empty( $product_ids ) ) {
					// No purchases yet - no CTA.
					return;
				}

				if ( count( $product_ids ) === 1 ) {
					// Single product - link directly to product review section.
					$formatted_action['cta_label'] = __( 'Review', 'rewardswp' );
					$formatted_action['cta_url']   = $this->integration->get_product_review_url( $product_ids[0] );
				} else {
					// Multiple products - link to My Account orders.
					$formatted_action['cta_label'] = __( 'Review', 'rewardswp' );
					$formatted_action['cta_url']   = $this->integration->get_orders_url();
				}
			} elseif ( 'easy_digital_downloads' === $integration_source ) {
				$download_ids = $this->integration->get_customer_purchased_download_ids( $customer_email );

				if ( empty( $download_ids ) ) {
					// No purchases yet - no CTA.
					return;
				}

				if ( count( $download_ids ) === 1 ) {
					// Single download - link directly to download review section.
					$formatted_action['cta_label'] = __( 'Review', 'rewardswp' );
					$formatted_action['cta_url']   = $this->integration->get_download_review_url( $download_ids[0] );
				} else {
					// Multiple downloads - link to purchase history.
					$formatted_action['cta_label'] = __( 'Review', 'rewardswp' );
					$formatted_action['cta_url']   = $this->integration->get_purchase_history_url();
				}
			}
		}

		// Signup action.
		if ( 'signup' === $action_id ) {
			// Only show CTA if not completed.
			if ( empty( $formatted_action['is_completed'] ) ) {
				// Only show "Create" CTA for guest members (have member account but no WordPress user).
				// Non-members should use the "Join now" CTA at the bottom of the panel.
				if ( ! is_null( $current_member ) && empty( $current_member->user_id ) ) {
					$formatted_action['cta_label']   = __( 'Create', 'rewardswp' );
					$formatted_action['cta_onclick'] = "window.dispatchEvent(new CustomEvent('show-register-form'))";
				}
			}
			return;
		}

		// Birthday action.
		if ( 'birthday' === $action_id ) {
			if ( ! is_null( $current_member ) && ! empty( $current_member->user_id ) ) {
				if ( empty( $formatted_action['birthday_date'] ) && empty( $formatted_action['is_completed'] ) ) {
					// Birthday not set — show "Add" CTA.
					$formatted_action['cta_label']   = __( 'Add', 'rewardswp' );
					$formatted_action['cta_onclick'] = "navigateToPanel('birthday-picker')";
				} elseif ( ! empty( $formatted_action['birthday_date'] ) && empty( $formatted_action['is_completed'] ) && ! $this->birthday_service->has_ever_received_birthday_points( $current_member ) ) {
					// Birthday set and no birthday points ever received — show "Update" CTA.
					$formatted_action['cta_label']   = __( 'Update', 'rewardswp' );
					$formatted_action['cta_onclick'] = "navigateToPanel('birthday-picker')";
				}
			}
			return;
		}
	}

	/**
	 * Get the list of enabled "Ways to Redeem" actions.
	 *
	 * @since 1.1.0
	 * @param string|null $currency_singular Optional singular currency label override.
	 * @param string|null $currency_plural   Optional plural currency label override.
	 * @return array
	 */
	private function get_redeem_actions( ?string $currency_singular = null, ?string $currency_plural = null ): array {
		$spend_actions_option = get_option_object( SpendActionsOption::class );
		$actions              = $spend_actions_option->get_enabled_actions();
		$formatted_actions    = [];

		// Get current member for redemption limit checks.
		$current_member = get_current_member();
		$member_id      = $current_member ? (int) $current_member->id : 0;

		foreach ( $actions as $action ) {
			// For free_product, perform additional checks.
			if ( 'free_product' === ( $action['type'] ?? '' ) ) {
				// Check if source matches current integration.
				$products_included = $action['products_included'] ?? [];
				if ( ! empty( $products_included[0]['source'] ) && $products_included[0]['source'] !== $this->integration->get_source() ) {
					// Source mismatch (e.g., product was configured with EDD but now WC is active), skip.
					continue;
				}

				// Check redemption limit - hide if member has reached their limit.
				if ( $member_id > 0 ) {
					$redemption_limit = $action['redemption_limit'] ?? '1';

					if ( 'unlimited' !== $redemption_limit ) {
						$action_id          = $action['id'] ?? '';
						$redemption_count   = $this->points_service->count_member_redemptions_for_action( $member_id, $action_id );
						$max_redemptions    = (int) $redemption_limit;

						if ( $redemption_count >= $max_redemptions ) {
							// Member has reached their limit, skip this action.
							continue;
						}
					}
				}
			}

			// Prefer custom title; fallback to reward formatted amount.
			$display_title = get_spend_action_display_title( $action );

			// Get product name for free_product actions.
			$product_name = '';
			if ( 'free_product' === ( $action['type'] ?? '' ) ) {
				$products_included = $action['products_included'] ?? [];
				if ( ! empty( $products_included[0]['name'] ) ) {
					$product_name = sanitize_text_field( $products_included[0]['name'] );
				}
			}

			$formatted_actions[] = [
				'id'                => $action['id'] ?? '',
				'type'              => $action['type'] ?? '',
				'cost'              => $spend_actions_option->get_formatted_description( $action, $currency_singular, $currency_plural ),
				'icon'              => $action['icon'] ?? 'gift-free',
				// Direct points required from action configuration.
				'required_points'   => $spend_actions_option->calculate_required_points( $action ),
				'action_config'     => $action,
				'reward_label'      => $spend_actions_option->get_reward_label( $action ),
				'points_cost_label' => $spend_actions_option->get_points_cost_label( $action, $currency_singular, $currency_plural ),
				'product_name'      => $product_name,
			];
		}

		return $formatted_actions;
	}

	/**
	 * Get widget configuration.
	 *
	 * Returns a nested configuration array with all widget settings.
	 * This is the single source of truth for widget configuration.
	 *
	 * @since 1.1.0
	 * @param array $args View arguments.
	 * @return array Configuration options.
	 */
	private function get_widget_config( array $args = [] ): array {

		/*
		|--------------------------------------------------------------------------
		| Dependencies & Member Data
		|--------------------------------------------------------------------------
		*/
		$current_member = get_current_member();
		$rewards_option = get_option_value( RewardsOption::class );

		// Member-derived defaults.
		$member_defaults = [
			'id'                  => $current_member->id ?? 0,
			'points_balance'      => $current_member->points_balance ?? 0,
			'name'                => '',
			'email'               => '',
			'referral_link'       => '',
			'completed_referrals' => $current_member->completed_referrals ?? 0,
			'pending_referrals'   => ( $current_member && ! empty( $current_member->id ) ) ? $current_member->get_pending_referral_count() : 0,
			'show_welcome_flow'   => false,
			'first_name'          => '',
			'signup_points'       => 0,
		];

		if ( $current_member && ! empty( $current_member->id ) ) {
			$member_defaults['name']              = $current_member->get_first_name();
			$member_defaults['email']             = $current_member->email;
			$member_defaults['referral_link']     = $current_member->get_referral_url();
			$member_defaults['show_welcome_flow'] = (bool) $current_member->get_meta( 'show_welcome_flow' );
			$member_defaults['first_name']        = $current_member->first_name ?: get_user_first_name( $current_member->user_id ?? 0 );

			$signup_points_entries = $this->points_service->get_points_entries(
				[
					'member_id'   => $current_member->id,
					'source_type' => 'signup',
				],
				1,
				1
			);
			if ( ! empty( $signup_points_entries ) ) {
				$member_defaults['signup_points'] = absint( $signup_points_entries[0]->points ?? 0 );
			}
		}

		// Check if we're on a confirmation page.
		$is_confirmation_page = false;
		if ( $current_member && ! empty( $current_member->id ) ) {
			if ( function_exists( 'is_order_received_page' ) && is_order_received_page() ) {
				$is_confirmation_page = true;
			} elseif ( function_exists( 'edd_is_success_page' ) && edd_is_success_page() ) {
				$is_confirmation_page = true;
			}
		}

		/*
		|--------------------------------------------------------------------------
		| Parse Args - Top Level
		|--------------------------------------------------------------------------
		*/
		$args = wp_parse_args( $args, [
			'is_demo'          => false,
			'is_admin_preview' => false,
			'mock_member_data' => null,
			'global_tier'      => 'lite',
			'currency'         => null,
			'activity'         => null,
		] );

		// Compute currency_symbol early so it's available for reward formatting.
		if ( $args['currency'] ) {
			$currency_data           = get_currency( $args['currency'] );
			$args['currency_symbol'] = $currency_data['symbol'] ?? get_site_currency_symbol();
		} else {
			$args['currency_symbol'] = get_site_currency_symbol();
		}

		/*
		|--------------------------------------------------------------------------
		| Parse Args - Member
		|--------------------------------------------------------------------------
		*/
		$args['member'] = wp_parse_args( $args['member'] ?? [], [
			'id'               => $member_defaults['id'],
			'name'             => $member_defaults['name'],
			'email'            => $member_defaults['email'],
			'show_welcome'     => $member_defaults['show_welcome_flow'],
			'signup_points'    => $member_defaults['signup_points'],
			'rewards'          => [],
			'rewards_count'    => 0,
			'points_balance'   => $member_defaults['points_balance'],
			'referral_link'    => $member_defaults['referral_link'],
			'friends_referred' => $member_defaults['completed_referrals'],
			'referral_total'   => $member_defaults['completed_referrals'] + $member_defaults['pending_referrals'],
		] );
		$args['member']['id']             = absint( $args['member']['id'] );
		$args['member']['points_balance'] = absint( $args['member']['points_balance'] );

		// Prepare existing birthday data for the picker.
		$args['member']['birthday_month'] = '';
		$args['member']['birthday_day']   = '';
		if ( $current_member && ! empty( $current_member->id ) ) {
			$birthday_raw = $current_member->get_meta( 'birthday' );
			if ( ! empty( $birthday_raw ) ) {
				$bday_parts = explode( '-', $birthday_raw );
				if ( count( $bday_parts ) === 2 ) {
					$args['member']['birthday_month'] = (string) (int) $bday_parts[0];
					$args['member']['birthday_day']   = (string) (int) $bday_parts[1];
				}
			}
		}

		// Check if the member can still edit their birthday (unlocked until first reward).
		$args['member']['birthday_can_edit'] = false;
		if ( $current_member && ! empty( $current_member->id ) && ! empty( $args['member']['birthday_month'] ) ) {
			$args['member']['birthday_can_edit'] = ! $this->birthday_service->has_ever_received_birthday_points( $current_member );
		}

		// Birthday points config for reward preview.
		$earn_actions                        = get_option_value( \Awesomemotive\Rewardswp\Settings\Options\Points\EarnActionsOption::class );
		$args['member']['birthday_points']   = ! empty( $earn_actions['birthday']['points'] ) ? absint( $earn_actions['birthday']['points'] ) : 0;
		$args['member']['birthday_min_days'] = \Awesomemotive\Rewardswp\Services\BirthdayService::MIN_DAYS_BEFORE_BIRTHDAY;

		// Birthday day data (persists all day, for birthday banner on main panel).
		if ( ! isset( $args['birthday_day'] ) ) {
			$args['birthday_day'] = ( $current_member && ! empty( $current_member->id ) )
				? $this->birthday_service->get_birthday_day_data( $current_member )
				: null;
		}

		// Load member rewards if not provided and member exists.
		if ( empty( $args['member']['rewards'] ) && $args['member']['id'] > 0 ) {
			$available_rewards = $this->rewards_service->get_advocate_rewards(
				$args['member']['id'],
				[ 'status' => 'issued' ]
			);

			$rewards_data = [];
			foreach ( $available_rewards as $reward ) {
				$reward_info = $this->prepare_reward_for_alpine( $reward );

				// Skip expired rewards.
				if ( ! empty( $reward_info['is_expired'] ) ) {
					continue;
				}

				$rewards_data[] = $reward_info;
			}

			$args['member']['rewards']       = $rewards_data;
			$args['member']['rewards_count'] = count( $rewards_data );
		}

		/*
		|--------------------------------------------------------------------------
		| Parse Args - Points
		|--------------------------------------------------------------------------
		*/
		$args['points'] = wp_parse_args( $args['points'] ?? [], [
			'enabled'        => $this->panel_service->is_points_program_active(),
			'redeem_actions' => null,
		] );

		$args['points']['currency'] = wp_parse_args( $args['points']['currency'] ?? [], [
			'singular' => get_points_currency_singular(),
			'plural'   => get_points_currency_plural(),
		] );

		/*
		|--------------------------------------------------------------------------
		| Parse Args - Referrals (with lite plan logic for rewards)
		|--------------------------------------------------------------------------
		*/
		$has_referral_reward_args = ! empty( $args['referrals']['advocate_reward'] ) || ! empty( $args['referrals']['friend_reward'] );
		$is_lite_plan             = ! $has_referral_reward_args && \Awesomemotive\Rewardswp\Helpers\get_rewardswp_plan_tier() === 'lite';

		$args['referrals'] = wp_parse_args( $args['referrals'] ?? [], [
			'enabled'         => $this->panel_service->is_referrals_program_active(),
			'advocate_reward' => $rewards_option['advocate'] ?? null,
			'friend_reward'   => ! $is_lite_plan ? ( $rewards_option['friend'] ?? null ) : null,
		] );

		// Hide referral rewards if Free Product is out of stock (skip check if args provided override).
		if ( ! $has_referral_reward_args ) {
			if ( $args['referrals']['advocate_reward'] && ! $this->panel_service->is_reward_available( $args['referrals']['advocate_reward'] ) ) {
				$args['referrals']['advocate_reward'] = null;
			}
			if ( $args['referrals']['friend_reward'] && ! $this->panel_service->is_reward_available( $args['referrals']['friend_reward'] ) ) {
				$args['referrals']['friend_reward'] = null;
			}
		}

		// Add formatted_amount to referral rewards for admin preview reactivity.
		if ( $args['referrals']['advocate_reward'] ) {
			$args['referrals']['advocate_reward']['formatted_amount'] = $this->format_reward_text( $args['referrals']['advocate_reward'], $args['currency_symbol'] );
		}
		if ( $args['referrals']['friend_reward'] ) {
			$args['referrals']['friend_reward']['formatted_amount'] = $this->format_reward_text( $args['referrals']['friend_reward'], $args['currency_symbol'] );
		}

		/*
		|--------------------------------------------------------------------------
		| Parse Args - Launcher
		|--------------------------------------------------------------------------
		*/
		$args['launcher'] = wp_parse_args( $args['launcher'] ?? [], [
			'display'  => get_option_value( LauncherDisplayOption::class ),
			'text'     => get_option_value( LauncherTextOption::class ),
			'position' => get_option_value( LauncherPositionOption::class ),
		] );

		/*
		|--------------------------------------------------------------------------
		| Parse Args - Widget (welcome/program settings)
		|--------------------------------------------------------------------------
		*/
		$args['widget'] = wp_parse_args( $args['widget'] ?? [], [
			'welcome_heading' => get_option_value( WelcomeHeadingOption::class ),
			'program_name'    => get_option_value( ProgramNameOption::class ),
		] );

		/*
		|--------------------------------------------------------------------------
		| Parse Args - Panel
		|--------------------------------------------------------------------------
		*/
		$args['panel'] = wp_parse_args( $args['panel'] ?? [], [
			'animation'         => 'scale',
			'initial_panel'     => '',
			'background_height' => 'auto',
			'brand_header_bg'   => '#1f2937',
		] );

		// Enforce feature access for panel animations.
		if ( ! can_access_rewardswp_feature( 'panel_animations' ) && 'scale' !== $args['panel']['animation'] ) {
			$args['panel']['animation'] = 'scale';
		}

		/*
		|--------------------------------------------------------------------------
		| Parse Args - Sharing
		|--------------------------------------------------------------------------
		*/
		$sharing_defaults     = get_option_value( SharingOption::class );
		$args['sharing']      = wp_parse_args( $args['sharing'] ?? [], $sharing_defaults );
		$args['sharing']      = wp_parse_args( $args['sharing'], [
			'x'        => false,
			'facebook' => false,
			'email'    => false,
		] );

		/*
		|--------------------------------------------------------------------------
		| Parse Args - Friend Claim
		|--------------------------------------------------------------------------
		*/
		$friend_claim_data    = $args['friend_claim'] ?? $this->get_friend_claim_data();
		$args['friend_claim'] = wp_parse_args( $friend_claim_data, [
			'enabled'         => false,
			'referral_code'   => '',
			'advocate'        => null,
			'advocate_name'   => '',
			'advocate_email'  => '',
			'reward_info'     => null,
			'already_claimed' => false,
			'existing_reward' => null,
			'existing_coupon' => '',
		] );

		/*
		|--------------------------------------------------------------------------
		| Format for Output
		|--------------------------------------------------------------------------
		*/
		$args['points']['formatted']       = number_format( $args['member']['points_balance'] );
		$args['points']['expiring']        = $this->get_expiring_points_data( $args );
		$args['points']['currency']['label'] = 1 === $args['member']['points_balance']
			? $args['points']['currency']['singular']
			: $args['points']['currency']['plural'];

		if ( null === $args['points']['redeem_actions'] ) {
			$args['points']['redeem_actions'] = $this->get_redeem_actions(
				$args['points']['currency']['singular'],
				$args['points']['currency']['plural']
			);
		}

		// Pre-render icon HTML for redeem actions.
		if ( ! empty( $args['points']['redeem_actions'] ) ) {
			$args['points']['redeem_actions'] = array_map( function ( $action ) {
				if ( ! empty( $action['icon'] ) && empty( $action['icon_html'] ) ) {
					ob_start();
					$this->icon_helper->render_icon( $action['icon'], 'size-6' );
					$action['icon_html'] = ob_get_clean();
				}
				return $action;
			}, $args['points']['redeem_actions'] );
		}

		$args['member']['is_guest']      = is_current_member_a_guest();
		$args['member']['is_guest_mode'] = ! is_user_logged_in()
			|| is_current_member_a_guest()
			|| is_current_user_logged_in_but_not_member()
			|| 0 === $args['member']['id'];
		$args['member']['is_logged_in']  = is_user_logged_in() && 0 !== $args['member']['id'];

		// Process member rewards to add icon_html and points_label for JS rendering.
		if ( ! empty( $args['member']['rewards'] ) ) {
			$args['member']['rewards'] = array_map( function ( $reward ) use ( $args ) {
				// Generate icon_html if only icon name provided.
				if ( ! empty( $reward['icon'] ) && empty( $reward['icon_html'] ) ) {
					ob_start();
					$this->icon_helper->render_icon( $reward['icon'], 'size-8' );
					$reward['icon_html'] = ob_get_clean();
				}

				// Add points_label if not set.
				if ( ! isset( $reward['points_label'] ) ) {
					$points_cost           = absint( $reward['points_cost'] ?? 0 );
					$reward['points_label'] = 1 === $points_cost
						? $args['points']['currency']['singular']
						: $args['points']['currency']['plural'];
				}

				return $reward;
			}, $args['member']['rewards'] );
		}

		$args['is_confirmation_page'] = $is_confirmation_page;

		return $args;
	}

	/**
	 * Prepare reward data for Alpine.js consumption.
	 *
	 * Adapted from AdvocateController::prepare_reward_for_alpine().
	 *
	 * @since 1.1.0
	 * @param object $reward The reward object.
	 * @return array Formatted reward data.
	 */
	private function prepare_reward_for_alpine( $reward ): array {
		// Get reward info from RewardType classes.
		$reward_type_instance = $this->reward_types_factory->get_reward_type( $reward->reward_type );

		if ( ! $reward_type_instance ) {
			// title/description are rendered via Alpine x-text (reward.title/.description),
			// which escapes on output, so use plain __() to avoid leaking HTML entities.
			$reward_info = [
				'title'       => __( 'Reward', 'rewardswp' ),
				'description' => __( 'Reward available', 'rewardswp' ),
				'icon'        => 'reward-free-product',
			];
		} else {
			// Set the reward object in the reward type instance.
			$reward_type_instance->set_reward( $reward );

			$reward_info = [
				'title'       => $reward_type_instance->get_panel_title(),
				'description' => method_exists( $reward_type_instance, 'get_panel_instructions' )
					? $reward_type_instance->get_panel_instructions()
					: $reward_type_instance->get_panel_title(),
				'icon'        => $reward_type_instance->get_icon_name(),
			];
		}

		// Calculate if expired.
		$is_expired = false;
		if ( ! empty( $reward->expires_at ) ) {
			$days_until_expiry = \Awesomemotive\Rewardswp\Helpers\get_date_diff_in_days( $reward->expires_at );
			$is_expired        = $days_until_expiry <= 0;
		}

		// Build coupon terms array.
		$coupon_terms = [];

		// 1. Minimum spend or "Applies to entire order".
		$minimum_order_amount = 0;
		if ( 'free_product' === $reward->reward_type ) {
			$minimum_order_amount = floatval( $reward->get_meta( 'minimum_order_amount' ) ?? 0 );

			if ( 0 === $minimum_order_amount ) {
				$rewards_settings = get_option_value( RewardsOption::class );
				$context          = $reward->recipient_type ?? 'advocate';
				if ( ! empty( $rewards_settings[ $context ]['minimum_order_amount'] ) ) {
					$minimum_order_amount = floatval( $rewards_settings[ $context ]['minimum_order_amount'] );
				}
			}
		}

		// Note: these coupon-term strings are rendered via Alpine x-text, which escapes on
		// output, so they must NOT be pre-escaped here (esc_html__ would leak &#039; etc.).
		if ( $minimum_order_amount > 0 ) {
			$coupon_terms[] = sprintf(
				/* translators: %s: minimum order amount with currency symbol */
				__( '%s min spend', 'rewardswp' ),
				\Awesomemotive\Rewardswp\Helpers\format_currency( $minimum_order_amount )
			);
		} elseif ( 'free_product' !== $reward->reward_type ) {
			$coupon_terms[] = __( 'Applies to entire order', 'rewardswp' );
		}

		// 2. Expiry text.
		$expiry_text = __( 'No expiry', 'rewardswp' );
		if ( ! empty( $reward->expires_at ) ) {
			$expires_timestamp = strtotime( $reward->expires_at );
			$expiry_text       = sprintf(
				/* translators: %s: expiration date */
				__( 'Expires %s', 'rewardswp' ),
				date_i18n( 'M j, Y', $expires_timestamp )
			);
		}
		$coupon_terms[] = $expiry_text;

		$coupon_terms_text = implode( ' • ', $coupon_terms );

		// Generate icon HTML.
		ob_start();
		$this->icon_helper->render_icon( $reward_info['icon'], 'size-8' );
		$icon_html = ob_get_clean();

		$coupon_code       = $reward->get_meta( 'coupon_code' ) ?? '';
		$is_coupon_applied = false;

		if ( ! empty( $coupon_code ) && method_exists( $this->integration, 'is_coupon_applied' ) ) {
			$is_coupon_applied = $this->integration->is_coupon_applied( $coupon_code );
		}

		// Get points cost from points entry.
		$points_cost    = 0;
		$points_entries = \Awesomemotive\Rewardswp\Helpers\get_points_entries(
			[
				'source_type' => 'reward_redemption',
				'source_id'   => $reward->id,
				'type'        => 'spent',
			],
			1,
			1
		);

		if ( ! empty( $points_entries ) ) {
			$points_cost = absint( $points_entries[0]->points );
		}

		$points_label = get_points_currency_plural();

		// Check free_product stock status.
		$is_out_of_stock    = false;
		$product_name       = '';
		$reward_description = '';
		$free_product_id    = null;
		$free_product_price = 0;

		if ( 'free_product' === $reward->reward_type ) {
			$free_product_id    = $reward->get_meta( 'free_product_id' );
			$product_name       = $reward->get_meta( 'free_product_name' ) ?? '';
			$reward_description = $reward->get_meta( 'reward_description' ) ?? '';

			if ( $free_product_id && method_exists( $this->integration, 'is_product_in_stock' ) ) {
				$is_out_of_stock = ! $this->integration->is_product_in_stock( $free_product_id );
			}

			if ( $free_product_id && method_exists( $this->integration, 'get_product' ) ) {
				$product_data = $this->integration->get_product( $free_product_id );
				if ( is_array( $product_data ) && isset( $product_data['price'] ) ) {
					$free_product_price = floatval( $product_data['price'] );
				}
			}
		}

		// Override title for free_product rewards.
		$display_title = $reward_info['title'];
		if ( 'free_product' === $reward->reward_type ) {
			$display_name = ! empty( $reward_description ) ? $reward_description : $product_name;

			if ( ! empty( $display_name ) ) {
				$product_value = $reward->get_meta( 'product_value' );
				if ( ! empty( $product_value ) && floatval( $product_value ) > 0 ) {
					$display_title = sprintf(
						/* translators: %1$s: formatted currency amount, %2$s: product name */
						__( '%1$s off %2$s', 'rewardswp' ),
						\Awesomemotive\Rewardswp\Helpers\format_currency( floatval( $product_value ) ),
						$display_name
					);
				} else {
					$display_title = sprintf(
						/* translators: %s: product name */
						__( 'Free %s', 'rewardswp' ),
						$display_name
					);
				}
			}
		}

		return [
			'id'                   => $reward->id,
			'title'                => $display_title,
			'description'          => $reward_info['description'],
			'coupon_code'          => $coupon_code,
			'is_applied'           => $is_coupon_applied,
			'coupon_terms'         => $coupon_terms_text,
			'expiry_text'          => $expiry_text,
			'is_expired'           => $is_expired,
			'is_out_of_stock'      => $is_out_of_stock,
			'icon_html'            => $icon_html,
			'points_cost'          => $points_cost,
			'points_label'         => $points_label,
			'product_name'         => $product_name,
			'reward_type'          => $reward->reward_type,
			'free_product_id'      => $free_product_id,
			'minimum_order_amount' => $minimum_order_amount,
			'free_product_price'   => $free_product_price,
		];
	}

	/**
	 * Get expiring points information for the current member.
	 *
	 * @since 1.1.0
	 * @param array $args View arguments containing member information.
	 * @return array|null Expiring points data or null if no expiring points found.
	 */
	private function get_expiring_points_data( array $args = [] ): ?array {
		// Check if we have member ID
		$member_id = absint( $args['member']['id'] ?? 0 );

		if ( $member_id <= 0 ) {
			return null;
		}

		// Get expiring points for the next 30 days
		$expiring_balance = $this->points_service->get_member_points_expiring_balance( $member_id, 30 );

		if ( $expiring_balance <= 0 ) {
			return null;
		}

		return [
			'expiring_points' => $expiring_balance,
			'days'            => 30,
		];
	}

	/**
	 * Format reward data for display using RewardTypesFactory.
	 *
	 * @since 1.1.0
	 * @param array|null $reward Reward configuration array with 'type' and 'reward_amount'.
	 * @return string Formatted reward text.
	 */
	private function format_reward_text( ?array $reward, string $currency_symbol = '' ): string {
		if ( empty( $reward ) || empty( $reward['type'] ) ) {
			return '';
		}

		$reward_type   = $reward['type'] ?? '';
		$reward_amount = floatval( $reward['reward_amount'] ?? 0 );

		// Special case: free_product needs custom handling for config-based rewards
		// since we need to extract product info from the config array.
		if ( 'free_product' === $reward_type ) {
			return $this->get_free_product_formatted_amount( $reward, $currency_symbol );
		}

		// Delegate to RewardType classes for consistent formatting.
		$reward_type_instance = $this->reward_types_factory->get_reward_type( $reward_type );

		if ( $reward_type_instance && method_exists( $reward_type_instance, 'get_reward_formatted_amount' ) ) {
			// Set currency override before formatting.
			if ( ! empty( $currency_symbol ) ) {
				$reward_type_instance->set_currency_symbol( $currency_symbol );
			}
			return $reward_type_instance->get_reward_formatted_amount( $reward_amount, 'default' );
		}

		// Fallback if reward type not found.
		return ! empty( $reward['name'] ) ? esc_html( $reward['name'] ) : esc_html__( 'Reward', 'rewardswp' );
	}

	/**
	 * Get reward information using RewardType classes.
	 *
	 * @since 1.1.0
	 * @param \Awesomemotive\Rewardswp\Models\Reward $reward The reward object.
	 * @return array Reward information with title, description, and icon.
	 */
	private function get_reward_info( $reward ): array {
		$reward_type_instance = $this->reward_types_factory->get_reward_type( $reward->reward_type );

		if ( ! $reward_type_instance ) {
			return [
				'title'       => esc_html__( 'Reward', 'rewardswp' ),
				'description' => esc_html__( 'Reward available', 'rewardswp' ),
				'icon'        => 'reward-free-product',
			];
		}

		// Set the reward object in the reward type instance
		$reward_type_instance->set_reward( $reward );

		return [
			'title'       => $reward_type_instance->get_panel_title(),
			'description' => method_exists( $reward_type_instance, 'get_panel_instructions' )
				? $reward_type_instance->get_panel_instructions()
				: $reward_type_instance->get_panel_title(),
			'icon'        => $reward_type_instance->get_icon_name(),
		];
	}

	/**
	 * Get reward information from a configuration array.
	 *
	 * Similar to get_reward_info() but works with config arrays instead of Reward models.
	 * Used for displaying referral program preview/setup information.
	 *
	 * @since 1.1.0
	 * @param array|null $reward_config Reward configuration array with 'type' and 'reward_amount'.
	 * @return array Reward information with icon, title, and formatted_amount.
	 */
	private function get_reward_info_from_config( ?array $reward_config, string $currency_symbol = '' ): array {
		if ( empty( $reward_config ) || empty( $reward_config['type'] ) ) {
			return [
				'icon'             => 'gift',
				'title'            => esc_html__( 'Reward', 'rewardswp' ),
				'formatted_amount' => '',
			];
		}

		$reward_type_instance = $this->reward_types_factory->get_reward_type( $reward_config['type'] );

		if ( ! $reward_type_instance ) {
			return [
				'icon'             => 'gift',
				'title'            => esc_html__( 'Reward', 'rewardswp' ),
				'formatted_amount' => '',
			];
		}

		// Use format_reward_text to get formatted_amount with currency support.
		$formatted_amount = $this->format_reward_text( $reward_config, $currency_symbol );

		return [
			'icon'             => $reward_type_instance->get_icon_name(),
			'title'            => $reward_type_instance->get_admin_title(),
			'formatted_amount' => $formatted_amount,
		];
	}

	/**
	 * Get formatted amount for free_product rewards.
	 *
	 * Returns the product name with optional value limit formatting.
	 * Priority: reward_description > product name > "Free product"
	 *
	 * @since 1.2.0
	 *
	 * @param array $reward_config Reward configuration array.
	 *
	 * @return string Formatted amount string.
	 */
	private function get_free_product_formatted_amount( array $reward_config, string $currency_symbol = '' ): string {
		$product_value   = floatval( $reward_config['reward_amount'] ?? 0 );
		$has_value_limit = $product_value > 0;
		$currency_symbol = $currency_symbol ?: get_site_currency_symbol();
		$formatted_value = \Awesomemotive\Rewardswp\Helpers\apply_currency_symbol(
			$currency_symbol,
			\Awesomemotive\Rewardswp\Helpers\format_currency( $product_value, '', true, false )
		);

		// Priority: reward_description > product name.
		$product_name = '';
		if ( ! empty( $reward_config['reward_description'] ) ) {
			$product_name = sanitize_text_field( $reward_config['reward_description'] );
		} elseif ( ! empty( $reward_config['products_included'][0]['name'] ) ) {
			$product_name = sanitize_text_field( $reward_config['products_included'][0]['name'] );
		}

		if ( ! empty( $product_name ) ) {
			if ( $has_value_limit ) {
				return sprintf(
					/* translators: %1$s: currency amount, %2$s: product name */
					esc_html__( '%1$s off %2$s', 'rewardswp' ),
					esc_html( $formatted_value ),
					esc_html( $product_name )
				);
			}
			return sprintf(
				/* translators: %s: product name */
				esc_html__( 'Free %s', 'rewardswp' ),
				esc_html( $product_name )
			);
		}

		// Fallback.
		if ( $has_value_limit ) {
			return sprintf(
				/* translators: %s: currency amount */
				esc_html__( '%s off product', 'rewardswp' ),
				esc_html( $formatted_value )
			);
		}

		return esc_html__( 'Free product', 'rewardswp' );
	}

	/**
	 * Get icon map for reward types.
	 *
	 * Returns an array mapping reward type slugs to their SVG HTML.
	 * Used for reactive admin preview updates.
	 *
	 * @since 1.1.1
	 * @return array <string, string> Map of reward type to SVG HTML.
	 */
	private function get_reward_icon_map(): array {
		$icon_map     = [];
		$reward_types = [
			'points'              => 'reward-points',
			'percentage_discount' => 'reward-percentage-coupon',
			'fixed_discount'      => 'reward-amount-coupon',
			'free_shipping'       => 'reward-free-shipping',
			'free_product'        => 'reward-free-product',
			'gift'                => 'gift',
		];

		foreach ( $reward_types as $type => $icon_name ) {
			ob_start();
			$this->icon_helper->render_icon( $icon_name, 'size-8' );
			$icon_map[ $type ] = ob_get_clean();
		}

		return $icon_map;
	}

	/**
	 * Get sharing options configuration.
	 *
	 * @since 1.1.0
	 *
	 * @param array $args View arguments.
	 * @return array Sharing options.
	 */
	private function get_sharing_options( array $args = [] ): array {
		// Get saved sharing options
		$sharing_settings = get_option_value( SharingOption::class );

		// Allow override from args (for preview with unsaved changes)
		// Use manual merge instead of wp_parse_args to properly handle false values
		// wp_parse_args may not properly override with false values
		if ( isset( $args['sharing'] ) && is_array( $args['sharing'] ) ) {
			// Manually merge to ensure false values override saved values
			foreach ( $args['sharing'] as $key => $value ) {
				$sharing_settings[ $key ] = $value;
			}
		}

		/**
		 * Allow plugins to override the settings before rendering.
		 *
		 * @since 1.0.0
		 *
		 * @param array $sharing_options
		 */
		$sharing_settings = apply_filters( 'rewardswp_pre_render_sharing_options', $sharing_settings );

		$advocate     = get_current_member();
		$referral_url = $args['member']['referral_link'] ?? '';
		if ( empty( $referral_url ) && $advocate ) {
			$referral_url = $advocate->get_referral_url();
		}
		if ( empty( $referral_url ) ) {
			$referral_url = home_url( '/preview' );
		}

		$all_options = [];

		// Email
		$rewards                = get_option_value( RewardsOption::class );
		$friend_reward_settings = is_array( $rewards['friend'] ?? null ) ? $rewards['friend'] : [];

		// Get display title using shared helper (handles free_product with "Free {product}" format).
		$reward_display_title = get_referral_reward_display_title( $friend_reward_settings );
		if ( '' === $reward_display_title ) {
			$reward_display_title = esc_html__( 'reward', 'rewardswp' );
		}
		$all_options['email']    = [
			'id'    => 'email',
			'icon'  => 'envelope',
			'label' => __( 'Email', 'rewardswp' ),
			'url'   => sprintf(
				'mailto:?subject=%1$s&body=%2$s%%0A%%0A',
				rawurlencode( wp_specialchars_decode( sanitize_text_field( $sharing_settings['email_subject'] ), ENT_QUOTES ) ),
				rawurlencode(
					replace_placeholders(
						sanitize_textarea_field( $sharing_settings['email_message'] ),
						[
							'site_name'     => wp_specialchars_decode( esc_html( get_bloginfo( 'name' ) ), ENT_QUOTES ),
							'reward_name'   => $reward_display_title,
							'advocate_name' => is_null( $advocate ) ? get_user_first_name( get_current_user_id() ) : $advocate->get_first_name(),
							'referral_link' => $referral_url,
						]
					)
				)
			),
		];
		$all_options['twitter']  = [
			'id'    => 'twitter',
			'icon'  => 'twitter',
			'label' => 'X',
			'url'   => sprintf(
				'https://twitter.com/intent/tweet?url=%1$s&text=%2$s',
				rawurlencode( $referral_url ),
				rawurlencode(
					replace_placeholders(
						$sharing_settings['x_message'],
						[ 'site_name' => esc_html( get_bloginfo( 'name' ) ) ]
					)
				)
			),
		];
		$all_options['facebook'] = [
			'id'    => 'facebook',
			'icon'  => 'facebook',
			'label' => 'Facebook',
			'url'   => sprintf(
				'https://www.facebook.com/sharer/sharer.php?u=%s',
				rawurlencode( $referral_url )
			),
		];

		if ( $args['is_admin_preview'] ?? false ) {
			return array_values( $all_options );
		}

		$enabled_options = [];
		if ( ! empty( $sharing_settings['email'] ) ) {
			$enabled_options[] = $all_options['email'];
		}
		if ( ! empty( $sharing_settings['x'] ) ) {
			$enabled_options[] = $all_options['twitter'];
		}
		if ( ! empty( $sharing_settings['facebook'] ) ) {
			$enabled_options[] = $all_options['facebook'];
		}

		return $enabled_options;
	}

	/**
	 * Render the template.
	 *
	 * @since 1.1.0
	 *
	 * @param array $args View arguments.
	 * @return void
	 */
	public function render( array $args = [] ): void {
		// Check if guest participation is disabled and current user is a guest.
		if ( ! is_guest_participation_enabled() && is_current_member_a_guest() ) {
			return; // Don't render the widget for guests if guest participation is disabled.
		}

		$template_id = $args['template_id'] ?? 'rewards-panel';

		// Get all widget config from single source of truth.
		$config = $this->get_widget_config( $args );

		// Set admin preview and demo flags for use in other methods.
		$this->is_admin_preview = $config['is_admin_preview'];
		$this->is_demo          = $config['is_demo'];
		$this->demo_urls        = $args['demo_urls'] ?? [];

		// Get sharing options with URLs for rendering.
		$sharing_options = $this->get_sharing_options( $args );

		?>
		<template id="<?php echo esc_attr( $template_id ); ?>">
			<?php $this->render_widget_wrapper( $config, $sharing_options ); ?>
		</template>

		<?php
		/*
		 * Render the web component
		 */
		?>
	<rewards-panel
			<?php if ( $config['is_admin_preview'] ) : ?>
		class="block h-full relative"
		<?php endif; ?>
		data-template-id="<?php echo esc_attr( $template_id ); ?>"
		data-ajax-url="<?php echo esc_url( frontend_ajax_url() ); ?>"
		data-share-points-nonce="<?php echo esc_attr( wp_create_nonce( PointsController::AJAX_NONCE ) ); ?>"
		data-advocate-nonce="<?php echo esc_attr( wp_create_nonce( AdvocateController::AJAX_NONCE ) ); ?>"
		data-claim-nonce="<?php echo esc_attr( wp_create_nonce( ClaimController::AJAX_NONCE ) ); ?>"
			data-user-points="<?php echo absint( $config['member']['points_balance'] ); ?>"
	></rewards-panel>

		<?php
	}

	/**
	 * Get the view contents as a string.
	 *
	 * @since 1.1.0
	 * @param array $args View arguments.
	 * @return string The rendered view.
	 */
	public function get_contents( array $args = [] ): string {
		ob_start();
		$this->render( $args );
		return ob_get_clean() ?? '';
	}

	/**
	 * Render the main widget wrapper with Alpine.js component.
	 *
	 * @since 1.1.0
	 * @param array $config Widget configuration (nested structure from get_widget_config).
	 * @param array $sharing_options Sharing options with URLs for rendering.
	 * @return void
	 */
	private function render_widget_wrapper( array $config, array $sharing_options = [] ): void {
		$currency_position = \Awesomemotive\Rewardswp\Helpers\get_currency_position();
		?>
		<div
			x-cloak
			x-data="rewardsPanelWidget({
				isFriendClaimMode: <?php echo $config['friend_claim']['enabled'] ? 'true' : 'false'; ?>,
				initialPanel: '<?php echo esc_js( $config['panel']['initial_panel'] ); ?>',
				globalTier: '<?php echo esc_js( $config['global_tier'] ); ?>',
				launcherDisplayMode: '<?php echo esc_js( $config['launcher']['display'] ); ?>',
				launcherText: '<?php echo esc_js( $config['launcher']['text'] ); ?>',
				originalLauncherText: '<?php echo esc_js( $config['launcher']['text'] ); ?>',
		friendClaimLauncherText: '<?php echo esc_js( __( 'Claim gift', 'rewardswp' ) ); ?>',
		friendClaimedLauncherText: '<?php echo esc_js( __( 'Your gift', 'rewardswp' ) ); ?>',
				pointsEnabled: <?php echo $config['points']['enabled'] ? 'true' : 'false'; ?>,
				referralsEnabled: <?php echo $config['referrals']['enabled'] ? 'true' : 'false'; ?>,
				friendsReferred: <?php echo absint( $config['member']['friends_referred'] ); ?>,
				referralTotal: <?php echo absint( $config['member']['referral_total'] ); ?>,
				isAdminPreview: <?php echo $config['is_admin_preview'] ? 'true' : 'false'; ?>,
				isDemo: <?php echo $config['is_demo'] ? 'true' : 'false'; ?>,
				currencySingular: '<?php echo esc_js( $config['points']['currency']['singular'] ); ?>',
				currencyPlural: '<?php echo esc_js( $config['points']['currency']['plural'] ); ?>',
				currencySymbol: '<?php echo esc_js( $config['currency_symbol'] ); ?>',
				currencyPosition: '<?php echo esc_js( $currency_position['position'] ); ?>',
				currencySpace: <?php echo $currency_position['space'] ? 'true' : 'false'; ?>,
		yourLabel: '<?php echo esc_js( __( 'Your', 'rewardswp' ) ); ?>',
				welcomeGreeting: '<?php echo esc_js( $config['widget']['welcome_heading'] ); ?>',
				programName: '<?php echo esc_js( $config['widget']['program_name'] ); ?>',
				memberId: <?php echo absint( $config['member']['id'] ); ?>,
				userPoints: <?php echo absint( $config['member']['points_balance'] ); ?>,
				userName: '<?php echo esc_js( $config['member']['name'] ); ?>',
				userEmail: '<?php echo esc_js( $config['member']['email'] ); ?>',
				referralLink: '<?php echo esc_url( $config['member']['referral_link'] ); ?>',
				showWelcomeFlow: <?php echo $config['member']['show_welcome'] ? 'true' : 'false'; ?>,
		isConfirmationPage: <?php echo $config['is_confirmation_page'] ? 'true' : 'false'; ?>,
				isGuestMember: <?php echo $config['member']['is_guest'] ? 'true' : 'false'; ?>,
				availableRewards: <?php echo array_to_json( $config['member']['rewards'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
				availableRewardsCount: <?php echo absint( $config['member']['rewards_count'] ); ?>,
		sharePointsNonce: '<?php echo esc_attr( wp_create_nonce( PointsController::AJAX_NONCE ) ); ?>',
		advocateNonce: '<?php echo esc_attr( wp_create_nonce( AdvocateController::AJAX_NONCE ) ); ?>',
		claimNonce: '<?php echo esc_attr( wp_create_nonce( ClaimController::AJAX_NONCE ) ); ?>',
		ajaxUrl: '<?php echo esc_url( frontend_ajax_url() ); ?>',
		welcomeBannerData: {
					memberName: '<?php echo esc_js( $config['member']['name'] ); ?>',
					signupPoints: <?php echo absint( $config['member']['signup_points'] ); ?>,
		},
		sharing: {
					x: <?php echo $config['sharing']['x'] ? 'true' : 'false'; ?>,
					facebook: <?php echo $config['sharing']['facebook'] ? 'true' : 'false'; ?>,
					email: <?php echo $config['sharing']['email'] ? 'true' : 'false'; ?>
				},
				friendClaimData: {
					referralCode: '<?php echo esc_js( $config['friend_claim']['referral_code'] ); ?>',
					advocateName: '<?php echo esc_js( $config['friend_claim']['advocate_name'] ); ?>',
					rewardInfo: <?php echo array_to_json( $config['friend_claim']['reward_info'] ?? [] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Array is already escaped. ?>,
				},
				mockMemberData: <?php echo isset( $config['mock_member_data'] ) ? array_to_json( $config['mock_member_data'] ) : 'null'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
				advocateReward: <?php echo isset( $config['referrals']['advocate_reward'] ) ? array_to_json( $config['referrals']['advocate_reward'] ) : 'null'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
				friendReward: <?php echo isset( $config['referrals']['friend_reward'] ) ? array_to_json( $config['referrals']['friend_reward'] ) : 'null'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
		rewardIconMap: <?php echo array_to_json( $this->get_reward_icon_map() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
		activity: <?php echo $config['activity'] ? array_to_json( $config['activity'] ) : 'null'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
		birthdayMonth: '<?php echo esc_js( $config['member']['birthday_month'] ); ?>',
		birthdayDay: '<?php echo esc_js( $config['member']['birthday_day'] ); ?>',
		birthdayCanEdit: <?php echo $config['member']['birthday_can_edit'] ? 'true' : 'false'; ?>,
		birthdayPoints: <?php echo (int) $config['member']['birthday_points']; ?>,
		birthdayMinDays: <?php echo (int) $config['member']['birthday_min_days']; ?>,
		birthdayDayFirst: <?php echo preg_match( '/^[jd]/', get_option( 'date_format', 'F j, Y' ) ) ? 'true' : 'false'; ?>,
		birthdayBanner: <?php echo ! empty( $config['birthday_day'] ) ? array_to_json( $config['birthday_day'] ) : 'null'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
		redeemActions: <?php echo array_to_json( $config['points']['redeem_actions'] ?? [] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
	})"
		x-init="
			const openFromHash = () => {
				if (window.location.hash === '#rewardswp-home') {
					if (!open) {
						open = true;
						currentPanel = 'main';
						showFixedHeader = false;
						scrollProgress = 0;
						headerCollapsed = false;
						$nextTick(() => {
							if ($refs.scrollContainer) { $refs.scrollContainer.scrollTop = 0; scrollTop = 0; }
							setTimeout(() => { if (typeof checkContentHeight === 'function') { checkContentHeight(); } }, 200);
						});
					}
				}
			};
			window.addEventListener('hashchange', openFromHash);
			openFromHash();
		"
		x-on:free-product-coupon-failed.window="freeProductCouponFailed = true"
		x-on:show-redeem-success.window="
			// Handle redeem success: populate data and show success panel
			freeProductCouponFailed = false; // Reset when new redemption occurs.
			redeemSuccessData = Object.assign({}, redeemSuccessData, {
				couponCode: $event.detail.couponCode,
				pointsSpent: $event.detail.pointsSpent,
				newBalance: $event.detail.newBalance,
				message: $event.detail.message || 'You have successfully redeemed ' + $event.detail.pointsSpent + ' points. Use the coupon code below at checkout:',
				icon_html: $event.detail.iconHtml || '',
			title: $event.detail.title || '',
			points_cost: $event.detail.pointsSpent,
			points_label: $event.detail.pointsLabel || 'points',
			coupon_terms: $event.detail.couponTerms || '',
			expiry_text: $event.detail.expiryText || '',
				is_applied: $event.detail.isApplied || false,
				reward_id: $event.detail.rewardId || 0,
				reward_type: $event.detail.rewardType || '',
				free_product_id: $event.detail.freeProductId || null,
				minimum_order_amount: $event.detail.minimumOrderAmount || 0,
				free_product_price: $event.detail.freeProductPrice || 0
			});
			// Update header points reactively
			userPoints = $event.detail.newBalance;
			userData.points = $event.detail.newBalance;
			// Refresh rewards data after successful redemption
			refreshRewardsData();
				// Hide the action from Ways to Redeem if limit was reached
				if ($event.detail.limitReached && $event.detail.actionId) {
					const actionElement = $root.querySelector('[data-action-id=' + String.fromCharCode(34) + $event.detail.actionId + String.fromCharCode(34) + ']');
					if (actionElement) {
						actionElement.style.display = 'none';
					}
				}
			// Mark that activity needs refresh when user navigates to it
			needsActivityRefresh = true;
		currentPanel = 'redeem-success';
		"
		x-on:setting-updated.window="
		isAdminPreview && handleSettingUpdate($event.detail);
		if ($event.detail.key === 'points_currency_singular') {
			currencySingular = $event.detail.value;
		}
		if ($event.detail.key === 'points_currency_plural') {
			currencyPlural = $event.detail.value;
		}
		if ($event.detail.key === 'welcome_heading') {
			welcomeGreeting = $event.detail.value;
		}
		if ($event.detail.key === 'program_name') {
			programName = $event.detail.value;
		}
		"
		x-cloak
		x-on:toggle-widget.window="open = !open"
		x-on:toggle-rewards-panel.window="toggle()"
		x-on:resize.window="measureBannerHeight()"
			x-on:change-test-state.window="isFriendClaimMode = $event.detail === 'friend-claim'"
			x-on:navigate-to-panel.window="navigateToPanel($event.detail.panel || $event.detail, $event.detail)"
			x-on:update-launcher-style.window="
				launcherStyle = (() => {
					return 'floating-circle';
				})();
			"
			x-on:update-launcher-display.window="
				open = false;
				launcherDisplayMode = $event.detail;
				$nextTick(() => measureLauncherWidth());
			"
			x-on:update-launcher-text.window="
				open = false;
				launcherText = $event.detail;
				$nextTick(() => measureLauncherWidth());
			"
			x-on:update-background-image.window="
				backgroundImageUrl = $event.detail;
				document.documentElement.style.setProperty('--rwp-header-text', this.headerTextColor);
			"
			x-on:update-logo.window="
				logoUrl = $event.detail;
			"
			x-on:update-show-logo.window="
				showLogo = $event.detail;
			"
			x-on:update-fade-overlay.window="
				showFadeOverlay = $event.detail;
			"
			x-on:global-tier-changed.window="
				globalTier = $event.detail;
			"
			x-on:background-image-system-updated.window="
				backgroundImage.enabled = $event.detail.enabled;
				backgroundImage.url = $event.detail.url;
				// Update legacy backgroundImageUrl for compatibility
				backgroundImageUrl = backgroundImage.enabled && backgroundImage.url ? backgroundImage.url : '';
			"
			x-on:text-overlay-system-updated.window="
				textOverlay[$event.detail.property] = $event.detail.value;
			"
			x-on:update-program-name.window="
				programName = $event.detail;
			"
			x-on:update-welcome-greeting.window="
				welcomeGreeting = $event.detail;
			"
		>
			<?php // Rewards launcher. ?>
			<?php $this->render_launcher( $config['launcher']['display'], $config['launcher']['text'] ); ?>

			<?php // Responsive Panel. ?>
			<?php $this->render_panel( $config ); ?>
		</div>
		<?php
	}

	/**
	 * Render the launcher button.
	 *
	 * @since 1.1.0
	 * @param string $launcher_display Launcher display option.
	 * @param string $launcher_text Launcher text option.
	 * @return void
	 */
	private function render_launcher( string $launcher_display = 'both', string $launcher_text = 'Rewards' ): void {
		?>
		<button
			x-ref="launcherButton"
			x-cloak
			x-show="(launcherStyle !== 'side-tab' || !open) && !(isMobile && open)"
		x-on:click="toggle()"
		x-bind:style="measuredWidth ? 'width: ' + (open || launcherDisplayMode === 'icon' ? '48px' : measuredWidth + 'px') + ';' : ''"
			class="rwp-launcher z-[var(--rwp-z-launcher)] bg-rwp-brand text-rwp-button hover:bg-rwp-brand-hover text-rwp-widget-button-text cursor-pointer flex items-center justify-center overflow-hidden select-none transition-[width,background-color] duration-300 ease-out motion-reduce:transition-none rounded-full h-12 py-2 pointer-events-auto focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
		x-bind:class="open || launcherDisplayMode === 'icon' ? 'px-3' : 'px-6'"
		data-highlight="launcher"
	x-bind:aria-label="open ? '<?php echo esc_attr__( 'Close rewards', 'rewardswp' ); ?>' : '<?php echo esc_attr__( 'Open rewards', 'rewardswp' ); ?> - ' + userData.points + ' ' + (userData.points == 1 ? currencySingular : currencyPlural) + ' <?php echo esc_attr__( 'available', 'rewardswp' ); ?>'"
			x-bind:aria-expanded="open.toString()"
			aria-controls="rwp-rewards-panel"
		>
			<?php
			/*
			 * Icon
			 */
			?>
			<span
				x-show="(launcherDisplayMode === 'icon' || launcherDisplayMode === 'both') || open"
				x-bind:class="'shrink-0' + (!open && launcherDisplayMode === 'both' ? ' mr-2' : '')">
				<span x-show="!open">
					<?php $this->icon_helper->render_icon( 'gift', 'size-6', 'bold' ); ?>
				</span>
				<span x-show="open">
					<?php $this->icon_helper->render_icon( 'x', 'size-6' ); ?>
				</span>
			</span>

			<?php
			/*
			 * Label
			 */
			?>
			<span
				x-ref="launcherLabel"
				x-show="!open && (launcherDisplayMode === 'text' || launcherDisplayMode === 'both')"
				x-text="launcherText"
				class="whitespace-nowrap font-medium text-sm">
			</span>
		</button>
		<?php
	}

	/**
	 * Render responsive panel wrapper.
	 *
	 * @since 1.1.0
	 * @param array $config Widget configuration (nested structure).
	 * @return void
	 */
	private function render_panel( array $config ): void {
		?>
		<?php // Mobile Panel. ?>
		<template x-if="isMobile">
			<div
				id="rwp-rewards-panel"
				x-ref="rewardsPanel"
				x-bind:hidden="!open"
				class="rwp-mobile-panel fixed inset-0 z-[var(--rwp-z-panel)] bg-rwp-widget-bg overflow-hidden pointer-events-auto flex-col"
				data-highlight="panel-bg"
				x-bind:style="'padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom); padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right);'"
				role="dialog"
				aria-labelledby="rewards-title"
			>
				<?php echo $this->render_panel_content( $config ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is escaped. ?>
			</div>
		</template>

		<?php // Desktop Panel. ?>
		<template x-if="!isMobile">
			<div
				id="rwp-rewards-panel"
				x-ref="rewardsPanel"
				x-bind:hidden="!open"
				class="rwp-desktop-panel rwp-desktop-panel--<?php echo esc_attr( $config['panel']['animation'] ); ?> z-[var(--rwp-z-panel)] bg-rwp-widget-bg rounded-xl overflow-hidden"
				x-bind:data-panel-state="isFriendClaimMode ? 'friend-claim' : 'combined'"
				data-highlight="panel-bg"
				role="dialog"
				aria-labelledby="rewards-title"
			>
				<?php echo $this->render_panel_content( $config ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is escaped. ?>
			</div>
		</template>
		<?php
	}

	/**
	 * Render the main panel content.
	 *
	 * @since 1.1.0
	 * @param array $config Widget configuration (nested structure).
	 * @return string HTML content.
	 */
	private function render_panel_content( array $config ): string {
		ob_start();
		$background_image_height_style = '';

		if ( is_numeric( $config['panel']['background_height'] ) ) {
			$background_image_height_style = sprintf(
				' height: %dpx;',
				absint( $config['panel']['background_height'] )
			);
		}

		?>






		<?php
			/*
			* Home panel header background image div
			*/
		?>




		<?php // Close button. ?>
		<button
			x-show="!isFriendClaimMode"
			x-on:click="open = false"
			x-bind:class="'group p-2 rounded transition-colors cursor-pointer z-[var(--rwp-z-close-button)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 ' + (isFriendClaimMode ? 'text-gray-500 hover:text-gray-700' : 'text-rwp-widget-header-text') + ' ' + (isMobile ? 'fixed' : 'absolute top-[12px] right-[12px]')"
			x-bind:style="isMobile ? 'top: calc(env(safe-area-inset-top) + 12px); right: calc(env(safe-area-inset-right) + 12px);' : ''"
			aria-label="<?php esc_attr_e( 'Close', 'rewardswp' ); ?>"
		>
			<div x-bind:class="'p-1 rounded transition-colors ' + (isFriendClaimMode ? 'group-hover:bg-gray-100' : 'group-hover:bg-white/10')">
				<div class="size-5 flex items-center justify-center">
					<?php $this->icon_helper->render_icon( 'x', 'size-5' ); ?>
				</div>
			</div>
		</button>

		<?php
			/*
			* Dynamic Fixed Header - Only appears when scrolled on main panel
			*/
		?>
		<div
			x-bind:hidden="!(currentPanel === 'main' && showFixedHeader && !isFriendClaimMode)"
			x-bind:class="'rwp-fixed-header absolute top-0 left-0 right-0 z-50 bg-rwp-brand text-rwp-widget-header-text flex items-center justify-between ' + (isMobile ? '' : 'px-5 h-16')"
			x-bind:style="isMobile ? 'padding-left: calc(env(safe-area-inset-left) + 20px); padding-right: calc(env(safe-area-inset-right) + 20px); padding-top: env(safe-area-inset-top); min-height: 64px;' : ''"
			data-highlight="header-bg"
		>
			<?php echo wp_kses( $this->render_fixed_header_content( $config ), kses() ); ?>
		</div>

		<?php
		/*
		* Fixed Header for Sub-panels - Always visible, no animations
		*/
		?>
		<div
			x-show="currentPanel !== 'main' && !isFriendClaimMode"
			x-bind:class="'absolute top-0 left-0 right-0 z-50 bg-rwp-brand text-rwp-widget-header-text flex items-center justify-between ' + (isMobile ? '' : 'px-5 h-16')"
			x-bind:style="isMobile ? 'padding-left: calc(env(safe-area-inset-left) + 20px); padding-right: calc(env(safe-area-inset-right) + 20px); padding-top: env(safe-area-inset-top); min-height: 64px;' : ''"
			data-highlight="header-bg"
		>
			<?php echo wp_kses( $this->render_fixed_header_content( $config ), kses() ); ?>
		</div>


		<?php
			/*
			* Home panel header background image div
			*/
		?>
		<template x-if="currentPanel === 'main' && !isFriendClaimMode">
			<div
				x-bind:class="'absolute top-0 left-0 w-full -z-10 bg-cover bg-center <?php echo $config['panel']['background_height'] === 'full' ? 'h-full' : ''; ?>' + (!(backgroundImage.enabled && backgroundImage.url && isProFeatureAccessible('pro')) ? ' bg-rwp-brand' : '')"
				x-bind:style="
				'transform: translate3d(0px, ' + getParallaxOffset(scrollTop) + 'px, 0px); ' +
				(backgroundImage.enabled && backgroundImage.url && isProFeatureAccessible('pro') ? 'background-image: url(' + backgroundImage.url + ');' : '') +
				'<?php echo is_numeric( $config['panel']['background_height'] ) ? ' height: ' . esc_js( (string) $config['panel']['background_height'] ) . 'px;' : ''; ?>' +
				<?php echo $config['panel']['background_height'] === 'auto' ? '\'height: \' + dynamicBackgroundHeight + \'px;\'' : ''; ?>"
			>
			</div>
		</template>



		<?php
		/*
		* Inner relative wrapper for positioning context
		*/
		?>


		<div
			x-bind:class="'relative w-full h-full flex flex-col panel-container flex-1' + (currentPanel === 'main' ? ' overflow-y-auto overflow-x-hidden custom-scrollbar' : '')"
			@scroll="handleScroll($event)"
			x-ref="scrollContainer"
		>


			<?php
			/*
			* Home panel header container
			*/
			?>
			<div
				x-show="currentPanel === 'main' && !isFriendClaimMode"
				class="panel-expanded-header text-rwp-widget-header-text bg-cover bg-center relative"
			>
				<div class="banner-content px-5 pb-6 pt-16 h-full flex flex-col justify-end relative"
					x-ref="bannerContent"
					x-bind:style="'opacity: ' + getTextOpacity(scrollTop)"
				>
					<div class="relative z-10">
						<div x-show="memberId > 0">
							<?php
							/*
							 * Points mode (points enabled, with or without referrals)
							 *
							 * Example:
							 *
							 * [Points currency label]
							 * [Points total]
							 *
							 */
							?>
							<template x-if="showPoints">
								<div>
									<div class="text-base" x-text="yourLabel + ' ' + (userData.points == 1 ? currencySingular : currencyPlural)"></div>
									<div class="font-bold text-4xl mb-1" x-text="userData.points.toLocaleString()"></div>
								</div>
							</template>

							<?php
							/*
							 * Referrals-only mode (referrals enabled, points disabled)
							 *
							 * Example:
							 *
							 * Share & Earn with
							 * [Program name]
							 *
							 */
							?>
							<template x-if="showReferrals && !showPoints">
								<div>
									<div class="text-base"><?php esc_html_e( 'Share & Earn with', 'rewardswp' ); ?></div>
									<div class="font-semibold text-3xl" x-text="programName"></div>
								</div>
							</template>
						</div>

						<?php
						/*
						* Non-member view: Welcome message

						* Example:
						*
						* [Welcome heaing]
						* [Program name]
						*/
						?>
						<div x-show="memberId === 0">
							<div class="text-base">
								<span x-show="!isFriendClaimMode" x-text="welcomeGreeting"></span>
							</div>
							<div class="font-semibold text-3xl">
								<span x-show="!isFriendClaimMode" x-text="programName"></span>
							</div>
						</div>
					</div>
				</div>
			</div>



			<?php
			/*
			* Main view content
			*/
			?>
			<div
				class="flex-1"
				x-bind:style="'height: 100%;' + (currentPanel !== 'main' ? ' padding-top: 64px;' : ' padding-top: 0;')"
				x-ref="contentArea"
			>
				<?php echo $this->render_panel_states( $config ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is escaped. ?>
			</div>

			<?php // Main panel bottom scroll fade — sticky inside scroll container for reliable positioning. ?>
			<div x-show="currentPanel === 'main'"
				x-bind:class="isScrolledToBottom ? 'opacity-0' : 'opacity-100'"
				class="sticky bottom-0 h-8 -mt-8 shrink-0 pointer-events-none z-40 transition-opacity duration-200"
				style="margin-right: var(--rwp-scrollbar-width, 8px); background: linear-gradient(to top, var(--color-rwp-widget-bg), transparent);"></div>

		</div>

		<?php
		return ob_get_clean();
	}

	/**
	 * Render fixed header content.
	 *
	 * @since 1.1.0
	 * @param array $config Widget configuration (nested structure).
	 * @return string HTML content.
	 */
	private function render_fixed_header_content( array $config ): string {
		ob_start();
		?>
		<div class="flex items-center gap-3 flex-1 min-w-0">
			<?php // Back button for sub-panels. ?>
			<button
				x-show="currentPanel !== 'main'"
				x-on:click="
					currentPanel === 'redeem-success' ? navigateToPanel('ways-to-redeem') :
					currentPanel === 'create-account-prompt' ? navigateToPanel('ways-to-redeem') :
					currentPanel.startsWith('reward-detail-') ? navigateToPanel('your-rewards') :
					currentPanel === 'birthday-picker' ? navigateToPanel('ways-to-earn') :
					navigateToPanel('main')
				"
				class="group p-2 rounded transition-colors cursor-pointer -ml-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
				aria-label="<?php esc_attr_e( 'Back', 'rewardswp' ); ?>"
			>
				<div class="p-1 rounded transition-colors group-hover:bg-white/10">
					<div class="size-5 flex items-center justify-center">
						<?php $this->icon_helper->render_icon( 'chevron-left' ); ?>
					</div>
				</div>
			</button>
			<div>
				<div class="font-semibold text-base">
					<!-- Points balance (shown on all panels when points program is active) -->
					<span x-show="showPoints && !!memberId"><span x-text="userData.points.toLocaleString()"></span> <span x-text="(userData.points == 1 ? currencySingular : currencyPlural)"></span></span>
					<!-- Referral count (shown on all panels when ONLY referrals program is active) -->
					<span x-show="showReferrals && !showPoints && !!memberId" x-text="friendsReferred === 0 ? '<?php echo esc_js( __( 'No friends referred yet', 'rewardswp' ) ); ?>' : (friendsReferred === 1 ? '1 <?php echo esc_js( __( 'friend referred', 'rewardswp' ) ); ?>' : friendsReferred + ' <?php echo esc_js( __( 'friends referred', 'rewardswp' ) ); ?>')">
					</span>
					<!-- Fallback: Activity label when no member or no programs -->
					<span x-show="!memberId && currentPanel === 'activity'"><?php esc_html_e( 'Activity', 'rewardswp' ); ?></span>
				</div>
				<div class="text-sm">
					<span x-show="!isFriendClaimMode" x-text="programName"></span>
				</div>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Render background image section.
	 *
	 * @since 1.1.0
	 * @param array $config Widget configuration.
	 * @return string HTML content.
	 */
	private function render_background_image( array $config = [] ): string {
		if ( empty( $config ) ) {
			$config = $this->get_widget_config( [] ); // Use empty args for fallback
		}
		ob_start();
		?>
		<template x-if="currentPanel === 'main'">
		<div
			x-bind:class="'absolute top-0 left-0 w-full -z-10 bg-cover bg-center <?php echo $config['panel']['background_height'] === 'full' ? 'h-full' : ''; ?>' + (!(backgroundImage.enabled && backgroundImage.url && isProFeatureAccessible('pro')) ? ' bg-rwp-brand' : '')"
			x-bind:style="
				(backgroundImage.enabled && backgroundImage.url && isProFeatureAccessible('pro') ? 'background-image: url(' + backgroundImage.url + ');' : '') +
				'<?php echo is_numeric( $config['panel']['background_height'] ) ? ' height: ' . esc_js( (string) $config['panel']['background_height'] ) . 'px;' : ''; ?>' +
				<?php echo $config['panel']['background_height'] === 'auto' ? '\'height: \' + dynamicBackgroundHeight + \'px;\'' : ''; ?>"
		>
			<?php // Text Readability Overlay. ?>
			<div
				x-show="backgroundImage.enabled && backgroundImage.url && textOverlay.mode !== 'disabled' && isProFeatureAccessible('pro')"
				class="absolute inset-0"
				x-bind:style="getOverlayStylesAsString()"
			></div>

			<?php // Brand color overlay. ?>
			<div
				x-show="backgroundImageUrl && rwpColors.headerBg !== '<?php echo esc_js( $config['panel']['brand_header_bg'] ); ?>'"
				class="absolute inset-0 bg-rwp-brand isolate mix-blend-color"
			></div>

			<?php // Legacy Gradient fade overlay. ?>
			<div
				x-show="showFadeOverlay && isProFeatureAccessible('pro')"
				class="absolute inset-x-0 bottom-0 h-10"
				x-bind:style="backgroundImageUrl ?
					'background: linear-gradient(to top, white, transparent);' :
					'background: linear-gradient(to top, ' + rwpColors.headerBg + '80, transparent);'"
			></div>
		</div>
		</template>
		<?php
		return ob_get_clean();
	}

	/**
	 * Render panel states content.
	 *
	 * @since 1.1.0
	 * @param array $config Widget configuration (nested structure).
	 * @return string HTML content.
	 */
	private function render_panel_states( array $config ): string {
		ob_start();

		$earn_actions   = $this->get_earn_actions(
			$config['points']['currency']['singular'],
			$config['points']['currency']['plural']
		);
		$redeem_actions = $config['points']['redeem_actions'];

		// Detect day-first vs month-first from WP date format.
		$wp_date_format = get_option( 'date_format', 'F j, Y' );
		$day_first      = (bool) preg_match( '/^[jd]/', $wp_date_format );

		// Make class properties available as local variables for use in the template
		$card_classes                = $this->card_classes;
		$nav_item_classes            = $this->nav_item_classes;
		$button_classes              = $this->get_button_classes( 'lg' );
		$button_lg_classes           = $this->get_button_classes( 'lg' );
		$button_sm_classes           = $this->get_button_classes( 'sm' );
		$button_with_spinner_classes = $this->get_button_classes( 'lg', true );
		$secondary_button_classes    = $this->secondary_button_classes;
		$icon_classes                = $this->icon_classes;
		$sub_panel_heading_classes   = $this->sub_panel_heading_classes;
		$input_classes               = $this->input_classes;
		?>


	<div
		x-show="!isFriendClaimMode && currentPanel === 'main'"
		x-bind:class="'px-4 pb-10 flex flex-col gap-4 ' + (currentPanel === 'main' ? (navigationDirection === 'initial' ? 'rwp-panel-fade-up' : (navigationDirection === 'forward' ? 'rwp-panel-slide-forward' : (navigationDirection === 'back' ? 'rwp-panel-slide-back' : ''))) : '')"
	>

		<!-- Birthday banner (shows all day on member's birthday) -->
		<div
			x-show="birthdayBanner && memberId > 0"
			data-birthday-banner
			class="<?php echo esc_attr( $card_classes ); ?> px-5"
		>
			<div class="flex items-center gap-3">
				<div class="flex-shrink-0">
					<?php $this->icon_helper->render_icon( 'earn-birthday', 'size-8 text-rwp-widget-icon-text' ); ?>
				</div>
				<div class="flex-1 min-w-0">
					<div class="text-base font-semibold text-rwp-widget-heading">
						<span x-text="birthdayBanner ? '<?php echo esc_js( __( 'Happy Birthday', 'rewardswp' ) ); ?> ' + birthdayBanner.member_name + '!' : ''"></span>
					</div>
					<div class="text-sm text-gray-500">
						<span x-text="birthdayBanner ? '<?php echo esc_js( __( 'Enjoy', 'rewardswp' ) ); ?> ' + birthdayBanner.points_earned.toLocaleString() + ' ' + currencyPlural + ' <?php echo esc_js( __( 'on us', 'rewardswp' ) ); ?>' : ''"></span>
					</div>
				</div>
			</div>
		</div>

		<?php if ( $config['member']['is_guest_mode'] ) : ?>


			<?php
			/*
			* Become a member Card
			*
			* Displays a "Become a member" card on the home panel for logged-out members
			*/
			?>
	<div x-show="memberId === 0 || isGuestMember">
	<div
		x-data="becomeMember({
			nonce: '<?php echo esc_attr( wp_create_nonce( AdvocateController::AJAX_NONCE ) ); ?>',
			ajaxUrl: '<?php echo esc_url( frontend_ajax_url() ); ?>',
			isLoggedIn: <?php echo is_user_logged_in() ? 'true' : 'false'; ?>,
			isGuestMember: <?php echo is_current_member_a_guest() ? 'true' : 'false'; ?>,
			userName: '<?php echo esc_js( $config['member']['name'] ); ?>',
			userEmail: '<?php echo esc_js( $config['member']['email'] ); ?>'
		})"
		x-on:show-register-form.window="currentScreen = 'register'; showForm = true; showError = false; $dispatch('navigate-to-panel', { panel: 'main', resetScroll: true });"
		x-on:show-login-form.window="currentScreen = 'login'; showForm = true; showError = false;"
			x-on:member-joined.window="justJoined = true"
			class="<?php echo esc_attr( $card_classes ); ?>"
			x-bind:class="navigationDirection === 'initial' ? 'rwp-stagger-fade-up rwp-stagger-1' : (navigationDirection === 'back' ? 'rwp-stagger-slide-left rwp-stagger-1' : '')"
			data-become-member-section
		>
				<?php
				/*
				* Become a member - Success State
				*/
				?>
				<div x-show="showSuccess" style="display: none;">
					<div class="flex justify-center mb-4">
						<div class="size-16 bg-green-100 rounded-full flex items-center justify-center">
							<svg class="size-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
								<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
							</svg>
						</div>
					</div>
					<h3 class="text-base font-semibold text-rwp-widget-heading mb-2">
						<?php
						/* translators: %s: Program name */
						echo esc_html(
							sprintf(
								/* translators: %s: Program name. */
								__( 'Welcome to %s!', 'rewardswp' ),
								get_option_value( ProgramNameOption::class )
							)
						);
						?>
					</h3>
					<p class="text-sm mb-6 px-2"><?php esc_html_e( 'You\'re now part of our rewards program! Start earning points and rewards with every purchase.', 'rewardswp' ); ?></p>

					<a
						x-bind:href="isAdminPreview ? 'javascript:void(0)' : '<?php echo esc_url( $this->integration->get_store_url() ); ?>'"
						x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
						x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
						x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
						class="<?php echo esc_attr( $button_classes ); ?> no-underline hover:bg-rwp-button-hover"
					>
						<?php
						// Dynamic button text based on active programs
						if ( $config['points']['enabled'] ) {
							esc_html_e( 'Start earning points', 'rewardswp' );
						} else {
							esc_html_e( 'Start shopping', 'rewardswp' );
						}
						?>
					</a>
				</div>

				<?php
				/*
				* Become a member - Initial State
				*
				* Displays a "Become a member" card on the home panel for logged-out members
				*/
				?>
				<div x-show="!showSuccess">
				<!-- Register Screen -->
				<div class="text-center" x-show="currentScreen === 'register'" x-transition:enter="transition-all duration-300 ease-out" x-transition:enter-start="opacity-0 -translate-y-4 scale-95" x-transition:enter-end="opacity-100 translate-y-0 scale-100">
					<h2 class="text-base font-semibold text-rwp-widget-heading mb-2"><?php esc_html_e( 'Join our rewards program', 'rewardswp' ); ?></h2>
					<p class="text-sm mb-4 px-2" x-show="!(isGuestMember && showGuestConfirmation)"><?php esc_html_e( 'Create a free account to start earning points and unlocking member-only perks.', 'rewardswp' ); ?></p>
					<p class="text-sm mb-4 px-2" x-show="isGuestMember && showGuestConfirmation" x-cloak><?php esc_html_e( 'Your order email will be used to create your account.', 'rewardswp' ); ?></p>

				<!-- Form for non-logged-in users, guest members, and logged-in non-members - Initially hidden -->
				<?php if ( $config['member']['is_guest_mode'] ) : ?>

						<!-- Guest confirmation: one-click account creation (no form fields needed, backend uses session) -->
						<div
							x-show="isGuestMember && showGuestConfirmation"
							x-transition:enter="transition-all duration-300 ease-out"
							x-transition:enter-start="opacity-0 -translate-y-4 scale-95"
							x-transition:enter-end="opacity-100 translate-y-0 scale-100"
							class="space-y-3"
						>
							<!-- Email identity card -->
							<div class="flex items-center gap-3 rounded-lg border border-gray-200 bg-gray-50/50 px-4 py-3 text-left">
								<?php $this->icon_helper->render_icon( 'envelope', 'size-5 text-gray-400 flex-shrink-0' ); ?>
								<div class="min-w-0">
									<p class="text-sm font-medium text-rwp-widget-heading truncate" x-text="formData.email"></p>
									<p class="text-xs text-gray-500"><?php esc_html_e( 'From your recent order', 'rewardswp' ); ?></p>
								</div>
							</div>

							<!-- Create account button -->
							<button
								type="button"
								tabindex="0"
								x-on:click="if (!isAdminPreview && !isDemo) { createMember() }"
								x-bind:disabled="isSubmitting"
								x-bind:class="isSubmitting ? 'opacity-50 cursor-not-allowed' : (isAdminPreview || isDemo) ? '!cursor-not-allowed' : 'hover:bg-rwp-brand-hover'"
								x-bind:data-preview-disabled="(isAdminPreview || isDemo) ? 'true' : null"
								x-bind:data-tippy-content="(isAdminPreview || isDemo) ? '<?php esc_attr_e( 'Disabled for preview', 'rewardswp' ); ?>' : null"
								class="<?php echo esc_attr( $this->get_button_classes( 'lg', true ) ); ?>"
								data-highlight="success-state"
								data-become-member-btn
								x-bind:aria-label="isSubmitting ? '<?php esc_attr_e( 'Creating account...', 'rewardswp' ); ?>' : '<?php esc_attr_e( 'Create my account', 'rewardswp' ); ?>'"
							>
								<span
									class="[grid-area:stack] transition-opacity duration-200"
									x-bind:class="{ 'invisible': isSubmitting, 'visible': !isSubmitting }"
								><?php echo esc_html( __( 'Create my account', 'rewardswp' ) ); ?></span>
								<?php $this->icon_helper->render_button_spinner( 'isSubmitting' ); ?>
							</button>

						</div>

						<!-- Full registration form (shown for non-guest visitors only) -->
						<form x-on:submit.prevent="if (!isAdminPreview && !isDemo) { createMember() }" x-show="showForm && !isGuestMember" x-transition:enter="transition-all duration-300 ease-out" x-transition:enter-start="opacity-0 -translate-y-4 scale-95" x-transition:enter-end="opacity-100 translate-y-0 scale-100" x-transition:leave="transition-all duration-200 ease-in" x-transition:leave-start="opacity-100 translate-y-0 scale-100" x-transition:leave-end="opacity-0 -translate-y-2 scale-98" style="display: none;">
						<?php $this->anti_spam_service->render_field(); ?>
						<div class="space-y-3">

							<div>
								<label for="member-name" class="sr-only block text-sm font-medium text-rwp-widget-text mb-1.5 text-left"><?php esc_html_e( 'Name', 'rewardswp' ); ?></label>
								<input
									type="text"
									id="member-name"
									x-model="formData.name"
									class="<?php echo esc_attr( $input_classes ); ?>"
									placeholder="<?php esc_attr_e( 'Your name', 'rewardswp' ); ?>"
									required
								>
							</div>
							<div>
								<label for="member-email" class="sr-only block text-sm font-medium text-rwp-widget-text mb-1.5 text-left"><?php esc_html_e( 'Email', 'rewardswp' ); ?></label>
								<input
									type="email"
									id="member-email"
									x-model="formData.email"
									class="<?php echo esc_attr( $input_classes ); ?>"
									placeholder="<?php esc_attr_e( 'Your email', 'rewardswp' ); ?>"
									required
								>
							</div>

							<button
								type="submit"
								tabindex="0"
								x-bind:disabled="isSubmitting"
								x-bind:class="isSubmitting ? 'opacity-50 cursor-not-allowed' : (isAdminPreview || isDemo) ? '!cursor-not-allowed' : 'hover:bg-rwp-brand-hover'"
								x-bind:data-preview-disabled="(isAdminPreview || isDemo) ? 'true' : null"
								x-bind:data-tippy-content="(isAdminPreview || isDemo) ? '<?php esc_attr_e( 'Disabled for preview', 'rewardswp' ); ?>' : null"
								class="<?php echo esc_attr( $this->get_button_classes( 'lg', true ) ); ?>"
								data-highlight="success-state"
								data-become-member-btn
								x-bind:aria-label="isSubmitting ? '<?php esc_attr_e( 'Creating account...', 'rewardswp' ); ?>' : '<?php esc_attr_e( 'Create my account', 'rewardswp' ); ?>'"
							>
							<span
								class="[grid-area:stack] transition-opacity duration-200"
								x-bind:class="{ 'invisible': isSubmitting, 'visible': !isSubmitting }"
							><?php echo esc_html( __( 'Create my account', 'rewardswp' ) ); ?></span>
							<?php $this->icon_helper->render_button_spinner( 'isSubmitting' ); ?>
						</button>
						</div>


					</form>
					<?php endif; ?>

					<!-- Error State for Register Screen -->
					<div x-show="showError && currentScreen === 'register'" x-transition:enter="transition-opacity duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" style="display: none;" class="mt-4">
						<div class="bg-red-50 border border-red-200 rounded-lg p-4">
							<div class="flex items-center">
								<svg class="size-5 text-red-400 mr-3 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
									<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
								</svg>
								<span class="text-sm text-red-700 text-left" x-html="errorMessage"></span>
							</div>
						</div>
					</div>

			<button
				x-show="(!showForm || currentScreen !== 'register') && !(isGuestMember && showGuestConfirmation)"
				x-on:click="if (!isAdminPreview && !isDemo) { if (isGuestMember && userData.email) { window.dispatchEvent(new CustomEvent('rewardswp-join-with-email', { detail: { email: userData.email, name: userData.name } })); } handleJoinClick(); } else { currentScreen = 'register'; showForm = true; showError = false; }"
				class="<?php echo esc_attr( $this->get_button_classes( 'lg', true ) ); ?>"
				data-highlight="success-state"
				data-become-member-btn
				x-bind:aria-label="'<?php esc_attr_e( 'Join now to become a member', 'rewardswp' ); ?>'"
				tabindex="0"
			>
				<?php echo esc_html( __( 'Join now', 'rewardswp' ) ); ?>
			</button>

					<?php if ( ! is_user_logged_in() ) : ?>
					<p class="text-sm mt-4">
						<?php esc_html_e( 'Already have an account?', 'rewardswp' ); ?>
						<button
							x-on:click="currentScreen = 'login'; showForm = true; showError = false;"
							class="font-medium underline text-rwp-accent cursor-pointer"
							tabindex="0"
						><?php esc_html_e( 'Sign in', 'rewardswp' ); ?></button>
					</p>
					<?php endif; ?>
					</div>

					<!-- Login Screen -->
					<div class="text-center" x-show="currentScreen === 'login'" x-transition:enter="transition-all duration-300 ease-out" x-transition:enter-start="opacity-0 -translate-y-4 scale-95" x-transition:enter-end="opacity-100 translate-y-0 scale-100">
						<h2 class="text-base font-semibold text-rwp-widget-heading mb-2"><?php esc_html_e( 'Sign in to your account', 'rewardswp' ); ?></h2>
						<p class="text-sm mb-6"><?php esc_html_e( 'Welcome back! Sign in to access your rewards.', 'rewardswp' ); ?></p>

						<!-- Login Form -->
						<?php if ( ! is_user_logged_in() ) : ?>
						<form x-on:submit.prevent="if (!isAdminPreview && !isDemo) { handleLogin() }" class="mb-4">
							<div class="space-y-3">
								<div>
									<label for="login-email" class="sr-only block text-sm font-medium text-rwp-widget-text mb-2 text-left"><?php esc_html_e( 'Email or Username', 'rewardswp' ); ?></label>
									<input
										type="text"
										id="login-email"
										x-model="formData.email"
										class="<?php echo esc_attr( $input_classes ); ?>"
										placeholder="<?php esc_attr_e( 'Your email or username', 'rewardswp' ); ?>"
										required
									>
								</div>
								<div>
									<label for="login-password" class="sr-only block text-sm font-medium text-rwp-widget-text mb-2 text-left"><?php esc_html_e( 'Password', 'rewardswp' ); ?></label>
									<input
										type="password"
										id="login-password"
										x-model="formData.password"
										class="<?php echo esc_attr( $input_classes ); ?>"
										placeholder="<?php esc_attr_e( 'Your password', 'rewardswp' ); ?>"
										required
									>
								</div>

								<button
									type="submit"
									tabindex="0"
									x-bind:disabled="isSubmitting"
									x-bind:class="isSubmitting ? 'opacity-50 cursor-not-allowed' : (isAdminPreview || isDemo) ? '!cursor-not-allowed' : 'hover:bg-rwp-button-hover'"
									x-bind:data-preview-disabled="(isAdminPreview || isDemo) ? 'true' : null"
									x-bind:data-tippy-content="(isAdminPreview || isDemo) ? '<?php esc_attr_e( 'Disabled for preview', 'rewardswp' ); ?>' : null"
									class="<?php echo esc_attr( $this->get_button_classes( 'lg', true ) ); ?>"
								>
								<span
									class="[grid-area:stack] transition-opacity duration-200"
									x-bind:class="{ 'invisible': isSubmitting, 'visible': !isSubmitting }"
								><?php echo esc_html( __( 'Sign in', 'rewardswp' ) ); ?></span>
							<?php $this->icon_helper->render_button_spinner( 'isSubmitting' ); ?>
							</button>

							</div>


						</form>
						<?php endif; ?>

						<!-- Error State for Login Screen -->
						<div x-show="showError && currentScreen === 'login'" x-transition:enter="transition-opacity duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" style="display: none;" class="mb-4">
							<div class="bg-red-50 border border-red-200 rounded-lg p-4">
								<div class="flex items-center">
									<svg class="size-5 text-red-400 mr-3 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
										<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
									</svg>
									<span class="text-sm text-red-700 text-left" x-html="errorMessage"></span>
								</div>
							</div>
						</div>

					<?php if ( ! is_user_logged_in() ) : ?>
					<p class="text-sm">
						<?php esc_html_e( "Don't have an account?", 'rewardswp' ); ?>
						<button
							x-on:click="currentScreen = 'register'; showForm = true; showError = false;"
							class="font-medium underline text-rwp-accent cursor-pointer"
							tabindex="0"
						><?php esc_html_e( 'Join now', 'rewardswp' ); ?></button>
					</p>
					<?php endif; ?>
				</div>
			</div>
		</div>
	</div>
	<?php endif; ?>

	<!-- Points Section for Non-Members -->
		<?php if ( $config['member']['is_guest_mode'] && ( $config['points']['enabled'] || $this->is_admin_preview ) ) : ?>
		<div x-show="memberId === 0 && pointsEnabled">
			<?php echo wp_kses( $this->render_points_section_for_non_members( $config ), kses() ); ?>
		</div>
	<?php endif; ?>

	<!-- Referrals Section for Non-Members -->
		<?php if ( $config['member']['is_guest_mode'] && ( $config['referrals']['enabled'] || $this->is_admin_preview ) ) : ?>
		<div x-show="memberId === 0 && referralsEnabled">
			<?php echo wp_kses( $this->render_referrals_section_for_non_members( $config ), kses() ); ?>
		</div>
	<?php endif; ?>


		<?php
		/*
		 * "Your rewards" home panel.
		 *
		 * Displays a "Your rewards" card on the home panel for logged-in members
		 * when there are available rewards.
		 */
		?>
		<div
			x-show="availableRewardsCount > 0 && memberId > 0"
			<?php if ( ! $config['is_admin_preview'] ) : ?>
			x-transition:enter="transition-opacity duration-300 ease-out"
			x-transition:enter-start="opacity-0"
			x-transition:enter-end="opacity-100"
			x-transition:leave="transition-opacity duration-200 ease-in"
			x-transition:leave-start="opacity-100"
			x-transition:leave-end="opacity-0"
			<?php endif; ?>
			class="bg-white rounded-lg p-4 py-3 text-rwp-widget-text shadow-card"
		>
			<button
				x-on:click="navigateToPanel('your-rewards')"
				class="<?php echo esc_attr( $nav_item_classes ); ?>"
			>
				<div class="flex-1">
					<div class="text-base font-semibold text-rwp-widget-heading">
						<?php esc_html_e( 'Your rewards', 'rewardswp' ); ?>
					</div>
					<div class="text-sm text-gray-500">
						<span x-text="availableRewardsCount === 1 ?
							'<?php echo esc_js( __( 'You have 1 reward available', 'rewardswp' ) ); ?>' :
							'<?php echo esc_js( __( 'You have', 'rewardswp' ) ); ?> ' + availableRewardsCount + ' <?php echo esc_js( __( 'rewards available', 'rewardswp' ) ); ?>'"></span>
					</div>
				</div>
				<?php $this->icon_helper->render_icon( 'chevron-right', 'size-5 text-gray-400' ); ?>
			</button>
		</div>


				<?php
				/*
				* Home panel - Points Section
				*
				* Shows "Ways to earn" and "Ways to redeem" buttons for logged-in members.
				*/
				?>
		<?php if ( $config['points']['enabled'] || $this->is_admin_preview ) : ?>
		<div class="<?php echo esc_attr( $card_classes ); ?>" x-show="memberId > 0 && pointsEnabled" x-bind:class="navigationDirection === 'initial' ? 'rwp-stagger-fade-up rwp-stagger-2' : (navigationDirection === 'back' ? 'rwp-stagger-slide-left rwp-stagger-2' : '')">



				<button
					x-on:click="navigateToPanel('ways-to-earn')"
					class="<?php echo esc_attr( $nav_item_classes ); ?>"
				>
					<div class="flex items-center gap-4">
						<div class="<?php echo esc_attr( $icon_classes ); ?>">
							<?php $this->icon_helper->render_icon( 'earn', 'size-8' ); ?>
						</div>
						<span class="text-sm text-rwp-widget-heading font-medium"><?php esc_html_e( 'Ways to earn', 'rewardswp' ); ?></span>
					</div>
					<?php $this->icon_helper->render_icon( 'chevron-right', 'size-5 text-gray-400' ); ?>
				</button>
				<button
					x-on:click="navigateToPanel('ways-to-redeem')"
					class="<?php echo esc_attr( $nav_item_classes ); ?>"
				>
					<div class="flex items-center gap-4">
						<div class="<?php echo esc_attr( $icon_classes ); ?>">
							<?php $this->icon_helper->render_icon( 'gift-free', 'size-8' ); ?>
						</div>
						<span class="text-sm text-rwp-widget-heading font-medium"><?php esc_html_e( 'Ways to redeem', 'rewardswp' ); ?></span>
					</div>
					<?php $this->icon_helper->render_icon( 'chevron-right', 'size-5 text-gray-400' ); ?>
				</button>
			</div>
			<?php endif; ?>


		<?php
		/*
		* Referrals Section
		*/
		?>
		<?php if ( $config['referrals']['enabled'] || $this->is_admin_preview ) : ?>
		<div id="refer-your-friends-section" class="<?php echo esc_attr( $card_classes ); ?>" x-show="memberId > 0 && referralsEnabled" x-bind:class="navigationDirection === 'initial' ? 'rwp-stagger-fade-up rwp-stagger-3' : (navigationDirection === 'back' ? 'rwp-stagger-slide-left rwp-stagger-3' : '')">
				<h2 class="text-base font-semibold text-rwp-widget-heading mb-2 text-left pl-4"><?php esc_html_e( 'Refer your friends', 'rewardswp' ); ?></h2>


				<?php
				// Get reward info for display. Admin preview adds Alpine attributes for reactivity.
				$has_advocate_reward = ! empty( $config['referrals']['advocate_reward'] ) && is_array( $config['referrals']['advocate_reward'] ) && ! empty( $config['referrals']['advocate_reward']['type'] );
				$has_friend_reward   = ! empty( $config['referrals']['friend_reward'] ) && is_array( $config['referrals']['friend_reward'] ) && ! empty( $config['referrals']['friend_reward']['type'] );
				$advocate_info       = $has_advocate_reward ? $this->get_reward_info_from_config( $config['referrals']['advocate_reward'], $config['currency_symbol'] ) : null;
				$friend_info         = $has_friend_reward ? $this->get_reward_info_from_config( $config['referrals']['friend_reward'], $config['currency_symbol'] ) : null;
				?>

				<!-- Advocate reward -->
				<?php if ( $has_advocate_reward || $this->is_admin_preview ) : ?>
				<div class="flex items-center justify-between"
					<?php
					if ( $this->is_admin_preview ) :
						?>
						x-show="advocateReward && advocateReward.type"<?php endif; ?>>
					<div class="flex items-center gap-4 px-4 py-3">
						<div class="<?php echo esc_attr( $icon_classes ); ?>"
							<?php
							if ( $this->is_admin_preview ) :
								?>
								x-html="getRewardIconHtml(advocateReward)"<?php endif; ?>>
							<?php if ( $has_advocate_reward ) : ?>
								<?php $this->icon_helper->render_icon( $advocate_info['icon'], 'size-8' ); ?>
							<?php endif; ?>
						</div>
						<div class="flex-1 text-left">
							<div class="font-medium text-rwp-widget-heading text-sm"><?php esc_html_e( 'You get', 'rewardswp' ); ?></div>
							<div class="text-sm"
								<?php
								if ( $this->is_admin_preview ) :
									?>
									x-text="getRewardDescription(advocateReward)"<?php endif; ?>>
								<?php if ( $has_advocate_reward ) : ?>
									<?php echo esc_html( $advocate_info['formatted_amount'] ); ?>
								<?php endif; ?>
							</div>
						</div>
					</div>
					<div class="size-5"></div>
				</div>
				<?php endif; ?>
				<!-- Friend reward -->
				<?php if ( $has_friend_reward || $this->is_admin_preview ) : ?>
				<div class="flex items-center justify-between"
					<?php
					if ( $this->is_admin_preview ) :
						?>
						x-show="friendReward && friendReward.type"<?php endif; ?>>
					<div class="flex items-center gap-4 px-4 py-3">
						<div class="<?php echo esc_attr( $icon_classes ); ?>"
							<?php
							if ( $this->is_admin_preview ) :
								?>
								x-html="getRewardIconHtml(friendReward)"<?php endif; ?>>
							<?php if ( $has_friend_reward ) : ?>
								<?php $this->icon_helper->render_icon( $friend_info['icon'], 'size-8' ); ?>
							<?php endif; ?>
						</div>
						<div class="flex-1 text-left">
							<div class="font-medium text-rwp-widget-heading text-sm"><?php esc_html_e( 'They get', 'rewardswp' ); ?></div>
							<div class="text-sm text-gray-700"
								<?php
								if ( $this->is_admin_preview ) :
									?>
									x-text="getRewardDescription(friendReward)"<?php endif; ?>>
								<?php if ( $has_friend_reward ) : ?>
									<?php echo esc_html( $friend_info['formatted_amount'] ); ?>
								<?php endif; ?>
							</div>
						</div>
					</div>
					<div class="size-5"></div>
				</div>
				<?php endif; ?>


				<!-- Referral link input -->
				<div>
					<label for="referral-link-input" class="block font-medium text-gray-900 sr-only text-sm/6"><?php esc_html_e( 'Invite link', 'rewardswp' ); ?></label>
					<div class="mt-2">
						<div x-data="{ isCheckmark: false }" class="flex bg-white rounded-lg outline-1 -outline-offset-1 outline-gray-200">
					<input
						x-on:click="handleCopyClick($el, null, false)"
						id="referral-link-input"
						type="text"
						readonly
						class="block min-w-0 pl-5 py-3 text-sm text-gray-900 cursor-pointer grow focus:outline-none sm:text-sm/6 text-ellipsis"
						x-bind:value="userData.referralLink || '<?php echo esc_url( $config['member']['referral_link'] ?: 'https://yoursite.com/?share=df04c90' ); ?>'"
						x-ref="referralInput">

							<div class="flex">
								<button
									x-on:click="handleCopyClick($refs.referralInput, $data)"
									class="py-3 px-3 relative transition-all duration-200 cursor-pointer"
									x-bind:class="{ 'hover:text-gray-900 text-gray-500': !isCheckmark}"
									x-ref="copyButton">
									<div class="relative size-6">
										<div class="absolute inset-0 transition-all duration-300 ease-in-out"
											x-bind:class="isCheckmark ? 'opacity-0 scale-95' : 'opacity-100 scale-100'">
											<?php $this->icon_helper->render_icon( 'copy', 'size-6' ); ?>
										</div>
										<div class="absolute inset-0 transition-all duration-300 ease-in-out"
											x-bind:class="isCheckmark ? 'opacity-100 scale-100' : 'opacity-0 scale-95'">
											<?php $this->icon_helper->render_icon( 'check', 'size-6' ); ?>
										</div>
									</div>
								</button>
							</div>
						</div>
					</div>
				</div>

				<?php
					$__sharing_markup = $this->render_sharing_options(
						$this->get_sharing_options(
							[
								'is_admin_preview' => $config['is_admin_preview'],
								'referral_link'    => $config['member']['referral_link'],
							]
						),
						$config
					);
				if ( $__sharing_markup !== '' ) {
					echo wp_kses( $__sharing_markup, kses() );
				}
				?>
			</div>
			<?php endif; ?>

		<?php
		/*
		* Your Activity Card
		*
		* Displays a "Your activity" card on the home panel for logged-in members.
		* This card appears at the bottom of the main panel and links to the activity sub-panel.
		*/
		?>
		<div
			x-show="memberId > 0 && (pointsEnabled || referralsEnabled)"
			<?php if ( ! $config['is_admin_preview'] ) : ?>
			x-transition:enter="transition-opacity duration-300 ease-out"
			x-transition:enter-start="opacity-0"
			x-transition:enter-end="opacity-100"
			x-transition:leave="transition-opacity duration-200 ease-in"
			x-transition:leave-start="opacity-100"
			x-transition:leave-end="opacity-0"
			<?php endif; ?>
			class="bg-white rounded-lg p-4 py-3 text-rwp-widget-text shadow-card"
			x-bind:class="navigationDirection === 'initial' ? 'rwp-stagger-fade-up rwp-stagger-4' : (navigationDirection === 'back' ? 'rwp-stagger-slide-left rwp-stagger-4' : '')"
		>
			<button
				x-on:click="navigateToPanel('activity')"
				class="<?php echo esc_attr( $nav_item_classes ); ?>"
			>
				<div class="flex items-center gap-4">
					<div class="<?php echo esc_attr( $icon_classes ); ?>">
						<?php $this->icon_helper->render_icon( 'clock-history', 'size-8' ); ?>
					</div>
					<span class="text-sm text-rwp-widget-heading font-medium"><?php esc_html_e( 'Your activity', 'rewardswp' ); ?></span>
				</div>
				<?php $this->icon_helper->render_icon( 'chevron-right', 'size-5 text-gray-400' ); ?>
			</button>
		</div>
		</div>




		<?php
		/*
		* Friend Claim Panel
		*/
		?>
		<div
			x-show="isFriendClaimMode && currentPanel === 'main'"
			x-transition:enter="transition-all duration-300 ease-out delay-150"
			x-transition:enter-start="opacity-0"
			x-transition:enter-end="opacity-100"
			x-transition:leave="transition-opacity duration-0"
			x-transition:leave-start="opacity-100"
			x-transition:leave-end="opacity-0"
			class="px-6 py-12 bg-rwp-widget-bg"
		>
			<?php echo wp_kses( $this->render_friend_claim_content( $config['friend_claim'], $config['currency_symbol'] ?? '' ), kses() ); ?>
		</div>


		<?php
		/*
		* "Ways to Earn" sub-panel.
		* Shows "Ways to earn" list for logged-in members.
		*/
		?>
	<div
		x-show="currentPanel === 'ways-to-earn'"
		x-bind:class="'relative h-full flex flex-col overflow-hidden ' + (currentPanel === 'ways-to-earn' ? (navigationDirection === 'initial' ? 'rwp-panel-fade-up' : (navigationDirection === 'forward' ? 'rwp-panel-slide-forward' : (navigationDirection === 'back' ? 'rwp-panel-slide-back' : ''))) : '')"
		x-data="{ panelAtTop: true, panelAtBottom: false }"
		x-effect="if (currentPanel === 'ways-to-earn') { const el = $el.querySelector('.overflow-y-auto'); if (el && el.scrollHeight > 0) { panelAtBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 5; } }"
	>
		<div class="flex-1 overflow-y-auto custom-scrollbar px-6 py-6 <?php echo ( $config['member']['is_guest_mode'] && ! $config['member']['is_guest'] ) ? 'pb-32' : ''; ?> text-rwp-widget-text"
			@scroll="panelAtTop = $el.scrollTop < 5; panelAtBottom = $el.scrollTop + $el.clientHeight >= $el.scrollHeight - 5"
		>

			<h2 class="<?php echo esc_attr( $sub_panel_heading_classes ); ?>"><?php esc_html_e( 'Ways to earn', 'rewardswp' ); ?></h2>

				<?php
				// Welcome banner - only show on first visit after joining
				// Use Alpine reactive data that gets updated when member joins
				$current_member    = get_current_member();
				$signup_points     = 0;
				$member_first_name = '';

				// Get initial values from PHP (fallback for page load)
				if ( $current_member && ! empty( $current_member->id ) ) {
					// Get member first name
					$member_first_name = $current_member->first_name ?: get_user_first_name( $current_member->user_id ?? 0 );

					// Get signup points from points entries
					$signup_points_entries = $this->points_service->get_points_entries(
						[
							'member_id'   => $current_member->id,
							'source_type' => 'signup',
						],
						1,
						1
					);

					if ( ! empty( $signup_points_entries ) ) {
						$signup_points = absint( $signup_points_entries[0]->points ?? 0 );
					}
				}
				?>
				<div
					x-show="showWelcomeBanner"
					role="status"
					class="mt-3 mb-2 flex items-center justify-between gap-2 rounded-lg bg-emerald-50 px-3 py-2 text-sm text-emerald-800"
					style="display: none;"
				>
					<?php $this->icon_helper->render_icon( 'check', 'size-4' ); ?>
					<div class="flex-1">
						<span x-show="welcomeBannerData.signupPoints > 0">
							<?php
							/* translators: %s: member first name - will be replaced by Alpine */
							echo esc_html__( 'Welcome, ', 'rewardswp' );
							?>
							<span x-text="welcomeBannerData.memberName || '<?php echo esc_js( $member_first_name ); ?>'"></span>!
						</span>
						<span x-show="welcomeBannerData.signupPoints === 0 || !welcomeBannerData.signupPoints">
							<?php
							/* translators: %s: member first name - will be replaced by Alpine */
							echo esc_html__( 'Welcome, ', 'rewardswp' );
							?>
							<span x-text="welcomeBannerData.memberName || '<?php echo esc_js( $member_first_name ); ?>'"></span>—<?php esc_html_e( 'you\'re in.', 'rewardswp' ); ?>
						</span>
					</div>
					<div class="ml-auto" x-show="welcomeBannerData.signupPoints > 0 || <?php echo $signup_points > 0 ? 'true' : 'false'; ?>">
						+<span x-text="(welcomeBannerData.signupPoints || <?php echo absint( $signup_points ); ?>).toLocaleString()"></span>
						<span x-text="(welcomeBannerData.signupPoints || <?php echo absint( $signup_points ); ?>) == 1 ? '<?php echo esc_js( $config['points']['currency']['singular'] ); ?>' : '<?php echo esc_js( $config['points']['currency']['plural'] ); ?>'"></span>
			</div>
		</div>

		<!-- Error Notification for Guest Upgrade -->
		<div x-show="errorMessage" class="mt-3 mb-2 rounded-lg bg-red-50 px-3 py-2 text-sm text-red-800" role="alert" style="display: none;">
			<?php $this->icon_helper->render_icon( 'x-mark', 'size-4 inline' ); ?>
			<span x-text="errorMessage"></span>
		</div>

		<div class="divide-y divide-gray-50">
				<?php foreach ( $earn_actions as $action ) : ?>
						<?php
						$is_signup_action  = isset( $action['id'] ) && $action['id'] === 'signup';
						$is_birthday_action = isset( $action['id'] ) && $action['id'] === 'birthday';
						?>
						<div
							class="grid grid-cols-[auto_1fr_auto] items-center gap-4 py-4"
							<?php if ( $is_birthday_action ) : ?>
							x-data="{
								birthdaySet: <?php echo ! empty( $action['birthday_date'] ) ? 'true' : 'false'; ?>,
								birthdayDisplay: '<?php
									if ( ! empty( $action['birthday_date'] ) ) {
										$bd_obj = \DateTime::createFromFormat( 'm-d', $action['birthday_date'] );
										echo $bd_obj ? esc_js( $bd_obj->format( $day_first ? 'j M' : 'M j' ) ) : esc_js( $action['birthday_date'] );
									}
								?>'
							}"
							x-on:birthday-saved.window="
								birthdaySet = true;
								birthdayDisplay = $event.detail.display;
							"
							<?php elseif ( $is_signup_action ) : ?>
							x-data="{
								isCompleted: <?php echo ! empty( $action['is_completed'] ) ? 'true' : 'false'; ?>,
								signupPoints: <?php echo absint( $action['points'] ?? 0 ); ?>,
								completedDate: '<?php echo esc_js( $action['completed_date'] ?? '' ); ?>'
							}"
							x-init="
								if (!isCompleted && $root.welcomeBannerData && !!$root.welcomeBannerData.signupPoints) {
									isCompleted = true;
									signupPoints = $root.welcomeBannerData.signupPoints;
									completedDate = '<?php echo esc_js( __( 'today', 'rewardswp' ) ); ?>';
								}
							"
							x-on:member-joined.window="
								if ($event.detail && !!$event.detail.signupPoints) {
									isCompleted = true;
									signupPoints = $event.detail.signupPoints;
									completedDate = '<?php echo esc_js( __( 'today', 'rewardswp' ) ); ?>';
								}
							"
							<?php endif; ?>
						>
							<div class="<?php echo esc_attr( $icon_classes ); ?>">
								<?php $this->icon_helper->render_icon( $action['icon'], 'size-8' ); ?>
							</div>
							<div class="min-w-0">
								<?php if ( ! empty( $action['title'] ) ) : ?>
									<div class="text-sm font-medium text-gray-900"><?php echo esc_html( $action['title'] ); ?></div>
								<?php endif; ?>
								<?php if ( $is_signup_action ) : ?>
									<!-- Show points when not completed, earned date when completed -->
									<div class="text-xs text-gray-500">
										<span x-show="!isCompleted" x-text="signupPoints.toLocaleString() + ' ' + (signupPoints == 1 ? '<?php echo esc_js( $config['points']['currency']['singular'] ); ?>' : '<?php echo esc_js( $config['points']['currency']['plural'] ); ?>')"></span>
										<span x-show="isCompleted" x-cloak><?php echo esc_html__( 'Earned', 'rewardswp' ); ?> <span x-text="completedDate"></span></span>
									</div>
								<?php elseif ( ! empty( $action['is_completed'] ) && ! empty( $action['completed_date'] ) ) : ?>
									<?php if ( $is_birthday_action ) : ?>
										<!-- Birthday completed this year: show default description (points value) -->
										<div class="text-xs text-gray-500"><?php echo esc_html( $action['description'] ); ?></div>
									<?php else : ?>
										<?php
										/*
										 * Show "Earned <when>" with the already-formatted date, which may be a
										 * relative phrase ("today", "3 days ago") or an absolute date for older
										 * entries. Matches the Alpine path above and avoids sniffing the localized
										 * date string for English markers, which broke phrasing on translated sites.
										 */
										?>
										<div class="text-xs text-gray-500">
											<?php
											printf(
												/* translators: %s: when the action was completed (relative time or absolute date) */
												esc_html__( 'Earned %s', 'rewardswp' ),
												esc_html( $action['completed_date'] )
											);
											?>
										</div>
									<?php endif; ?>
							<?php elseif ( $is_birthday_action ) : ?>
								<!-- Birthday date: reactive via Alpine local state -->
								<?php
								$bd_points   = absint( $action['points'] ?? 0 );
								$bd_currency = $bd_points === 1
									? $config['points']['currency']['singular']
									: $config['points']['currency']['plural'];
								?>
								<div x-show="birthdaySet" x-cloak class="text-xs text-gray-500">
									<?php echo esc_html( number_format_i18n( $bd_points ) . ' ' . $bd_currency ); ?> <?php esc_html_e( 'on', 'rewardswp' ); ?> <span x-text="birthdayDisplay"></span>
								</div>
								<div x-show="!birthdaySet" class="text-xs text-gray-500"><?php echo esc_html( $action['description'] ); ?></div>
							<?php else : ?>
								<div class="text-xs text-gray-500"><?php echo esc_html( $action['description'] ); ?></div>
							<?php endif; ?>
						</div>

						<!-- CTA Button Section -->
						<?php if ( $is_signup_action ) : ?>
							<?php
							// Check if a button will be rendered for this user.
							$has_signup_button = ! empty( $action['cta_is_guest'] ) || ! empty( $action['cta_onclick'] );
							?>
							<!-- Signup action: Always render completion indicator in DOM for dynamic updates -->
							<div class="justify-self-end" x-show="<?php echo $has_signup_button ? 'true' : 'isCompleted'; ?>" x-cloak>
								<?php if ( ! empty( $action['cta_is_guest'] ) ) : ?>
									<!-- Guest member: Show "Create" button to upgrade to full member -->
									<div x-show="!isCompleted">
										<button
											type="button"
											x-on:click="if (!isAdminPreview) { window.dispatchEvent(new CustomEvent('upgrade-guest-to-member', { detail: { memberId: $root.memberId } })); }"
											x-bind:disabled="($root.loading === true)"
											x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
											x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
											x-bind:class="{
												'opacity-50 cursor-not-allowed': ($root.loading === true),
												'hover:bg-rwp-button-hover': ($root.loading !== true),
												'!cursor-not-allowed': isAdminPreview
											}"
											class="<?php echo esc_attr( $this->get_button_classes( 'sm', true ) ); ?> min-h-[36px] min-w-[5rem]"
										>
											<span
												class="[grid-area:stack] transition-opacity duration-200"
												x-bind:class="{ 'invisible': ($root.loading === true), 'visible': ($root.loading !== true) }"
											><?php echo esc_html( $action['cta_label'] ); ?></span>
											<?php $this->icon_helper->render_button_spinner( '($root.loading === true)', 'size-4' ); ?>
										</button>
									</div>
								<?php elseif ( ! empty( $action['cta_onclick'] ) ) : ?>
									<!-- Non-member with onclick CTA (e.g., show register form) -->
									<button
										type="button"
										x-show="!isCompleted"
										x-on:click="if (!isAdminPreview) { <?php echo esc_attr( $action['cta_onclick'] ); ?> }"
										x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
										x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
										x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
										class="<?php echo esc_attr( $this->get_button_classes( 'sm', true ) ); ?> min-h-[36px] min-w-[5rem]"
									>
										<?php echo esc_html( $action['cta_label'] ); ?>
									</button>
								<?php endif; ?>
								<!-- Completion indicator - always in DOM, controlled by Alpine -->
								<div
									x-show="isCompleted"
									x-cloak
									class="text-emerald-600 font-semibold text-sm"
									x-bind:aria-label="'<?php echo esc_attr__( 'You earned', 'rewardswp' ); ?> ' + signupPoints.toLocaleString() + ' ' + (signupPoints == 1 ? '<?php echo esc_js( $config['points']['currency']['singular'] ); ?>' : '<?php echo esc_js( $config['points']['currency']['plural'] ); ?>') + ' <?php echo esc_attr__( 'for creating your account', 'rewardswp' ); ?>'"
								>
									+<span x-text="signupPoints.toLocaleString()"></span>
								</div>
							</div>
						<?php elseif ( $is_birthday_action && empty( $action['is_completed'] ) ) : ?>
							<!-- Birthday action: reactive CTA that switches from Add to Edit after save -->
							<div class="justify-self-end" x-show="!!memberId" x-cloak>
								<button
									type="button"
									x-on:click="if (!isAdminPreview) { navigateToPanel('birthday-picker') }"
									x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
									x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
									x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
									class="<?php echo esc_attr( $this->get_button_classes( 'sm', true ) ); ?> min-h-[36px] min-w-[5rem]"
									x-text="birthdaySet ? '<?php echo esc_js( __( 'Update', 'rewardswp' ) ); ?>' : '<?php echo esc_js( __( 'Set', 'rewardswp' ) ); ?>'"
								></button>
							</div>
						<?php elseif ( ! empty( $action['cta_label'] ) ) : ?>
							<!-- Non-signup actions with CTA -->
							<div class="justify-self-end" x-show="!!memberId" x-cloak>
								<?php if ( ! empty( $action['cta_is_guest'] ) ) : ?>
									<!-- Guest member CTA - triggers account creation -->
									<button
										type="button"
										x-on:click="if (!isAdminPreview) { window.dispatchEvent(new CustomEvent('upgrade-guest-to-member', { detail: { memberId: $root.memberId } })); }"
										x-bind:disabled="($root.loading === true)"
										x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
										x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
										x-bind:class="{
											'opacity-50 cursor-not-allowed': ($root.loading === true),
											'hover:bg-rwp-button-hover': ($root.loading !== true),
											'!cursor-not-allowed': isAdminPreview
										}"
										class="<?php echo esc_attr( $this->get_button_classes( 'sm', true ) ); ?> min-h-[36px] min-w-[5rem]"
									>
										<span
											class="[grid-area:stack] transition-opacity duration-200"
											x-bind:class="{ 'invisible': ($root.loading === true), 'visible': ($root.loading !== true) }"
										><?php echo esc_html( $action['cta_label'] ); ?></span>
										<?php $this->icon_helper->render_button_spinner( '($root.loading === true)', 'size-4' ); ?>
									</button>
								<?php elseif ( ! empty( $action['cta_onclick'] ) ) : ?>
									<!-- CTA with onclick handler (no URL change) -->
									<button
										type="button"
										x-on:click="if (!isAdminPreview) { <?php echo esc_attr( $action['cta_onclick'] ); ?> }"
										x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
										x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
										x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
										class="<?php echo esc_attr( $this->get_button_classes( 'sm', true ) ); ?> min-h-[36px] min-w-[5rem]"
									>
										<?php echo esc_html( $action['cta_label'] ); ?>
									</button>
								<?php elseif ( ! empty( $action['cta_url'] ) ) : ?>
									<!-- Regular CTA with URL - visible only to members -->
									<a
										x-show="!!memberId"
										x-cloak
										x-bind:href="isAdminPreview ? 'javascript:void(0)' : '<?php echo esc_url( $action['cta_url'] ); ?>'"
										x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
										x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
										x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
										class="<?php echo esc_attr( $this->get_button_classes( 'sm', true ) ); ?> min-h-[36px] min-w-[5rem]"
									>
										<?php echo esc_html( $action['cta_label'] ); ?>
									</a>
								<?php endif; ?>
							</div>
						<?php
						elseif ( ! empty( $action['is_completed'] ) && ! empty( $action['points_earned'] ) && ! $is_birthday_action ) :
								$points_label = $action['points_earned'] == 1 ? $config['points']['currency']['singular'] : $config['points']['currency']['plural'];
								$aria_label   = sprintf(
									/* translators: 1: points earned, 2: points label (e.g. "Points"), 3: action title (e.g. "writing a product review") */
									__( 'You earned %1$s %2$s for %3$s', 'rewardswp' ),
									number_format( $action['points_earned'] ),
									$points_label,
									strtolower( $action['title'] )
								);
						?>
							<div
								class="justify-self-end text-emerald-600 font-semibold text-sm"
								aria-label="<?php echo esc_attr( $aria_label ); ?>"
							>
								+<?php echo esc_html( number_format( $action['points_earned'] ) ); ?>
							</div>
						<?php endif; ?>
					</div>
			<?php endforeach; ?>
			</div>

		</div>

		<!-- Top scroll fade -->
		<div x-show="!panelAtTop"
			class="pointer-events-none absolute top-0 left-0 h-8 z-10"
			style="right: var(--rwp-scrollbar-width, 8px); background: linear-gradient(to bottom, var(--color-rwp-widget-bg), transparent);"></div>
		<!-- Bottom scroll fade — hidden when guest join section is visible -->
		<div x-show="!panelAtBottom && memberId !== 0"
			class="pointer-events-none absolute bottom-0 left-0 h-12 z-10"
			style="right: var(--rwp-scrollbar-width, 8px); background: linear-gradient(to top, var(--color-rwp-widget-bg), transparent);"></div>

		<?php if ( $config['member']['is_guest_mode'] && ! $config['member']['is_guest'] ) : ?>
		<div x-show="memberId === 0">
		<!-- Fade Overlay -->
		<div
			class="absolute bottom-0 left-0 right-0 h-24 pointer-events-none"
			style="background: linear-gradient(to top, var(--color-rwp-widget-bg), var(--color-rwp-widget-bg) 20%, transparent);"
			x-data="{ hideJoinSection: false }"
			x-on:member-joined.window="hideJoinSection = true"
			x-show="!hideJoinSection"
		></div>

		<!-- Sticky Join Section -->
		<div
			class="absolute bottom-0 left-0 right-0 bg-white px-6 py-4 border-t border-gray-100"
			x-data="{ hideJoinSection: false, isLoggedIn: <?php echo is_user_logged_in() ? 'true' : 'false'; ?> }"
			x-on:member-joined.window="hideJoinSection = true"
			x-show="!hideJoinSection"
		>
	<div>
	<button
		type="button"
		x-on:click="isLoggedIn ? window.dispatchEvent(new CustomEvent('create-member-direct')) : window.dispatchEvent(new CustomEvent('show-register-form'))"
		class="<?php echo esc_attr( $this->get_button_classes( 'lg' ) ); ?>"
		style="--tw-ring-color: var(--rwp-accent);"
	>
			<?php esc_html_e( 'Join now', 'rewardswp' ); ?>
	</button>
		</div>
				<div x-show="!isLoggedIn" x-cloak class="mt-3 text-center text-rwp-widget-text">
					<p class="text-sm">
						<?php esc_html_e( 'Already have an account?', 'rewardswp' ); ?>
						<button
							x-on:click="navigateToPanel('main', { resetScroll: true }); $dispatch('show-login-form');"
							class="font-medium underline text-rwp-accent cursor-pointer"
						><?php esc_html_e( 'Sign in', 'rewardswp' ); ?></button>
					</p>
				</div>
			</div>
		</div>
			<?php endif; ?>
		</div>

		<?php
		/*
		* "Birthday Picker" sub-panel.
		* Inline panel for setting or editing member birthday.
		*/
		?>
	<div
		x-show="currentPanel === 'birthday-picker'"
		x-bind:class="'relative h-full flex flex-col ' + (currentPanel === 'birthday-picker' ? (navigationDirection === 'initial' ? 'rwp-panel-fade-up' : (navigationDirection === 'forward' ? 'rwp-panel-slide-forward' : (navigationDirection === 'back' ? 'rwp-panel-slide-back' : ''))) : '')"
	>
		<div class="flex-1 overflow-y-auto custom-scrollbar px-6 py-6 text-rwp-widget-text">

			<!-- Success State -->
			<div x-show="birthdaySuccess" x-transition class="text-center">
				<div class="flex justify-center mb-3">
					<?php $this->icon_helper->render_icon( 'check-circle-alt', 'size-10 text-green-600', 'bold' ); ?>
				</div>
				<h3 class="text-lg font-semibold text-rwp-widget-heading mb-1"><?php esc_html_e( 'Birthday set!', 'rewardswp' ); ?></h3>
				<template x-if="birthdayRewardDate">
					<p class="text-sm text-gray-500 mb-6"><?php esc_html_e( 'You\'ll earn', 'rewardswp' ); ?> <span x-text="birthdayPoints + ' ' + currencyPlural"></span> <?php esc_html_e( 'on', 'rewardswp' ); ?> <span x-text="birthdayRewardDate"></span></p>
				</template>
				<template x-if="!birthdayRewardDate">
					<p class="text-sm text-gray-500 mb-6"><?php esc_html_e( 'You\'ll earn bonus points on your special day!', 'rewardswp' ); ?></p>
				</template>
				<button
					type="button"
					x-on:click="resetBirthdayPicker(); navigateToPanel('ways-to-earn')"
					class="<?php echo esc_attr( $this->get_button_classes( 'lg' ) ); ?>"
				>
					<?php esc_html_e( 'Keep earning', 'rewardswp' ); ?>
				</button>
			</div>

			<!-- Form State -->
			<div x-show="!birthdaySuccess">
				<div class="text-center mb-4">
					<div class="flex justify-center mx-auto mb-3">
						<?php $this->icon_helper->render_icon( 'earn-birthday', 'size-10 text-rwp-widget-icon-text' ); ?>
					</div>
					<h3 class="<?php echo esc_attr( $sub_panel_heading_classes ); ?> text-center" x-text="birthdayIsEditing ? '<?php echo esc_js( __( 'Update your birthday', 'rewardswp' ) ); ?>' : '<?php echo esc_js( __( 'When is your birthday?', 'rewardswp' ) ); ?>'"></h3>
					<p class="text-sm text-gray-500">
						<template x-if="birthdayRewardPreview">
							<span><?php esc_html_e( 'You\'ll earn', 'rewardswp' ); ?> <span x-text="birthdayPoints + ' ' + currencyPlural"></span> <?php esc_html_e( 'on', 'rewardswp' ); ?> <span x-text="birthdayRewardPreview.rewardDate"></span></span>
						</template>
						<template x-if="!birthdayRewardPreview">
							<span><?php esc_html_e( 'You\'ll earn', 'rewardswp' ); ?> <span x-text="birthdayPoints + ' ' + currencyPlural"></span> <?php esc_html_e( 'on your special day!', 'rewardswp' ); ?></span>
						</template>
					</p>
				</div>

				<?php
				// Reusable select classes.
				$select_classes = 'col-start-1 row-start-1 w-full appearance-none bg-white text-base px-3 py-3 pr-8 rounded-lg outline-1 -outline-offset-1 outline-gray-200 focus:outline-2 focus:-outline-offset-2 ' . $this->focus_classes;
				$chevron_svg    = '<svg class="pointer-events-none col-start-1 row-start-1 mr-3 size-4 self-center justify-self-end text-gray-500" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /></svg>';
				?>
				<form x-on:submit.prevent="submitBirthday()">
					<div class="grid gap-3 mb-4 <?php echo $day_first ? 'grid-cols-[6rem_1fr]' : 'grid-cols-[1fr_6rem]'; ?>">
						<?php if ( $day_first ) : ?>
						<div class="grid grid-cols-1">
							<label for="rwp-birthday-day" class="sr-only"><?php esc_html_e( 'Day', 'rewardswp' ); ?></label>
							<select
								id="rwp-birthday-day"
								x-model="birthdayDay"
								x-ref="birthdayDaySelect"
								class="<?php echo esc_attr( $select_classes ); ?>"
								required
							>
								<option value=""><?php esc_html_e( 'Day', 'rewardswp' ); ?></option>
								<template x-for="d in birthdayDaysInMonth" :key="d">
									<option :value="d" :selected="String(d) === birthdayDay" x-text="d"></option>
								</template>
							</select>
							<?php echo $chevron_svg; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
						</div>
						<?php endif; ?>
						<div class="grid grid-cols-1">
							<label for="rwp-birthday-month" class="sr-only"><?php esc_html_e( 'Month', 'rewardswp' ); ?></label>
							<select
								id="rwp-birthday-month"
								x-model="birthdayMonth"
								x-on:change="if (!birthdayDay) { setTimeout(() => $refs.birthdayDaySelect?.focus(), 300) }"
								class="<?php echo esc_attr( $select_classes ); ?>"
								required
							>
								<option value=""><?php esc_html_e( 'Month', 'rewardswp' ); ?></option>
								<option value="1"><?php esc_html_e( 'January', 'rewardswp' ); ?></option>
								<option value="2"><?php esc_html_e( 'February', 'rewardswp' ); ?></option>
								<option value="3"><?php esc_html_e( 'March', 'rewardswp' ); ?></option>
								<option value="4"><?php esc_html_e( 'April', 'rewardswp' ); ?></option>
								<option value="5"><?php esc_html_e( 'May', 'rewardswp' ); ?></option>
								<option value="6"><?php esc_html_e( 'June', 'rewardswp' ); ?></option>
								<option value="7"><?php esc_html_e( 'July', 'rewardswp' ); ?></option>
								<option value="8"><?php esc_html_e( 'August', 'rewardswp' ); ?></option>
								<option value="9"><?php esc_html_e( 'September', 'rewardswp' ); ?></option>
								<option value="10"><?php esc_html_e( 'October', 'rewardswp' ); ?></option>
								<option value="11"><?php esc_html_e( 'November', 'rewardswp' ); ?></option>
								<option value="12"><?php esc_html_e( 'December', 'rewardswp' ); ?></option>
							</select>
							<?php echo $chevron_svg; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
						</div>
						<?php if ( ! $day_first ) : ?>
						<div class="grid grid-cols-1">
							<label for="rwp-birthday-day" class="sr-only"><?php esc_html_e( 'Day', 'rewardswp' ); ?></label>
							<select
								id="rwp-birthday-day"
								x-model="birthdayDay"
								x-ref="birthdayDaySelect"
								class="<?php echo esc_attr( $select_classes ); ?>"
								required
							>
								<option value=""><?php esc_html_e( 'Day', 'rewardswp' ); ?></option>
								<template x-for="d in birthdayDaysInMonth" :key="d">
									<option :value="d" :selected="String(d) === birthdayDay" x-text="d"></option>
								</template>
							</select>
							<?php echo $chevron_svg; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
						</div>
						<?php endif; ?>
					</div>


					<!-- Error Message (server validation errors only) -->
					<div x-show="birthdayError" x-transition class="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg">
						<p class="text-sm text-red-700" x-text="birthdayError"></p>
					</div>

					<button
						type="submit"
						x-show="!birthdayIsEditing || birthdayChanged"
						x-bind:class="birthdaySubmitting ? 'pointer-events-none' : ''"
						class="<?php echo esc_attr( $this->get_button_classes( 'lg', true ) ); ?>"
					>
						<span
							class="[grid-area:stack] transition-opacity duration-200"
							x-bind:class="{ 'invisible': birthdaySubmitting, 'visible': !birthdaySubmitting }"
							x-text="birthdayIsEditing ? '<?php echo esc_js( __( 'Update birthday', 'rewardswp' ) ); ?>' : '<?php echo esc_js( __( 'Set my birthday', 'rewardswp' ) ); ?>'"
						></span>
						<?php $this->icon_helper->render_button_spinner( 'birthdaySubmitting' ); ?>
					</button>
				</form>
			</div>

		</div>
	</div>

		<?php
		/*
		* "Ways to redeem" sub-panel.
		* Shows "Ways to redeem" list for logged-in members.
		*/
		?>
	<div
		x-show="currentPanel === 'ways-to-redeem'"
		x-bind:class="'ways-to-redeem-panel relative h-full flex flex-col overflow-hidden ' + (currentPanel === 'ways-to-redeem' ? (navigationDirection === 'initial' ? 'rwp-panel-fade-up' : (navigationDirection === 'forward' ? 'rwp-panel-slide-forward' : (navigationDirection === 'back' ? 'rwp-panel-slide-back' : ''))) : '')"
		x-data="{ panelAtTop: true, panelAtBottom: false }"
		x-effect="if (currentPanel === 'ways-to-redeem') { const el = $el.querySelector('.overflow-y-auto'); if (el && el.scrollHeight > 0) { panelAtBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 5; } }"
	>
		<div class="flex-1 overflow-y-auto custom-scrollbar px-6 py-6 <?php echo ( $config['member']['is_guest_mode'] && ! $config['member']['is_guest'] ) ? 'pb-32' : ''; ?> text-rwp-widget-text"
			@scroll="panelAtTop = $el.scrollTop < 5; panelAtBottom = $el.scrollTop + $el.clientHeight >= $el.scrollHeight - 5"
		>

			<h2 class="<?php echo esc_attr( $sub_panel_heading_classes ); ?>"><?php esc_html_e( 'Ways to redeem', 'rewardswp' ); ?></h2>

			<?php if ( empty( $redeem_actions ) ) : ?>
				<div class="text-center py-8">
					<div class="text-gray-400 mb-2">
						<?php $this->icon_helper->render_icon( 'gift', 'size-12 mx-auto' ); ?>
					</div>
					<p class="text-base text-gray-500">
						<?php esc_html_e( 'No rewards available to redeem at this time.', 'rewardswp' ); ?>
					</p>
					<p class="text-sm text-gray-400 mt-1">
						<?php esc_html_e( 'Check back later for new ways to use your points.', 'rewardswp' ); ?>
					</p>
				</div>
			<?php else : ?>
			<div class="divide-y divide-gray-50" x-data="redeemManager">
				<?php foreach ( $redeem_actions as $action ) : ?>
						<?php $action_key = $action['type'] . '_' . $action['id']; ?>
						<div class="grid grid-cols-[auto_1fr_auto] items-center gap-4 py-4" data-action-id="<?php echo esc_attr( $action['id'] ); ?>">
								<div class="<?php echo esc_attr( $icon_classes ); ?>">
									<?php $this->icon_helper->render_icon( $action['icon'], 'size-8' ); ?>
								</div>
							<div class="min-w-0">
								<div class="text-sm font-medium text-gray-900"><?php echo esc_html( $action['reward_label'] ); ?></div>
								<div class="text-xs text-gray-500 mt-1"><?php echo esc_html( number_format( $action['required_points'] ) . ' ' . ( $action['required_points'] === 1 ? $config['points']['currency']['singular'] : $config['points']['currency']['plural'] ) ); ?></div>

									<!-- Success Message -->
									<div x-show="redeemStates['<?php echo esc_js( $action_key ); ?>']?.success" x-transition class="text-sm text-green-600 mt-1">
										<div class="flex items-center gap-2">
											<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
												<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
											</svg>
											<?php esc_html_e( 'Coupon generated and applied to cart!', 'rewardswp' ); ?>
										</div>
									</div>

									<!-- Error Message -->
									<div x-show="redeemStates['<?php echo esc_js( $action_key ); ?>']?.error" x-transition class="text-sm text-red-600 mt-1">
										<div class="flex items-center gap-2">

											<span x-text="redeemStates['<?php echo esc_js( $action_key ); ?>']?.errorMessage"></span>
										</div>
									</div>
								</div>

					<?php
					/**
					 * "Redeem" button for logged-in members.
					 * Demo/admin preview: dispatches mock show-redeem-success event.
					 * Guest members: navigates to create-account-prompt panel.
					 * Regular members: calls redeemPointsForCoupon() via AJAX.
					 */
					$is_guest_member   = is_current_member_a_guest();
					$is_mock_redeem    = $config['is_demo'] || $config['is_admin_preview'];
					?>
				<div class="justify-self-end" x-show="memberId" x-cloak <?php echo $is_mock_redeem ? 'x-data="{ mockRedeeming: false }"' : ''; ?>>
					<button
						<?php
						if ( $is_mock_redeem ) :
							// Generate icon HTML for the reward.
							ob_start();
							$this->icon_helper->render_icon( $action['icon'] ?: 'gift', 'size-10' );
							$mock_icon_html = ob_get_clean();

							// Generate a coupon code: use action's code if set, otherwise generate from hash.
							$mock_coupon_code = ! empty( $action['coupon_code'] )
								? $action['coupon_code']
								: strtolower( str_replace( ' ', '-', $config['points']['currency']['plural'] ) ) . '-' . substr( md5( $action['type'] . '_' . $action['id'] ), 0, 8 );

							// Generate coupon terms per action type.
							$mock_minimum_order_value = floatval( $action['action_config']['minimum_order_value'] ?? 0 );

							if ( 'free_shipping' === $action['type'] ) {
								$mock_coupon_terms = __( 'Free shipping', 'rewardswp' ) . ' · ' . __( 'No expiry', 'rewardswp' );
							} elseif ( 'free_product' === $action['type'] ) {
								if ( $mock_minimum_order_value > 0 ) {
									$mock_coupon_terms = sprintf(
										/* translators: %s: minimum order amount with currency symbol */
										__( '%s min spend', 'rewardswp' ),
										\Awesomemotive\Rewardswp\Helpers\format_currency( $mock_minimum_order_value )
									) . ' · ' . __( 'No expiry', 'rewardswp' );
								} else {
									$mock_coupon_terms = __( 'No expiry', 'rewardswp' );
								}
							} else {
								$mock_coupon_terms = __( 'Applies to entire order', 'rewardswp' ) . ' · ' . __( 'No expiry', 'rewardswp' );
							}

							// Build mock event data for show-redeem-success.
							$mock_event_data = [
								'couponCode'         => $mock_coupon_code,
								'pointsSpent'        => absint( $action['required_points'] ),
								'iconHtml'           => $mock_icon_html,
								'title'              => $action['reward_label'] ?: ( $action['cost'] ?? '' ),
								'pointsLabel'        => absint( $action['required_points'] ) === 1
									? $config['points']['currency']['singular']
									: $config['points']['currency']['plural'],
								'couponTerms'        => $mock_coupon_terms,
								'expiryText'         => __( 'No expiry', 'rewardswp' ),
								'rewardType'         => $action['type'],
								'isApplied'          => false,
								'rewardId'           => 0,
								'freeProductId'      => null,
								'minimumOrderAmount' => $mock_minimum_order_value,
								'freeProductPrice'   => 0,
								'actionId'           => $action['id'] ?? '',
							];
							$mock_event_json = array_to_json( $mock_event_data );
							?>
							x-on:click="
								if (!mockRedeeming && (userData?.points ?? 0) >= <?php echo absint( $action['required_points'] ); ?>) {
									mockRedeeming = true;
									setTimeout(() => {
										const eventData = <?php echo $mock_event_json; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>;
										eventData.newBalance = (userData?.points ?? 0) - <?php echo absint( $action['required_points'] ); ?>;
										redeemSuccessData.redeemedLabel = '<?php echo esc_js( $action['reward_label'] ?: ( $action['cost'] ?? '' ) ); ?>';
										window.dispatchEvent(new CustomEvent('show-redeem-success', { detail: eventData }));
										mockRedeeming = false;
									}, 600);
								}
							"
							x-bind:disabled="mockRedeeming || (userData?.points ?? 0) < <?php echo absint( $action['required_points'] ); ?>"
						<?php elseif ( $is_guest_member ) :
							// Generate icon HTML for the reward
							ob_start();
							$this->icon_helper->render_icon( $action['icon'] ?: 'gift', 'size-10' );
							$icon_html = ob_get_clean();
							// Create reward data array and use array_to_json for proper escaping
							$reward_data = [
								'title'    => $action['reward_label'] ?: $action['cost'],
								'iconHtml' => $icon_html,
								'points'   => absint( $action['required_points'] ),
							];
							$reward_json = array_to_json( $reward_data );
							?>
							x-on:click="if (!isAdminPreview && (userData?.points ?? 0) >= <?php echo absint( $action['required_points'] ); ?>) { window.dispatchEvent(new CustomEvent('navigate-to-create-account-prompt', { detail: { reward: <?php echo $reward_json; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> } })); }"
							x-bind:disabled="(userData?.points ?? 0) < <?php echo absint( $action['required_points'] ); ?>"
						<?php else : ?>
							x-on:click="if (!isAdminPreview) { redeemSuccessData.redeemedLabel = '<?php echo esc_js( $action['reward_label'] ?: $action['cost'] ); ?>'; redeemPointsForCoupon('<?php echo esc_js( $action['type'] ); ?>', '<?php echo esc_js( $action['id'] ); ?>', '<?php echo esc_js( $action_key ); ?>'); }"
							x-bind:disabled="redeemStates['<?php echo esc_js( $action_key ); ?>']?.redeeming || (userData?.points ?? 0) < <?php echo absint( $action['required_points'] ); ?>"
						<?php endif; ?>
						<?php if ( ! $is_mock_redeem ) : ?>
						x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
						x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
						x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
						<?php endif; ?>
						class="<?php echo esc_attr( $this->get_button_classes( 'sm', true ) ); ?> min-h-[36px] min-w-[5rem]"
					>
							<span
								class="[grid-area:stack] transition-opacity duration-200"
								<?php if ( $is_mock_redeem ) : ?>
								x-bind:class="{ 'invisible': mockRedeeming, 'visible': !mockRedeeming }"
							<?php elseif ( ! $is_guest_member ) : ?>
								x-bind:class="{ 'invisible': redeemStates['<?php echo esc_js( $action_key ); ?>']?.redeeming, 'visible': !redeemStates['<?php echo esc_js( $action_key ); ?>']?.redeeming }"
								<?php endif; ?>
							><?php esc_html_e( 'Redeem', 'rewardswp' ); ?></span>
							<?php
							if ( $is_mock_redeem ) {
								$this->icon_helper->render_button_spinner( 'mockRedeeming', 'size-4' );
							} elseif ( ! $is_guest_member ) {
								$this->icon_helper->render_button_spinner(
									sprintf( "redeemStates['%s']?.redeeming", esc_js( $action_key ) ),
									'size-4'
								);
							}
							?>
						</button>
					</div>

					</div>
					<?php endforeach; ?>
				</div>
			<?php endif; ?>
		</div>

		<!-- Top scroll fade -->
		<div x-show="!panelAtTop"
			class="pointer-events-none absolute top-0 left-0 h-8 z-10"
			style="right: var(--rwp-scrollbar-width, 8px); background: linear-gradient(to bottom, var(--color-rwp-widget-bg), transparent);"></div>
		<!-- Bottom scroll fade — hidden when guest join section is visible -->
		<div x-show="!panelAtBottom && memberId !== 0"
			class="pointer-events-none absolute bottom-0 left-0 h-12 z-10"
			style="right: var(--rwp-scrollbar-width, 8px); background: linear-gradient(to top, var(--color-rwp-widget-bg), transparent);"></div>

		<?php if ( $config['member']['is_guest_mode'] && ! $config['member']['is_guest'] ) : ?>
		<div x-show="memberId === 0">
		<!-- Fade Overlay -->
		<div
			class="absolute bottom-0 left-0 right-0 h-24 pointer-events-none"
			style="background: linear-gradient(to top, var(--color-rwp-widget-bg), var(--color-rwp-widget-bg) 20%, transparent);"
			x-data="{ hideJoinSection: false }"
			x-on:member-joined.window="hideJoinSection = true"
			x-show="!hideJoinSection"
		></div>

		<!-- Sticky Join Section -->
		<div
			class="absolute bottom-0 left-0 right-0 bg-white px-6 py-4 border-t border-gray-100"
			x-data="{ hideJoinSection: false, isLoggedIn: <?php echo is_user_logged_in() ? 'true' : 'false'; ?> }"
			x-on:member-joined.window="hideJoinSection = true"
			x-show="!hideJoinSection"
		>
	<div>
	<button
		type="button"
		x-on:click="isLoggedIn ? window.dispatchEvent(new CustomEvent('create-member-direct')) : window.dispatchEvent(new CustomEvent('show-register-form'))"
		class="<?php echo esc_attr( $this->get_button_classes( 'lg' ) ); ?>"
	>
			<?php esc_html_e( 'Join now', 'rewardswp' ); ?>
		</button>
		</div>
				<div x-show="!isLoggedIn" x-cloak class="mt-3 text-center text-rwp-widget-text">
					<p class="text-sm text-gray-600">
						<?php esc_html_e( 'Already have an account?', 'rewardswp' ); ?>
						<button
							x-on:click="navigateToPanel('main', { resetScroll: true }); $dispatch('show-login-form');"
							class="font-medium underline text-rwp-accent cursor-pointer"
						><?php esc_html_e( 'Sign in', 'rewardswp' ); ?></button>
					</p>
				</div>
			</div>
		</div>
			<?php endif; ?>
		</div>



		<?php
		/*
		* Activity Panel
		*
		* Shows tabbed activity logs (points, referrals, rewards) for logged-in members.
		* Uses properties from the parent rewardsPanelWidget component.
		* @since 1.2.0
		*/
		?>
		<div
			x-show="currentPanel === 'activity'"
			x-init="$watch('currentPanel', value => {
				if (value === 'activity') {
					if (activityItems.length === 0 || needsActivityRefresh) {
						if (needsActivityRefresh && activityTab === 'points') {
							activityItems = [];
							activityPage = 1;
							activityHasMore = false;
							activityCache = {};
						}
						loadActivity();
						needsActivityRefresh = false;
					}
				}
			})"
			x-bind:class="'relative h-full flex flex-col ' + (currentPanel === 'activity' ? (navigationDirection === 'initial' ? 'rwp-panel-fade-up' : (navigationDirection === 'forward' ? 'rwp-panel-slide-forward' : (navigationDirection === 'back' ? 'rwp-panel-slide-back' : ''))) : '')"
		>
			<div class="shrink-0 px-6 pt-6 text-rwp-widget-text">
				<h2 class="<?php echo esc_attr( $sub_panel_heading_classes ); ?>"><?php esc_html_e( 'Activity', 'rewardswp' ); ?></h2>
				<!-- Segmented Control (hidden when only one tab is available) -->
				<div class="relative w-full mb-3" x-show="(pointsEnabled || (!!memberId && userData.points > 0)) && referralsEnabled">
					<div class="relative inline-grid items-center justify-center w-full h-10 grid-cols-2 p-1 text-gray-500 bg-gray-100 rounded-xl select-none" role="tablist">
						<button
							x-ref="activityPointsBtn"
							x-show="pointsEnabled || (!!memberId && userData.points > 0)"
							x-on:click="switchActivityTab('points')"
							type="button"
							class="relative z-20 inline-flex items-center justify-center w-full h-8 px-3 text-sm font-medium transition-all rounded-md cursor-pointer whitespace-nowrap <?php echo esc_attr( $this->focus_classes ); ?>"
							x-bind:class="activityTab === 'points' ? 'text-gray-900' : 'text-gray-500'"
							role="tab"
							x-bind:aria-selected="activityTab === 'points'"
						>
							<?php esc_html_e( 'Points', 'rewardswp' ); ?>
						</button>
						<button
							x-ref="activityReferralsBtn"
							x-show="referralsEnabled"
							x-on:click="switchActivityTab('referrals')"
							type="button"
							class="relative z-20 inline-flex items-center justify-center w-full h-8 px-3 text-sm font-medium transition-all rounded-md cursor-pointer whitespace-nowrap <?php echo esc_attr( $this->focus_classes ); ?>"
							x-bind:class="activityTab === 'referrals' ? 'text-gray-900' : 'text-gray-500'"
							role="tab"
							x-bind:aria-selected="activityTab === 'referrals'"
						>
							<?php esc_html_e( 'Referrals', 'rewardswp' ); ?>
						</button>
						<div class="absolute z-10 bg-white rounded-lg shadow-sm left-1 top-1 transition-transform duration-350 ease-[cubic-bezier(0.22,1.3,0.36,1)]" x-bind:style="'width: calc(50% - 4px); height: 32px; transform: translateX(' + ({'points': 0, 'referrals': 100}[activityTab] ?? 0) + '%)'"></div>
					</div>
				</div>
			</div>
			<div class="relative flex-1 min-h-0">
			<div x-ref="activityScroll" x-on:scroll.passive="syncActivityFade()" class="h-full overflow-y-auto custom-scrollbar px-6 pt-1 pb-6 text-rwp-widget-text">

				<!-- Loading State -->
				<div x-show="activityLoading" class="flex items-center justify-center py-8">
					<div class="animate-spin size-6 border-2 border-gray-200 border-t-gray-600 rounded-full"></div>
				</div>

				<!-- Activity List -->
				<div x-show="!activityLoading">
					<!-- Empty State - Points Tab -->
					<template x-if="activityItems.length === 0 && !activityLoading && activityTab === 'points'">
						<div class="text-center py-8">
							<div class="flex justify-center mb-3">
								<?php $this->icon_helper->render_icon( 'points-activity', 'size-10 text-gray-400' ); ?>
							</div>
							<h3 class="text-lg font-medium text-gray-900 mb-1" x-text="'<?php echo esc_js( __( 'No', 'rewardswp' ) ); ?> ' + currencyPlural + ' <?php echo esc_js( __( 'yet', 'rewardswp' ) ); ?>'"></h3>
							<p class="text-sm text-gray-500 mb-4" x-text="'<?php echo esc_js( __( 'See all the ways you can earn', 'rewardswp' ) ); ?> ' + currencyPlural"></p>
							<button
								x-on:click="navigateToPanel('ways-to-earn')"
								class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-rwp-brand hover:bg-rwp-brand-hover cursor-pointer transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rwp-brand <?php echo esc_attr( $this->focus_classes ); ?>"
							>
								<span x-text="'<?php echo esc_js( __( 'Earn', 'rewardswp' ) ); ?> ' + currencyPlural"></span>
							</button>
						</div>
					</template>

					<!-- Empty State - Referrals Tab -->
					<template x-if="activityItems.length === 0 && !activityLoading && activityTab === 'referrals'">
						<div class="text-center py-8">
							<div class="flex justify-center mb-3">
								<?php $this->icon_helper->render_icon( 'earn-referral', 'size-10 text-gray-400' ); ?>
							</div>
							<h3 class="text-lg font-medium text-gray-900 mb-1"><?php esc_html_e( 'No referrals yet', 'rewardswp' ); ?></h3>
							<p class="text-sm text-gray-500 mb-4"><?php esc_html_e( 'When friends shop using your referral link, you\'ll see their activity here', 'rewardswp' ); ?></p>
							<button
								x-on:click="navigateToPanel('main')"
								class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-rwp-brand hover:bg-rwp-brand-hover cursor-pointer transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rwp-brand <?php echo esc_attr( $this->focus_classes ); ?>"
							>
								<?php esc_html_e( 'Invite friends', 'rewardswp' ); ?>
							</button>
						</div>
					</template>

					<!-- Referral Summary Row (shown above activity items when referrals tab has data) -->
					<div x-show="activityTab === 'referrals' && activityItems.length > 0 && activityStatusCounts" class="flex items-center gap-2 text-sm text-gray-500 pb-2 mb-1">
						<span x-text="(activityStatusCounts ? activityStatusCounts.completed : 0) + ' <?php echo esc_js( __( 'completed', 'rewardswp' ) ); ?>'"></span>
						<span x-show="activityStatusCounts && !!activityStatusCounts.pending">&middot;</span>
						<span x-show="activityStatusCounts && !!activityStatusCounts.pending" x-text="(activityStatusCounts ? activityStatusCounts.pending : 0) + ' <?php echo esc_js( __( 'pending', 'rewardswp' ) ); ?>'"></span>
					</div>

					<!-- Activity Items -->
					<div class="divide-y divide-gray-100">
						<template x-for="item in activityItems" x-bind:key="item.id">
							<div class="py-3">
								<div class="flex items-start justify-between gap-4">
									<!-- Left column: Description + Date -->
									<div class="flex-1 min-w-0">
										<!-- Referrals tab: Separate friend_name (semi-bold) and action_text (normal) -->
										<template x-if="activityTab === 'referrals' && item.friend_name">
											<p class="text-sm text-gray-900">
												<span class="font-semibold" x-text="item.friend_name"></span>
												<template x-if="item.action_text">
													<span class="font-normal" x-text="' ' + item.action_text"></span>
												</template>
											</p>
										</template>
										<!-- Points/other tabs: Primary line: Reward/action name -->
										<template x-if="activityTab !== 'referrals' || !item.friend_name">
											<p class="text-sm text-gray-900" x-text="item.target || item.action"></p>
										</template>
										<!-- Small text for order_id (refunds) -->
										<template x-if="item.order_id">
											<p class="text-xs text-gray-400 mt-0.5" x-text="'#' + item.order_id"></p>
										</template>
										<!-- Small text for product_name (product reviews) -->
										<template x-if="item.product_name">
											<p class="text-xs text-gray-400 mt-0.5" x-text="item.product_name"></p>
										</template>
										<!-- Secondary line: Date (all tabs) -->
										<p class="text-xs text-gray-500 mt-0.5" x-text="item.date"></p>
									</div>
									<!-- Right column: Points value + currency OR Status pill -->
									<div class="flex-shrink-0 text-right">
										<!-- Points tab: Point value (with inline pending badge) + currency name below -->
										<template x-if="activityTab === 'points' && item.points !== undefined">
											<div>
												<div class="flex items-center justify-end gap-1.5">
													<p x-bind:class="item.source_type === 'points_expiration' ? 'text-gray-500' : (item.is_deduction ? 'text-gray-700' : (item.status === 'pending' ? 'text-gray-500' : 'text-emerald-600'))"
														class="text-sm font-semibold"
														x-text="(item.is_deduction ? '-' : '+') + Math.abs(item.points).toLocaleString()">
													</p>
													<span x-show="item.status === 'pending'"
														class="text-[11px] text-gray-500">
														<?php esc_html_e( 'Pending', 'rewardswp' ); ?>
													</span>
												</div>
												<p class="text-xs text-gray-500 mt-0.5 text-right"
													x-text="getCurrencyName(Math.abs(item.points))">
												</p>
											</div>
										</template>
										<!-- Referrals/rewards tab: Status badge -->
										<template x-if="activityTab !== 'points' && item.status">
											<span
												x-bind:class="{
													'bg-emerald-50 text-emerald-700 border border-emerald-100': item.status === 'completed' || item.status === 'redeemed',
													'bg-amber-50 text-amber-700 border border-amber-100': item.status === 'pending',
													'bg-gray-50 text-gray-700 border border-gray-200': item.status === 'issued',
													'bg-red-50 text-red-700 border border-red-100': item.status === 'blocked' || item.status === 'rejected',
													'bg-gray-100 text-gray-600 border border-gray-200': item.status === 'canceled'
												}"
												class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium mt-0.5"
												x-text="({
													completed: '<?php echo esc_js( __( 'Completed', 'rewardswp' ) ); ?>',
													redeemed: '<?php echo esc_js( __( 'Redeemed', 'rewardswp' ) ); ?>',
													pending: '<?php echo esc_js( __( 'Pending', 'rewardswp' ) ); ?>',
													issued: '<?php echo esc_js( __( 'Issued', 'rewardswp' ) ); ?>',
													blocked: '<?php echo esc_js( __( 'Blocked', 'rewardswp' ) ); ?>',
													rejected: '<?php echo esc_js( __( 'Rejected', 'rewardswp' ) ); ?>',
													canceled: '<?php echo esc_js( __( 'Canceled', 'rewardswp' ) ); ?>',
													expired: '<?php echo esc_js( __( 'Expired', 'rewardswp' ) ); ?>'
												})[item.status] || (item.status.charAt(0).toUpperCase() + item.status.slice(1))"
											></span>
										</template>
									</div>
								</div>
							</div>
						</template>
					</div>

					<!-- Load More -->
					<div x-show="activityHasMore" class="mt-4 text-center">
						<button
							x-on:click="loadMoreActivity()"
							x-bind:disabled="activityLoadingMore"
							class="text-sm text-gray-600 hover:text-gray-900 font-medium cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed <?php echo esc_attr( $this->focus_classes ); ?>"
						>
							<span x-show="!activityLoadingMore"><?php esc_html_e( 'Load more', 'rewardswp' ); ?></span>
							<span x-show="activityLoadingMore" class="inline-flex items-center gap-1.5">
								<span class="animate-spin size-3.5 border-2 border-gray-200 border-t-gray-600 rounded-full"></span>
								<?php esc_html_e( 'Loading...', 'rewardswp' ); ?>
							</span>
						</button>
					</div>
				</div>
			</div>
			<!-- Top scroll fade -->
			<div
				x-show="activityShowTopFade"
				class="pointer-events-none absolute top-0 left-0 h-8 z-10"
				style="right: var(--rwp-scrollbar-width, 8px); background: linear-gradient(to bottom, var(--color-rwp-widget-bg), transparent);"
			></div>
			<!-- Bottom scroll fade -->
			<div
				x-show="activityShowFade"
				class="pointer-events-none absolute bottom-0 left-0 h-12 rounded-b-2xl"
				style="right: var(--rwp-scrollbar-width, 8px); background: linear-gradient(to top, var(--color-rwp-widget-bg), transparent);"
			></div>
			</div>
		</div>

		<?php
		/*
		* Create Account Prompt Panel (for guest members trying to redeem)
		*/
		?>
		<div
			x-show="currentPanel === 'create-account-prompt'"
			x-bind:class="'create-account-prompt-panel relative h-full flex flex-col ' + (currentPanel === 'create-account-prompt' ? (navigationDirection === 'initial' ? 'rwp-panel-fade-up' : (navigationDirection === 'forward' ? 'rwp-panel-slide-forward' : (navigationDirection === 'back' ? 'rwp-panel-slide-back' : ''))) : '')"
		>
			<div class="flex-1 overflow-y-auto custom-scrollbar px-6 py-6 text-rwp-widget-text">
				<!-- Icon and Header -->
				<div class="text-center mb-6">

					<div class="relative inline-block mb-2">
						<!-- Main reward icon -->
						<div class="size-10 flex items-center justify-center text-rwp-widget-text/45" x-show="lockedReward" x-html="lockedReward?.iconHtml">
							<!-- Icon will be populated by Alpine -->
						</div>
					</div>
					<h2 class="text-2xl font-bold text-rwp-widget-heading mb-3">
						<span x-show="lockedReward" x-text="lockedReward?.title"></span>
					</h2>
					<p class="text-sm text-rwp-widget-text">
						<?php esc_html_e( 'Requires an account to redeem', 'rewardswp' ); ?>
					</p>

				</div>

				<!-- Benefits List -->
				<div class="space-y-3 mb-8">
					<div class="flex items-start gap-3">
						<?php $this->icon_helper->render_icon( 'check', 'size-5 text-emerald-500 flex-shrink-0 mt-0.5' ); ?>
						<div class="flex-1">
							<p class="text-sm font-medium text-rwp-widget-heading">
								<?php esc_html_e( 'Redeem rewards now', 'rewardswp' ); ?>
							</p>
							<p class="text-sm text-rwp-widget-text">
								<?php
								printf(
									/* translators: %s is the currency label */
									esc_html__( 'Turn your %s into discounts and perks', 'rewardswp' ),
									'<span x-text="currencyPlural.toLowerCase()"></span>'
								);
								?>
							</p>
						</div>
					</div>
					<div class="flex items-start gap-3">
						<?php $this->icon_helper->render_icon( 'check', 'size-5 text-emerald-500 flex-shrink-0 mt-0.5' ); ?>
						<div class="flex-1">
							<p class="text-sm font-medium text-rwp-widget-heading">
								<?php esc_html_e( 'Instant setup', 'rewardswp' ); ?>
							</p>
							<p class="text-sm text-rwp-widget-text">
								<?php esc_html_e( 'One click and you\'re ready to go', 'rewardswp' ); ?>
							</p>
						</div>
					</div>
					<?php
					// Only show signup bonus benefit if signup points are configured.
					$earn_actions_option = get_option_object( EarnActionsOption::class );
					$all_actions         = $earn_actions_option->get_enabled_actions();
					$signup_action       = null;
					foreach ( $all_actions as $action ) {
						if ( isset( $action['id'] ) && $action['id'] === 'signup' ) {
							$signup_action = $action;
							break;
						}
					}
					$signup_points  = $signup_action['points'] ?? 0;
					$signup_enabled = ! empty( $signup_action );
					if ( $signup_enabled && $signup_points > 0 ) :
						?>
						<div class="flex items-start gap-3">
							<?php $this->icon_helper->render_icon( 'check', 'size-5 text-emerald-500 flex-shrink-0 mt-0.5' ); ?>
							<div class="flex-1">
								<p class="text-sm font-medium text-rwp-widget-heading">
									<?php esc_html_e( 'Earn even more', 'rewardswp' ); ?>
								</p>
								<p class="text-sm text-rwp-widget-text">
									<?php
									printf(
										/* translators: 1: Signup points amount, 2: Currency label */
										esc_html__( 'Get +%1$s bonus %2$s for signing up', 'rewardswp' ),
										esc_html( number_format_i18n( $signup_points ) ),
										'<span x-text="' . ( $signup_points == 1 ? 'currencySingular.toLowerCase()' : 'currencyPlural.toLowerCase()' ) . '"></span>'
									);
									?>
								</p>
							</div>
						</div>
					<?php endif; ?>
				</div>

		<!-- Create Account Button -->
		<button
			type="button"
			x-on:click="window.dispatchEvent(new CustomEvent('show-register-form'))"
			class="<?php echo esc_attr( $this->get_button_classes( 'lg', true ) ); ?> w-full"
		>
			<?php esc_html_e( 'Create account to use points', 'rewardswp' ); ?>
		</button>

				<!-- Back Link -->
				<div class="text-center mt-4">
					<button
						type="button"
						x-on:click="navigateToPanel('ways-to-redeem')"
						class="text-sm text-rwp-widget-text hover:text-rwp-widget-text underline cursor-pointer"
					>
						<?php esc_html_e( 'Back to rewards', 'rewardswp' ); ?>
					</button>
				</div>
			</div>
		</div>

		<!-- Redeem Success Panel -->
		<div
			x-show="currentPanel === 'redeem-success'"
			x-bind:class="'redeem-success-panel relative h-full flex flex-col ' + (currentPanel === 'redeem-success' ? (navigationDirection === 'initial' ? 'rwp-panel-fade-up' : (navigationDirection === 'forward' ? 'rwp-panel-slide-forward' : (navigationDirection === 'back' ? 'rwp-panel-slide-back' : ''))) : '')"
		>
			<div class="flex-1 overflow-y-auto custom-scrollbar px-6 py-6 pb-4 text-rwp-widget-text">
				<!-- Success Header -->
				<div
					class="text-center mb-6"
					x-data="{
						cartSubtotal: <?php echo (float) $this->integration->get_cart_subtotal(); ?>,
						currencySymbol: '<?php echo esc_js( $config['currency_symbol'] ); ?>'
					}"
				>
					<div class="flex justify-center mb-2">
						<?php $this->icon_helper->render_icon( 'check-circle-alt', 'size-10 text-green-600', 'bold' ); ?>
					</div>
					<p class="text-base text-gray-600 mt-2">
						<?php esc_html_e( 'Redeemed ', 'rewardswp' ); ?>
						<span x-text="redeemSuccessData.points_cost"></span>
						<span x-text="' ' + redeemSuccessData.points_label"></span>
					</p>
					<h2 class="text-2xl font-bold text-gray-900 text-pretty" x-text="redeemSuccessData.title">
						<!-- Title populated from Alpine data -->
					</h2>
					</div>

			<?php // Usage Instructions - only for non-free-product rewards. ?>
			<template x-if="redeemSuccessData.reward_type !== 'free_product'">
				<div class="text-sm text-gray-600 text-center mb-2 px-2">
					<p><?php esc_html_e( 'Use this coupon on your next order', 'rewardswp' ); ?></p>
				</div>
			</template>

			<?php // Coupon Code Display with Copy Functionality. ?>
			<div>
				<?php // Non-free-product rewards: show code first, then button. ?>
				<template x-if="redeemSuccessData.reward_type !== 'free_product'">
					<div class="mb-4" x-data="copyHandler()">
						<div x-data="{ isCheckmark: false }" class="flex rounded-lg bg-white outline-1 -outline-offset-1 outline-gray-200">
							<input
								type="text"
								readonly
								x-ref="couponCode"
								x-bind:value="redeemSuccessData.couponCode"
								x-on:click="handleCopyClick($refs.couponCode, null, false)"
								class="block min-w-0 grow px-5 py-3 text-sm font-mono text-gray-900 cursor-pointer focus:outline-none sm:text-sm/6 text-ellipsis"
							>
							<div class="flex">
								<button
									x-on:click="handleCopyClick($refs.couponCode, $data)"
									class="py-3 px-3 relative transition-all duration-200 cursor-pointer"
									x-bind:class="{ 'hover:text-gray-900 text-gray-500': !isCheckmark}"
									x-ref="copyButton">
									<div class="relative size-6">
										<div class="absolute inset-0 transition-all duration-300 ease-in-out"
											x-bind:class="isCheckmark ? 'opacity-0 scale-95' : 'opacity-100 scale-100'">
											<?php $this->icon_helper->render_icon( 'copy', 'size-6' ); ?>
										</div>
										<div class="absolute inset-0 transition-all duration-300 ease-in-out"
											x-bind:class="isCheckmark ? 'opacity-100 scale-100' : 'opacity-0 scale-95'">
											<?php $this->icon_helper->render_icon( 'check', 'size-6' ); ?>
										</div>
									</div>
								</button>
							</div>
						</div>
					</div>
				</template>

				<!-- Button and messages section -->
				<div>

				<!-- Cart-aware Button Section -->
				<div
					x-data="couponUtils( {
						nonce: '<?php echo esc_attr( wp_create_nonce( ClaimController::AJAX_NONCE ) ); ?>',
						buttonText: '<?php echo esc_js( __( 'Apply to cart', 'rewardswp' ) ); ?>',
						rewardId: redeemSuccessData.reward_id || 0,
						couponCode: redeemSuccessData.couponCode || '',
						isApplied: redeemSuccessData.is_applied || false,
						cartItemCount: <?php echo (int) $this->integration->get_cart_item_count(); ?>,
						cartProductIds: <?php echo esc_attr( wp_json_encode( $this->integration->get_cart_product_ids() ) ); ?>,
						cartSubtotal: <?php echo (float) $this->integration->get_cart_subtotal(); ?>,
						freeProductId: redeemSuccessData.free_product_id || null,
						minimumOrderAmount: redeemSuccessData.minimum_order_amount || 0,
						integrationSource: '<?php echo esc_js( $this->integration->get_source() ); ?>',
						checkoutUrl: '<?php echo esc_url( $this->is_demo && ! empty( $this->demo_urls['checkout'] ) ? $this->demo_urls['checkout'] : $this->integration->get_checkout_url() ); ?>',
						cartUrl: '<?php echo esc_url( $this->is_demo && ! empty( $this->demo_urls['cart'] ) ? $this->demo_urls['cart'] : $this->integration->get_cart_url() ); ?>',
						storeUrl: '<?php echo esc_url( $this->is_demo && ! empty( $this->demo_urls['store'] ) ? $this->demo_urls['store'] : $this->integration->get_store_url() ); ?>',
						isAdminPreview: <?php echo $this->is_admin_preview ? 'true' : 'false'; ?>,
						isDemo: <?php echo $this->is_demo ? 'true' : 'false'; ?>,
						addToCartText: '<?php echo esc_js( __( 'Add to cart', 'rewardswp' ) ); ?>',
						claimRewardText: '<?php echo esc_js( __( 'Claim reward', 'rewardswp' ) ); ?>',
						applyToCartText: '<?php echo esc_js( __( 'Claim free product', 'rewardswp' ) ); ?>',
						currencySymbol: '<?php echo esc_js( $config['currency_symbol'] ); ?>',
						pendingRewardId: <?php echo (int) $this->get_pending_reward_id(); ?>,
						dismissedRewardId: <?php echo (int) $this->get_dismissed_reward_id(); ?>
					} )"
					x-on:reset-buttons.window="if ( $event.detail.except !== $el ) isApplied = false;"
				>
				<?php
				// State: free-product-ready - Helper text when minimum IS met.
				$free_product_type  = $this->reward_types_factory->get_reward_type( 'free_product' );
				$panel_instructions = $free_product_type ? $free_product_type->get_panel_instructions() : __( 'Discount applies automatically.', 'rewardswp' );
				?>
					<template x-if="redeemSuccessData.reward_type === 'free_product' && !isApplied && !productAddedButCouponFailed && !isPending && (redeemSuccessData.minimum_order_amount <= 0 || cartSubtotal >= redeemSuccessData.minimum_order_amount)">
						<p class="text-sm font-medium text-gray-500 text-center mb-2"><?php echo esc_html( $panel_instructions ); ?></p>
					</template>

				<?php // State: free-product-locked - Claim instruction above button. ?>
					<template x-if="redeemSuccessData.reward_type === 'free_product' && !isApplied && !productAddedButCouponFailed && !isPending && redeemSuccessData.minimum_order_amount > 0 && cartSubtotal < redeemSuccessData.minimum_order_amount">
						<p class="text-sm text-gray-500 text-center text-pretty mb-2" x-text="'<?php echo esc_js( __( 'Claim now — we\'ll add it to your cart when you spend', 'rewardswp' ) ); ?> ' + <?php echo \Awesomemotive\Rewardswp\Helpers\js_money_expr( 'redeemSuccessData.minimum_order_amount' ); ?> + ' <?php echo esc_js( __( 'or more', 'rewardswp' ) ); ?>'"></p>
					</template>

				<?php
				/*
				 * Apply Coupon Button - Only show when NOT applied, NOT pending, and NOT failed due to minimum spend.
				 * Note: Shows even when isDismissed - dismissed only prevents auto-apply, user can still manually apply.
				 */
				?>
					<button
						x-show="!isApplied && !isPending && !productAddedButCouponFailed"
						class="<?php echo esc_attr( $this->get_button_classes( 'lg', true ) ); ?>"
						x-on:click="applyCoupon( redeemSuccessData.couponCode, redeemSuccessData.reward_id )"
						x-bind:disabled="isApplying"
						x-bind:class="{
							'opacity-50 cursor-not-allowed': isApplying,
							'hover:bg-rwp-brand-hover': !isApplying
						}"
					>
						<span
							class="[grid-area:stack] flex items-center justify-center transition-opacity duration-200"
							x-bind:class="{ 'invisible': isApplying, 'visible': !isApplying }"
						>
							<!-- Dynamic text based on reward type, cart state, minimum spend, and integration -->
							<template x-if="redeemSuccessData.reward_type === 'free_product'">
								<span x-text="getFreeProductButtonText(redeemSuccessData.free_product_id, redeemSuccessData.minimum_order_amount)"></span>
							</template>
							<template x-if="redeemSuccessData.reward_type !== 'free_product'">
								<span>
							<span x-show="isWooCommerce"><?php echo esc_html( __( 'Apply to cart', 'rewardswp' ) ); ?></span>
							<span x-show="!isWooCommerce"><?php echo esc_html( __( 'Apply coupon', 'rewardswp' ) ); ?></span>
								</span>
							</template>
						</span>
							<?php $this->icon_helper->render_button_spinner( 'isApplying' ); ?>
						</button>

					<div x-show="hasErrors" class="text-sm mt-2 text-red-500"><?php esc_html_e( 'An error occurred. Please reload the page and try again.', 'rewardswp' ); ?></div>

					<!-- Success Message - Show when coupon applied -->
					<div x-show="isApplied" x-cloak class="mb-1 font-medium inline-flex items-center gap-2 rounded-lg px-3 py-2 text-emerald-600 text-sm w-full justify-center">
						<template x-if="redeemSuccessData.reward_type === 'free_product'">
							<span><?php esc_html_e( 'Free product added to cart', 'rewardswp' ); ?></span>
						</template>
						<template x-if="redeemSuccessData.reward_type !== 'free_product'">
							<span x-text="redeemSuccessData.title + ' <?php echo esc_js( __( 'applied', 'rewardswp' ) ); ?>'"></span>
						</template>
					</div>

					<?php // State: free-product-pending - Waiting for minimum spend. ?>
					<template x-if="isPending && !productAddedButCouponFailed">
						<div class="text-sm w-full">
							<!-- Confirmation badge -->
							<div class="font-medium inline-flex items-center gap-1 text-emerald-600 text-sm w-full justify-center">
								<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
									<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
								</svg>
								<span><?php esc_html_e( 'Reward claimed', 'rewardswp' ); ?></span>
							</div>

							<!-- Explanation text -->
							<div class="text-center text-gray-500 px-2 mb-3" x-text="'<?php echo esc_js( __( 'We\'ll add it to your cart when you spend', 'rewardswp' ) ); ?> ' + <?php echo \Awesomemotive\Rewardswp\Helpers\js_money_expr( 'redeemSuccessData.minimum_order_amount' ); ?>">
							</div>

							<!-- Action buttons -->
							<div class="space-y-3">
								<!-- Primary CTA - Start/Continue Shopping -->
								<a
									x-bind:href="isAdminPreview ? 'javascript:void(0)' : storeUrl"
									x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
									x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
									x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
									class="<?php echo esc_attr( $button_lg_classes ); ?> no-underline block"
									x-text="isCartEmpty ? '<?php esc_attr_e( 'Start shopping', 'rewardswp' ); ?>' : '<?php esc_attr_e( 'Continue shopping', 'rewardswp' ); ?>'"
								></a>

								<!-- Secondary CTA - View Cart (WooCommerce only, when cart has items) -->
								<template x-if="isWooCommerce && !isCartEmpty">
									<a
										x-bind:href="isAdminPreview ? 'javascript:void(0)' : cartUrl"
										x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
										x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
										x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
										class="<?php echo esc_attr( $secondary_button_classes ); ?> no-underline block"
									>
										<?php esc_html_e( 'View cart', 'rewardswp' ); ?>
									</a>
								</template>
							</div>
						</div>
					</template>

					<?php // Partial Success Message - Product added but coupon failed (fallback for edge cases). ?>
					<div x-show="productAddedButCouponFailed && !isPending" x-cloak class="mt-3 mb-3 text-sm w-full space-y-2">
						<div x-show="productWasNewlyAdded" class="text-center text-emerald-600 font-medium">
							<?php esc_html_e( 'Product added to cart', 'rewardswp' ); ?>
						</div>
						<?php
						$almost_there_text = __( 'Almost there — add', 'rewardswp' );
						$currency_symbol   = $config['currency_symbol'];
						$more_to_unlock    = __( 'more to unlock', 'rewardswp' );
						?>
						<template x-if="remainingToUnlock > 0">
							<div class="text-center text-gray-600 px-2" x-text="'<?php echo esc_js( $almost_there_text . ' ' ); ?>' + <?php echo \Awesomemotive\Rewardswp\Helpers\js_money_expr( 'remainingToUnlock', "'" . esc_js( $currency_symbol ) . "'" ); ?> + ' <?php echo esc_js( $more_to_unlock ); ?>'"></div>
						</template>
					</div>

					<!-- Buttons when coupon failed but product added (fallback) -->
					<template x-if="productAddedButCouponFailed && !isPending">
						<div>
							<!-- Primary CTA - Continue Shopping -->
							<a
								x-bind:href="isAdminPreview ? 'javascript:void(0)' : storeUrl"
								x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
								x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
								x-bind:class="{ 'mb-3': isWooCommerce, '!cursor-not-allowed': isAdminPreview }"
								class="<?php echo esc_attr( $button_lg_classes ); ?> no-underline block"
							>
								<?php esc_html_e( 'Continue shopping', 'rewardswp' ); ?>
							</a>

							<!-- Secondary CTA - View Cart (WooCommerce only) -->
							<template x-if="isWooCommerce">
								<a
									x-bind:href="isAdminPreview ? 'javascript:void(0)' : cartUrl"
									x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
									x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
									x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
									class="<?php echo esc_attr( $secondary_button_classes ); ?> no-underline block"
								>
									<?php esc_html_e( 'View cart', 'rewardswp' ); ?>
								</a>
							</template>
						</div>
					</template>

					<!-- Error Message - Show when coupon failed to apply (no product involved) -->
					<div x-show="hasErrors && !productAddedButCouponFailed" x-cloak class="mb-1 font-medium inline-flex items-center gap-2 rounded-lg px-3 py-2 text-red-600 text-sm w-full justify-center">
						<span x-text="errorMessage"></span>
					</div>

					<!-- Buttons when coupon is applied successfully -->
					<template x-if="isApplied && !isCartEmpty">
						<div>
							<!-- Primary CTA - Go to Checkout -->
							<a
								x-bind:href="isAdminPreview ? 'javascript:void(0)' : checkoutUrl"
								x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
								x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
								class="<?php echo esc_attr( $button_lg_classes ); ?> no-underline block"
								x-bind:class="{ 'mb-3': isWooCommerce, '!cursor-not-allowed': isAdminPreview }"
							>
								<?php esc_html_e( 'Go to checkout', 'rewardswp' ); ?>
							</a>

							<!-- Secondary CTA - View Cart (WooCommerce only) -->
							<template x-if="isWooCommerce">
								<a
									x-bind:href="isAdminPreview ? 'javascript:void(0)' : cartUrl"
									x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
									x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
									x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
									class="<?php echo esc_attr( $secondary_button_classes ); ?> no-underline block"
								>
									<?php esc_html_e( 'View cart', 'rewardswp' ); ?>
								</a>
							</template>
						</div>
					</template>

					<!-- Button when coupon is applied but cart is empty -->
					<template x-if="isApplied && isCartEmpty">
						<div>
							<!-- Primary CTA - Shop Now -->
							<a
								x-bind:href="isAdminPreview ? 'javascript:void(0)' : storeUrl"
								x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
								x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
								x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
								class="<?php echo esc_attr( $button_lg_classes ); ?> no-underline block"
							>
								<?php esc_html_e( 'Shop now', 'rewardswp' ); ?>
							</a>
							<!-- No secondary button when cart is empty -->
						</div>
					</template>

				<!-- Coupon Terms (below buttons) - show only expiry when applied or ready (minimum met). -->
				<template x-if="redeemSuccessData.coupon_terms">
					<div class="mt-2 text-center">
						<span class="text-sm text-gray-500" x-text="(isApplied || (redeemSuccessData.reward_type === 'free_product' && !isPending && (redeemSuccessData.minimum_order_amount <= 0 || cartSubtotal >= redeemSuccessData.minimum_order_amount))) && redeemSuccessData.expiry_text ? redeemSuccessData.expiry_text : redeemSuccessData.coupon_terms"></span>
					</div>
				</template>
				</div>
				</div>

			</div>

			</div>

			<?php // Progressive disclosure for free_product: Copy code drawer - shown in FP_ELIGIBLE_READY and FP_LOCKED_PENDING states. ?>
			<div
				x-data="{
					couponFailed: false,
					showCode: false,
					isCheckmark: false,
					isPending: false,
					isApplied: false,
					cartSubtotal: <?php echo (float) $this->integration->get_cart_subtotal(); ?>,
					minimumOrderAmount: 0,
					pendingRewardId: <?php echo (int) $this->get_pending_reward_id(); ?>,
					...copyHandler()
				}"
				x-init="
					minimumOrderAmount = redeemSuccessData.minimum_order_amount || 0;
					isPending = pendingRewardId > 0 && pendingRewardId === parseInt(redeemSuccessData.reward_id || 0, 10);
					window.addEventListener('free-product-coupon-failed', () => { couponFailed = true; });
					window.addEventListener('show-redeem-success', () => { couponFailed = false; isPending = false; isApplied = false; });
					window.addEventListener('reward-set-pending', () => { isPending = true; });
					window.addEventListener('reward-auto-applied', () => { isPending = false; isApplied = true; });
					window.addEventListener('coupon-applied', () => { isApplied = true; });
				"
				x-show="redeemSuccessData.reward_type === 'free_product' && !isApplied && (isPending || (redeemSuccessData.minimum_order_amount <= 0 || cartSubtotal >= redeemSuccessData.minimum_order_amount))"
				x-cloak
				class="px-6 text-center bg-gray-50 border-t border-gray-100 flex flex-col-reverse items-center"
			>
					<button
						x-on:click="if (!showCode) { showCode = true; $nextTick(() => { handleCopyClick($refs.couponCode, $data); }); } else { showCode = false; }"
						class="text-sm text-gray-500 hover:text-gray-600 underline underline-offset-2 cursor-pointer bg-transparent border-0 my-4"
						x-text="showCode ? '<?php echo esc_js( __( 'Hide code', 'rewardswp' ) ); ?>' : '<?php echo esc_js( __( 'Use a code at checkout', 'rewardswp' ) ); ?>'"
					></button>

					<!-- Collapsible code section -->
					<div x-show="showCode" x-collapse x-cloak class="w-full">
						<div class="pt-4">
						<div class="flex rounded-lg bg-white outline-1 -outline-offset-1 outline-gray-200">
							<input
								type="text"
								readonly
								x-ref="couponCode"
								x-bind:value="redeemSuccessData.couponCode"
								x-on:click="handleCopyClick($refs.couponCode, null, false)"
								class="block min-w-0 grow px-5 py-3 text-sm font-mono text-gray-900 cursor-pointer focus:outline-none sm:text-sm/6 text-ellipsis"
							>
							<div class="flex">
								<button
									x-on:click="handleCopyClick($refs.couponCode, $data)"
									class="py-3 px-3 relative transition-all duration-200 cursor-pointer"
									x-bind:class="{ 'hover:text-gray-900 text-gray-500': !isCheckmark}"
									x-ref="copyButton">
									<div class="relative size-6">
										<div class="absolute inset-0 transition-all duration-300 ease-in-out"
											x-bind:class="isCheckmark ? 'opacity-0 scale-95' : 'opacity-100 scale-100'">
											<?php $this->icon_helper->render_icon( 'copy', 'size-6' ); ?>
										</div>
										<div class="absolute inset-0 transition-all duration-300 ease-in-out"
											x-bind:class="isCheckmark ? 'opacity-100 scale-100' : 'opacity-0 scale-95'">
											<?php $this->icon_helper->render_icon( 'check', 'size-6' ); ?>
										</div>
									</div>
								</button>
							</div>
						</div>
						</div>
					</div>
				</div>
			</div>
		</div>


		<?php
		/*
		* "Your Rewards" sub-panel.
		* Shows "Your rewards" list for logged-in members.
		*/
		?>
		<div
			x-show="currentPanel === 'your-rewards'"
			x-bind:class="'relative h-full flex flex-col overflow-hidden ' + (currentPanel === 'your-rewards' ? (navigationDirection === 'initial' ? 'rwp-panel-fade-up' : (navigationDirection === 'forward' ? 'rwp-panel-slide-forward' : (navigationDirection === 'back' ? 'rwp-panel-slide-back' : ''))) : '')"
			x-data="{ panelAtTop: true, panelAtBottom: false }"
			<?php // Reading availableRewards.length registers it as an Alpine dependency so the effect re-runs when rewards load/change. ?>
			x-effect="if (currentPanel === 'your-rewards') { void availableRewards?.length; const el = $el.querySelector('.overflow-y-auto'); if (el && el.scrollHeight > 0) { panelAtBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 5; } }"
		>
			<div class="flex-1 overflow-y-auto custom-scrollbar px-0 py-6"
				@scroll="panelAtTop = $el.scrollTop < 5; panelAtBottom = $el.scrollTop + $el.clientHeight >= $el.scrollHeight - 5"
			>

			<h2 class="<?php echo esc_attr( $sub_panel_heading_classes ); ?> px-6"><?php esc_html_e( 'Your rewards', 'rewardswp' ); ?></h2>

			<div class="px-3">
				<template x-if="availableRewards && availableRewards.length > 0">
					<template x-for="reward in availableRewards" x-bind:key="reward.id">
						<div>
							<button
								x-on:click="navigateToPanel('reward-detail-' + reward.id)"
								class="<?php echo esc_attr( $nav_item_classes ); ?> !px-3"
									>
							<div class="flex items-center gap-4">
								<div class="<?php echo esc_attr( $icon_classes ); ?>" x-html="reward.icon_html">
									<!-- Icon will be populated from server -->
								</div>
								<div class="flex-1">
									<div class="font-medium text-rwp-widget-heading text-sm" x-text="reward.title"></div>
									<div class="text-xs text-gray-500">
										<template x-if="reward.is_out_of_stock">
											<span class="text-amber-600"><?php esc_html_e( 'Out of stock', 'rewardswp' ); ?></span>
										</template>
										<template x-if="!reward.is_out_of_stock && reward.points_cost > 0">
											<span x-text="'<?php echo esc_js( __( 'Redeemed', 'rewardswp' ) ); ?> ' + reward.points_cost + ' ' + reward.points_label"></span>
										</template>
										<template x-if="!reward.is_out_of_stock && reward.points_cost === 0">
											<span><?php esc_html_e( 'From referral', 'rewardswp' ); ?></span>
										</template>
									</div>
								</div>
							</div>
										<svg class="size-5 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
											<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
								</svg>
								</button>
							</div>
						</template>
				</template>

				<!-- Empty state when no rewards -->
					<template x-if="!availableRewards || availableRewards.length === 0">
						<div class="text-center py-8">
							<div class="flex justify-center mb-3">
								<?php $this->icon_helper->render_icon( 'gift-free', 'size-10 text-gray-400' ); ?>
							</div>
							<h3 class="text-lg font-medium text-gray-900 mb-1"><?php esc_html_e( 'No rewards yet', 'rewardswp' ); ?></h3>
							<p class="text-sm text-gray-500 mb-5"><?php esc_html_e( 'Start earning to unlock your first reward', 'rewardswp' ); ?></p>
							<div class="flex justify-center">
								<button
									x-on:click="navigateToPanel('ways-to-earn')"
									class="<?php echo esc_attr( $button_sm_classes ); ?> hover:bg-rwp-button-hover"
								>
									<?php
									printf(
										/* translators: %s: currency label (e.g. "Points") */
										esc_html__( 'Earn %s', 'rewardswp' ),
										esc_html( $config['points']['currency']['plural'] )
									);
									?>
								</button>
							</div>
						</div>
					</template>
				</div>
			</div>

			<!-- Top scroll fade -->
			<div x-show="!panelAtTop"
				class="pointer-events-none absolute top-0 left-0 h-8 z-10"
				style="right: var(--rwp-scrollbar-width, 8px); background: linear-gradient(to bottom, var(--color-rwp-widget-bg), transparent);"></div>
			<!-- Bottom scroll fade -->
			<div x-show="!panelAtBottom"
				class="pointer-events-none absolute bottom-0 left-0 h-12 z-10"
				style="right: var(--rwp-scrollbar-width, 8px); background: linear-gradient(to top, var(--color-rwp-widget-bg), transparent);"></div>
		</div>

		<?php
		/*
		 * Single Reward Detail Panel.
		 */
		?>
		<template x-for="reward in availableRewards" x-bind:key="'detail-' + reward.id">
			<div
				x-show="currentPanel === 'reward-detail-' + reward.id"
				x-bind:class="'reward-detail-panel relative h-full flex flex-col ' + (currentPanel === 'reward-detail-' + reward.id ? (navigationDirection === 'initial' ? 'rwp-panel-fade-up' : (navigationDirection === 'forward' ? 'rwp-panel-slide-forward' : (navigationDirection === 'back' ? 'rwp-panel-slide-back' : ''))) : '')"
			>




				<div class="flex-1 overflow-y-auto custom-scrollbar px-6 py-6 pb-6 text-rwp-widget-text">
					<!-- Icon and Title (Centered like friend claim) -->
					<div
						class="text-center mb-6"
						x-data="{
							cartSubtotal: <?php echo (float) $this->integration->get_cart_subtotal(); ?>,
							currencySymbol: '<?php echo esc_js( $config['currency_symbol'] ); ?>'
						}"
					>
						<div class="flex justify-center mx-auto mb-2 [&>svg]:size-10" x-html="reward.icon_html">
							<!-- Icon will be populated from server -->
						</div>
						<h2 class="text-2xl font-bold text-gray-900 text-pretty" x-text="reward.title"></h2>
					</div>

					<?php // Description (smaller text) - only for non-free-product rewards. ?>
					<template x-if="reward.reward_type !== 'free_product'">
						<p class="text-sm text-gray-600 text-center mb-2" x-text="reward.description"></p>
					</template>

			<!-- Coupon Code Display with Copy Functionality -->
			<template x-if="reward.coupon_code && reward.coupon_code !== ''">
					<div>
						<?php // Non-free-product rewards: show code first, then button. ?>
						<template x-if="reward.reward_type !== 'free_product'">
							<div class="mb-4" x-data="copyHandler()">
								<div x-data="{ isCheckmark: false }" class="flex rounded-lg bg-white outline-1 -outline-offset-1 outline-gray-200">
									<input
										type="text"
										readonly
										x-ref="couponCode"
										x-bind:value="reward.coupon_code"
										x-on:click="handleCopyClick($refs.couponCode, null, false)"
										class="block min-w-0 grow px-5 py-3 text-sm font-mono text-gray-900 cursor-pointer focus:outline-none sm:text-sm/6 text-ellipsis"
									>
									<div class="flex">
										<button
											x-on:click="handleCopyClick($refs.couponCode, $data)"
											class="py-3 px-3 relative transition-all duration-200 cursor-pointer"
											x-bind:class="{ 'hover:text-gray-900 text-gray-500': !isCheckmark}"
											x-ref="copyButton">
											<div class="relative size-6">
												<div class="absolute inset-0 transition-all duration-300 ease-in-out"
													x-bind:class="isCheckmark ? 'opacity-0 scale-95' : 'opacity-100 scale-100'">
													<?php $this->icon_helper->render_icon( 'copy', 'size-6' ); ?>
												</div>
												<div class="absolute inset-0 transition-all duration-300 ease-in-out"
													x-bind:class="isCheckmark ? 'opacity-100 scale-100' : 'opacity-0 scale-95'">
													<?php $this->icon_helper->render_icon( 'check', 'size-6' ); ?>
												</div>
											</div>
										</button>
									</div>
								</div>
							</div>
						</template>

						<!-- Button and messages section -->
						<div>

					<!-- Out of Stock Notice -->
					<template x-if="reward.is_out_of_stock">
						<div class="text-center mb-4 p-4 bg-amber-50 rounded-lg">
							<p class="text-sm text-amber-700 font-medium">
								<?php esc_html_e( 'This product is currently out of stock', 'rewardswp' ); ?>
							</p>
							<p class="text-xs text-amber-600 mt-1">
								<?php esc_html_e( 'Your reward will remain available when the product is back in stock.', 'rewardswp' ); ?>
							</p>
						</div>
					</template>

					<!-- Cart-aware Button Section -->
					<div
						x-data="couponUtils( {
							nonce: '<?php echo esc_attr( wp_create_nonce( ClaimController::AJAX_NONCE ) ); ?>',
							buttonText: '<?php echo esc_js( __( 'Apply to cart', 'rewardswp' ) ); ?>',
							rewardId: reward.id,
							couponCode: reward.coupon_code || '',
							appliedButtonText: '<?php echo esc_js( __( 'Coupon applied', 'rewardswp' ) ); ?>',
							isApplied: reward.is_applied || false,
							isOutOfStock: reward.is_out_of_stock || false,
							cartItemCount: <?php echo (int) $this->integration->get_cart_item_count(); ?>,
							cartProductIds: <?php echo esc_attr( wp_json_encode( $this->integration->get_cart_product_ids() ) ); ?>,
							cartSubtotal: <?php echo (float) $this->integration->get_cart_subtotal(); ?>,
							freeProductId: reward.free_product_id || null,
							minimumOrderAmount: reward.minimum_order_amount || 0,
							integrationSource: '<?php echo esc_js( $this->integration->get_source() ); ?>',
							checkoutUrl: '<?php echo esc_url( $this->is_demo && ! empty( $this->demo_urls['checkout'] ) ? $this->demo_urls['checkout'] : $this->integration->get_checkout_url() ); ?>',
							cartUrl: '<?php echo esc_url( $this->is_demo && ! empty( $this->demo_urls['cart'] ) ? $this->demo_urls['cart'] : $this->integration->get_cart_url() ); ?>',
							storeUrl: '<?php echo esc_url( $this->is_demo && ! empty( $this->demo_urls['store'] ) ? $this->demo_urls['store'] : $this->integration->get_store_url() ); ?>',
							isAdminPreview: <?php echo $this->is_admin_preview ? 'true' : 'false'; ?>,
							isDemo: <?php echo $this->is_demo ? 'true' : 'false'; ?>,
							addToCartText: '<?php echo esc_js( __( 'Add to cart', 'rewardswp' ) ); ?>',
							claimRewardText: '<?php echo esc_js( __( 'Claim reward', 'rewardswp' ) ); ?>',
							applyToCartText: '<?php echo esc_js( __( 'Claim free product', 'rewardswp' ) ); ?>',
							currencySymbol: '<?php echo esc_js( $config['currency_symbol'] ); ?>',
							pendingRewardId: <?php echo (int) $this->get_pending_reward_id(); ?>,
							dismissedRewardId: <?php echo (int) $this->get_dismissed_reward_id(); ?>
						} )"
						x-on:reset-buttons.window="if ( $event.detail.except !== $el ) isApplied = false;"
					>
						<?php
						// State: free-product-ready - Helper text when minimum IS met.
						$free_product_type  = $this->reward_types_factory->get_reward_type( 'free_product' );
						$panel_instructions = $free_product_type ? $free_product_type->get_panel_instructions() : __( 'Discount applies automatically.', 'rewardswp' );
						?>
						<template x-if="reward.reward_type === 'free_product' && !isApplied && !productAddedButCouponFailed && !isPending && (reward.minimum_order_amount <= 0 || cartSubtotal >= reward.minimum_order_amount)">
							<p class="text-sm font-medium text-gray-500 text-center mb-2"><?php echo esc_html( $panel_instructions ); ?></p>
						</template>

					<?php // State: free-product-locked - Claim instruction above button. ?>
						<template x-if="reward.reward_type === 'free_product' && !isApplied && !productAddedButCouponFailed && !isPending && reward.minimum_order_amount > 0 && cartSubtotal < reward.minimum_order_amount">
							<p class="text-sm text-gray-500 text-center text-pretty mb-2" x-text="'<?php echo esc_js( __( 'Claim now — we\'ll add it to your cart when you spend', 'rewardswp' ) ); ?> ' + <?php echo \Awesomemotive\Rewardswp\Helpers\js_money_expr( 'reward.minimum_order_amount' ); ?> + ' <?php echo esc_js( __( 'or more', 'rewardswp' ) ); ?>'"></p>
						</template>

					<?php
					/*
					 * Apply Coupon Button - Only show when NOT applied, NOT pending, NOT out of stock, and NOT failed due to minimum spend.
					 * Note: Shows even when isDismissed - dismissed only prevents auto-apply, user can still manually apply.
					 */
					?>
						<button
							x-show="!isApplied && !isPending && !isOutOfStock && !productAddedButCouponFailed"
							class="<?php echo esc_attr( $this->get_button_classes( 'lg', true ) ); ?>"
							x-on:click="applyCoupon( reward.coupon_code )"
							x-bind:disabled="isApplying"
							x-bind:class="{
								'opacity-50 cursor-not-allowed': isApplying,
								'hover:bg-rwp-brand-hover': !isApplying
							}"
						>
							<span
								class="[grid-area:stack] flex items-center justify-center transition-opacity duration-200"
								x-bind:class="{ 'invisible': isApplying, 'visible': !isApplying }"
							>
								<!-- Dynamic text based on reward type, cart state, minimum spend, and integration -->
								<template x-if="reward.reward_type === 'free_product'">
									<span x-text="getFreeProductButtonText(reward.free_product_id, reward.minimum_order_amount)"></span>
								</template>
								<template x-if="reward.reward_type !== 'free_product'">
									<span>
								<span x-show="isWooCommerce"><?php echo esc_html( __( 'Apply to cart', 'rewardswp' ) ); ?></span>
								<span x-show="!isWooCommerce"><?php echo esc_html( __( 'Apply coupon', 'rewardswp' ) ); ?></span>
									</span>
								</template>
							</span>
							<?php $this->icon_helper->render_button_spinner( 'isApplying' ); ?>
						</button>

						<div x-show="hasErrors" class="text-sm mt-2 text-red-500"><?php esc_html_e( 'An error occurred. Please reload the page and try again.', 'rewardswp' ); ?></div>

						<!-- Success Message - Show when coupon applied -->
						<div x-show="isApplied" x-cloak class="mb-1 font-medium inline-flex items-center gap-2 rounded-lg px-3 py-2 text-emerald-600 text-sm w-full justify-center">
							<template x-if="reward.reward_type === 'free_product'">
								<span><?php esc_html_e( 'Free product added to cart', 'rewardswp' ); ?></span>
							</template>
							<template x-if="reward.reward_type !== 'free_product'">
								<span x-text="reward.title + ' <?php echo esc_js( __( 'applied', 'rewardswp' ) ); ?>'"></span>
							</template>
						</div>

						<?php // State: free-product-pending - Waiting for minimum spend. ?>
						<template x-if="isPending && !productAddedButCouponFailed">
							<div class="text-sm w-full">
								<!-- Confirmation badge -->
								<div class="font-medium inline-flex items-center gap-1 text-emerald-600 text-sm w-full justify-center">
									<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
										<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
									</svg>
									<span><?php esc_html_e( 'Reward claimed', 'rewardswp' ); ?></span>
								</div>

								<!-- Explanation text -->
								<div class="text-center text-gray-500 px-2 mb-3" x-text="'<?php echo esc_js( __( 'We\'ll add it to your cart when you spend', 'rewardswp' ) ); ?> ' + <?php echo \Awesomemotive\Rewardswp\Helpers\js_money_expr( 'reward.minimum_order_amount' ); ?>">
								</div>

								<!-- Action buttons -->
								<div class="space-y-3">
									<!-- Primary CTA - Start/Continue Shopping -->
									<a
										x-bind:href="isAdminPreview ? 'javascript:void(0)' : storeUrl"
										x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
										x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
										x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
										class="<?php echo esc_attr( $button_lg_classes ); ?> no-underline block"
										x-text="isCartEmpty ? '<?php esc_attr_e( 'Start shopping', 'rewardswp' ); ?>' : '<?php esc_attr_e( 'Continue shopping', 'rewardswp' ); ?>'"
									></a>

									<!-- Secondary CTA - View Cart (WooCommerce only, when cart has items) -->
									<template x-if="isWooCommerce && !isCartEmpty">
										<a
											x-bind:href="isAdminPreview ? 'javascript:void(0)' : cartUrl"
											x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
											x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
											x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
											class="<?php echo esc_attr( $secondary_button_classes ); ?> no-underline block"
										>
											<?php esc_html_e( 'View cart', 'rewardswp' ); ?>
										</a>
									</template>
								</div>
							</div>
						</template>

						<!-- Partial Success Message - Product added but coupon failed (fallback for edge cases) -->
						<div x-show="productAddedButCouponFailed && !isPending" x-cloak class="mt-3 mb-3 text-sm w-full space-y-2">
							<div x-show="productWasNewlyAdded" class="text-center text-emerald-600 font-medium">
								<?php esc_html_e( 'Product added to cart', 'rewardswp' ); ?>
							</div>
							<?php
							$almost_there_text = __( 'Almost there — add', 'rewardswp' );
							$currency_symbol   = $config['currency_symbol'];
							$more_to_unlock    = __( 'more to unlock', 'rewardswp' );
							?>
							<template x-if="remainingToUnlock > 0">
								<div class="text-center text-gray-600 px-2" x-text="'<?php echo esc_js( $almost_there_text . ' ' ); ?>' + <?php echo \Awesomemotive\Rewardswp\Helpers\js_money_expr( 'remainingToUnlock', "'" . esc_js( $currency_symbol ) . "'" ); ?> + ' <?php echo esc_js( $more_to_unlock ); ?>'"></div>
							</template>
						</div>

						<!-- Buttons when coupon failed but product added (fallback) -->
						<template x-if="productAddedButCouponFailed && !isPending">
							<div>
								<!-- Primary CTA - Continue Shopping -->
								<a
									x-bind:href="isAdminPreview ? 'javascript:void(0)' : storeUrl"
									x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
									x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
									x-bind:class="{ 'mb-3': isWooCommerce, '!cursor-not-allowed': isAdminPreview }"
									class="<?php echo esc_attr( $button_lg_classes ); ?> no-underline block"
								>
									<?php esc_html_e( 'Continue shopping', 'rewardswp' ); ?>
								</a>

								<!-- Secondary CTA - View Cart (WooCommerce only) -->
								<template x-if="isWooCommerce">
									<a
										x-bind:href="isAdminPreview ? 'javascript:void(0)' : cartUrl"
										x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
										x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
										x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
										class="<?php echo esc_attr( $secondary_button_classes ); ?> no-underline block"
									>
										<?php esc_html_e( 'View cart', 'rewardswp' ); ?>
									</a>
								</template>
							</div>
						</template>

						<!-- Error Message - Show when coupon failed to apply (no product involved) -->
						<div x-show="hasErrors && !productAddedButCouponFailed && !isPending" x-cloak class="mb-1 font-medium inline-flex items-center gap-2 rounded-lg px-3 py-2 text-red-600 text-sm w-full justify-center">
							<span x-text="errorMessage"></span>
						</div>

							<!-- Buttons when coupon is applied successfully -->
							<template x-if="isApplied && !isCartEmpty">
								<div>
									<!-- Primary CTA - Go to Checkout -->
									<a
										x-bind:href="isAdminPreview ? 'javascript:void(0)' : checkoutUrl"
										x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
										x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
										class="<?php echo esc_attr( $button_lg_classes ); ?> no-underline block"
										x-bind:class="{ 'mb-3': isWooCommerce, '!cursor-not-allowed': isAdminPreview }"
									>
										<?php esc_html_e( 'Go to checkout', 'rewardswp' ); ?>
									</a>

									<!-- Secondary CTA - View Cart (WooCommerce only) -->
									<template x-if="isWooCommerce">
										<a
											x-bind:href="isAdminPreview ? 'javascript:void(0)' : cartUrl"
											x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
											x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
											x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
											class="<?php echo esc_attr( $secondary_button_classes ); ?> no-underline block"
										>
											<?php esc_html_e( 'View cart', 'rewardswp' ); ?>
										</a>
									</template>
								</div>
							</template>

							<!-- Button when coupon is applied but cart is empty -->
							<template x-if="isApplied && isCartEmpty">
								<div>
									<!-- Primary CTA - Shop Now -->
									<a
										x-bind:href="isAdminPreview ? 'javascript:void(0)' : storeUrl"
										x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
										x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
										x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
										class="<?php echo esc_attr( $button_lg_classes ); ?> no-underline block"
									>
										<?php esc_html_e( 'Shop now', 'rewardswp' ); ?>
									</a>
									<!-- No secondary button when cart is empty -->
								</div>
							</template>

						<!-- Coupon Terms (below buttons) - show only expiry when applied or ready (minimum met). -->
						<template x-if="reward.coupon_terms">
							<div class="mt-2 text-center">
								<span class="text-sm text-gray-500" x-text="(isApplied || (reward.reward_type === 'free_product' && !isPending && (reward.minimum_order_amount <= 0 || cartSubtotal >= reward.minimum_order_amount))) && reward.expiry_text ? reward.expiry_text : reward.coupon_terms"></span>
							</div>
						</template>
						</div>
						</div>

					</div>
					</template>

					<!-- Back to Your Rewards Button -->



				</div>

				<?php // Progressive disclosure for free_product: Copy code drawer - shown in FP_ELIGIBLE_READY and FP_LOCKED_PENDING states. ?>
				<div
					x-data="{
						couponFailed: false,
						showCode: false,
						isCheckmark: false,
						isPending: false,
						isApplied: false,
						cartSubtotal: <?php echo (float) $this->integration->get_cart_subtotal(); ?>,
						minimumOrderAmount: 0,
						pendingRewardId: <?php echo (int) $this->get_pending_reward_id(); ?>,
						...copyHandler()
					}"
					x-init="
						minimumOrderAmount = reward.minimum_order_amount || 0;
						isApplied = reward.is_applied || false;
						isPending = pendingRewardId > 0 && pendingRewardId === parseInt(reward.id || 0, 10);
						window.addEventListener('free-product-coupon-failed', () => { couponFailed = true; });
						window.addEventListener('show-redeem-success', () => { couponFailed = false; isPending = false; });
						window.addEventListener('reward-set-pending', () => { isPending = true; });
						window.addEventListener('reward-auto-applied', () => { isPending = false; isApplied = true; });
						window.addEventListener('coupon-applied', () => { isApplied = true; });
					"
					x-show="reward.reward_type === 'free_product' && !isApplied && (isPending || (reward.minimum_order_amount <= 0 || cartSubtotal >= reward.minimum_order_amount))"
					x-cloak
					class="px-6 text-center bg-gray-50 border-t border-gray-100 flex flex-col-reverse items-center"
				>
					<button
						x-on:click="if (!showCode) { showCode = true; $nextTick(() => { handleCopyClick($refs.couponCode, $data); }); } else { showCode = false; }"
							class="text-sm text-gray-400 hover:text-gray-600 underline underline-offset-2 cursor-pointer bg-transparent border-0 my-4"
							x-text="showCode ? '<?php echo esc_js( __( 'Hide code', 'rewardswp' ) ); ?>' : '<?php echo esc_js( __( 'Use a code at checkout', 'rewardswp' ) ); ?>'"
						></button>

						<!-- Collapsible code section -->
						<div x-show="showCode" x-collapse x-cloak class="w-full">
							<div class="pt-4">
							<div class="flex rounded-lg bg-white outline-1 -outline-offset-1 outline-gray-200">
								<input
									type="text"
									readonly
									x-ref="couponCode"
									x-bind:value="reward.coupon_code"
									x-on:click="handleCopyClick($refs.couponCode, null, false)"
									class="block min-w-0 grow px-5 py-3 text-sm font-mono text-gray-900 cursor-pointer focus:outline-none sm:text-sm/6 text-ellipsis"
								>
								<div class="flex">
									<button
										x-on:click="handleCopyClick($refs.couponCode, $data)"
										class="py-3 px-3 relative transition-all duration-200 cursor-pointer"
										x-bind:class="{ 'hover:text-gray-900 text-gray-500': !isCheckmark}"
										x-ref="copyButton">
										<div class="relative size-6">
											<div class="absolute inset-0 transition-all duration-300 ease-in-out"
												x-bind:class="isCheckmark ? 'opacity-0 scale-95' : 'opacity-100 scale-100'">
												<?php $this->icon_helper->render_icon( 'copy', 'size-6' ); ?>
											</div>
											<div class="absolute inset-0 transition-all duration-300 ease-in-out"
												x-bind:class="isCheckmark ? 'opacity-100 scale-100' : 'opacity-0 scale-95'">
												<?php $this->icon_helper->render_icon( 'check', 'size-6' ); ?>
											</div>
										</div>
									</button>
								</div>
							</div>
							</div>
						</div>
					</div>
				</div>
			</div>
		</template>

		<?php
		return ob_get_clean();
	}

	/**
	 * Check if this panel has an integrated launcher.
	 *
	 * @since 1.1.0
	 * @return bool True since RewardsWidgetView has integrated launcher.
	 */
	public function has_integrated_launcher(): bool {
		return true;
	}

	/**
	 * Check if this panel provides its own header.
	 *
	 * @since 1.1.0
	 * @return bool True since RewardsWidgetView provides custom header.
	 */
	public function provides_header(): bool {
		return true;
	}

	/**
	 * Render friend claim content.
	 *
	 * @since 1.1.0
	 * @param array $claim_data Claim data array.
	 * @return string HTML content.
	 */
	private function render_friend_claim_content( array $claim_data = [], string $currency_symbol = '' ): string {
		ob_start();

		$button_classes              = $this->get_button_classes( 'lg' );
		$button_lg_classes           = $this->get_button_classes( 'lg' );
		$button_with_spinner_classes = $this->get_button_classes( 'lg', true );
		$secondary_button_classes    = $this->secondary_button_classes;
		$input_classes               = $this->input_classes;

		$advocate_name  = $claim_data['advocate_name'] ?? '';
		$advocate_email = $claim_data['advocate_email'] ?? '';
		$reward_info    = $claim_data['reward_info'] ?? null;
		$referral_code  = $claim_data['referral_code'] ?? '';

		// Get reward information using the existing helper method.
		$friend_reward_info = $this->get_reward_info_from_config( $reward_info, $currency_symbol );
		$reward_name        = $friend_reward_info['formatted_amount'];

		// Extract reward type and free product ID for smart button text.
		$reward_type     = $reward_info['type'] ?? '';
		$free_product_id = '';
		if ( 'free_product' === $reward_type && ! empty( $reward_info['products_included'][0]['id'] ) ) {
			$free_product_id = $reward_info['products_included'][0]['id'];
		}
		?>

		<!-- Claim Form -->
		<div
			x-data="friendClaimForm({
				nonce: '<?php echo esc_attr( wp_create_nonce( 'rewardswp_friend_form_nonce' ) ); ?>',
				referralCode: '<?php echo esc_js( $referral_code ); ?>',
				ajaxUrl: '<?php echo esc_url( frontend_ajax_url() ); ?>',
				storeUrl: '<?php echo esc_url( $this->is_demo && ! empty( $this->demo_urls['store'] ) ? $this->demo_urls['store'] : $this->integration->get_store_url() ); ?>',
				checkoutUrl: '<?php echo esc_url( $this->is_demo && ! empty( $this->demo_urls['checkout'] ) ? $this->demo_urls['checkout'] : $this->integration->get_checkout_url() ); ?>',
				cartUrl: '<?php echo esc_url( $this->is_demo && ! empty( $this->demo_urls['cart'] ) ? $this->demo_urls['cart'] : $this->integration->get_cart_url() ); ?>',
				cartItemCount: <?php echo (int) $this->integration->get_cart_item_count(); ?>,
				cartProductIds: <?php echo esc_attr( wp_json_encode( $this->integration->get_cart_product_ids() ) ); ?>,
				integrationSource: '<?php echo esc_js( $this->integration->get_source() ); ?>',
				alreadyClaimed: <?php echo $claim_data['already_claimed'] ? 'true' : 'false'; ?>,
				existingCoupon: '<?php echo esc_js( $claim_data['existing_coupon'] ); ?>',
				existingRewardId: <?php echo isset( $claim_data['existing_reward'] ) && $claim_data['existing_reward'] ? (int) $claim_data['existing_reward']->id : 'null'; ?>,
				isCouponApplied: <?php echo ( ! empty( $claim_data['existing_coupon'] ) && method_exists( $this->integration, 'is_coupon_applied' ) && $this->integration->is_coupon_applied( $claim_data['existing_coupon'] ) ) ? 'true' : 'false'; ?>,
				isAdminPreview: <?php echo $this->is_admin_preview ? 'true' : 'false'; ?>,
				isDemo: <?php echo $this->is_demo ? 'true' : 'false'; ?>,
				rewardType: '<?php echo esc_js( $reward_type ); ?>',
				freeProductId: '<?php echo esc_js( $free_product_id ); ?>',
				rewardTitle: '<?php echo esc_js( $friend_reward_info['formatted_amount'] ); ?>',
				minimumOrderAmount: <?php echo floatval( $reward_info['minimum_order_amount'] ?? 0 ); ?>,
				addToCartText: '<?php echo esc_js( __( 'Add to cart', 'rewardswp' ) ); ?>',
				claimRewardText: '<?php echo esc_js( __( 'Claim reward', 'rewardswp' ) ); ?>',
				applyToCartText: '<?php echo esc_js( __( 'Claim free product', 'rewardswp' ) ); ?>'
			})"
			class="text-rwp-widget-text"
		>
			<!-- Reward Display -->
			<div x-show="!showSuccess" class="text-center mb-6">
				<!-- Centered Icon (match success screen) -->
				<div class="flex justify-center mb-2">
					<?php
					$icon_name    = $friend_reward_info['icon'];
					$icon_classes = 'size-10 text-gray-700';
					$this->icon_helper->render_icon( $icon_name, $icon_classes );
					?>
				</div>

				<!-- Reward value display -->
				<div>
					<!-- Attribution eyebrow text -->
					<?php if ( ! empty( $advocate_name ) ) : ?>
						<p class="text-base text-gray-600 mb-0.5">
							<?php
							printf(
								/* translators: %s: advocate name */
								esc_html__( '%s has sent you a gift', 'rewardswp' ),
								esc_html( $advocate_name )
							);
							?>
						</p>
					<?php else : ?>
						<p class="text-base text-gray-600 mb-0.5">
							<?php esc_html_e( 'You were sent a gift', 'rewardswp' ); ?>
						</p>
					<?php endif; ?>

					<!-- Large bold reward value -->
					<div class="text-2xl font-bold text-gray-900">
						<?php echo esc_html( $friend_reward_info['formatted_amount'] ); ?>
					</div>
				</div>
			</div>
			<!-- Success State -->
			<div x-show="showSuccess" style="display: none;">
				<div class="text-center mb-6">

					<div class="flex justify-center mb-2">
						<?php $this->icon_helper->render_icon( 'check-circle-alt', 'size-10 text-green-600', 'bold' ); ?>
					</div>

					<!-- Reward value display -->
					<div>
						<p class="text-base text-gray-600 mt-2 mb-0.5">
							<?php esc_html_e( 'You have received', 'rewardswp' ); ?>
						</p>
						<div class="flex items-center justify-center gap-3">
							<div class="size-8 flex items-center justify-center flex-shrink-0 text-gray-700">
								<?php $this->icon_helper->render_icon( $friend_reward_info['icon'], 'size-8' ); ?>
							</div>
							<div class="text-2xl font-bold text-gray-900" x-text="rewardTitle">
								<?php echo esc_html( $friend_reward_info['formatted_amount'] ); ?>
							</div>
						</div>
					</div>
				</div>

				<!-- Usage Instructions -->
				<div x-show="couponCode" class="text-sm text-gray-600 text-center mb-2 px-2">
					<p><?php esc_html_e( 'Use at checkout on your first order', 'rewardswp' ); ?></p>
				</div>

				<!-- Coupon Code Display (if coupon code available) -->
				<div x-show="couponCode" class="mb-4">
					<div x-data="{ isCheckmark: false }" class="flex rounded-lg bg-white outline-1 -outline-offset-1 outline-gray-200">
						<input
							x-on:click="handleCopyClick($refs.couponCode, null, false)"
							type="text"
							readonly
							class="block min-w-0 grow px-5 py-3 text-sm text-gray-900 focus:outline-none sm:text-sm/6 text-ellipsis cursor-pointer font-mono"
							x-ref="couponCode"
							x-bind:value="couponCode"
						>

						<div class="flex">
								<button
									x-on:click="handleCopyClick($refs.couponCode, $data)"
									class="py-3 px-3 relative transition-all duration-200 cursor-pointer"
									x-bind:class="{ 'hover:text-gray-900 text-gray-500': !isCheckmark}"
									x-ref="copyButton">
									<div class="relative size-6">
										<div class="absolute inset-0 transition-all duration-300 ease-in-out"
											x-bind:class="isCheckmark ? 'opacity-0 scale-95' : 'opacity-100 scale-100'">
											<?php $this->icon_helper->render_icon( 'copy', 'size-6' ); ?>
										</div>
										<div class="absolute inset-0 transition-all duration-300 ease-in-out"
											x-bind:class="isCheckmark ? 'opacity-100 scale-100' : 'opacity-0 scale-95'">
											<?php $this->icon_helper->render_icon( 'check', 'size-6' ); ?>
										</div>
									</div>
								</button>
							</div>
					</div>
				</div>



				<!-- Apply Coupon Button - Only show when NOT applied and NOT failed due to minimum spend -->
				<div x-show="couponCode && !couponApplied && !productAddedButCouponFailed" x-cloak class="mb-4">
					<button
						x-on:click="applyCouponToCart()"
						x-bind:disabled="applyingCoupon"
						x-bind:class="{
							'opacity-50 cursor-not-allowed': applyingCoupon,
							'hover:bg-rwp-brand-hover': !applyingCoupon
						}"
						class="<?php echo esc_attr( $this->get_button_classes( 'lg', true ) ); ?>"
					>
						<span
							class="[grid-area:stack] flex items-center justify-center transition-opacity duration-200"
							x-bind:class="{ 'invisible': applyingCoupon, 'visible': !applyingCoupon }"
						>
							<!-- Dynamic text based on reward type, cart state, and minimum spend -->
							<template x-if="rewardType === 'free_product'">
								<span x-text="freeProductButtonText"></span>
							</template>
							<template x-if="rewardType !== 'free_product'">
								<span>
							<span x-show="isWooCommerce"><?php echo esc_html( __( 'Apply to cart', 'rewardswp' ) ); ?></span>
							<span x-show="!isWooCommerce"><?php echo esc_html( __( 'Apply coupon', 'rewardswp' ) ); ?></span>
								</span>
							</template>
						</span>
						<?php $this->icon_helper->render_button_spinner( 'applyingCoupon' ); ?>
					</button>
					<div x-show="couponError && !productAddedButCouponFailed" class="text-sm mt-2 text-red-500" x-text="couponErrorMessage"></div>
				</div>

				<!-- Success Message - Show when coupon applied -->
				<div x-show="couponApplied" x-cloak class="mb-1 font-medium inline-flex items-center gap-2 rounded-lg px-3 py-2 text-emerald-600 text-sm w-full justify-center">
					<span><?php echo esc_html( $friend_reward_info['formatted_amount'] ); ?> <?php esc_html_e( 'applied', 'rewardswp' ); ?></span>
				</div>

				<?php // Partial Success Message - Product added but coupon failed (minimum spend not met). ?>
				<div x-show="productAddedButCouponFailed" x-cloak class="mt-3 mb-3 text-sm w-full space-y-2">
					<div x-show="productWasNewlyAdded" class="text-center text-emerald-600 font-medium">
						<?php esc_html_e( 'Product added to cart', 'rewardswp' ); ?>
					</div>
					<?php
					$almost_there_text = __( 'Almost there — add', 'rewardswp' );
					$more_to_unlock = __( 'more to unlock', 'rewardswp' );
					?>
					<template x-if="remainingToUnlock > 0">
						<div class="text-center text-gray-600 px-2" x-text="'<?php echo esc_js( $almost_there_text . ' ' ); ?>' + <?php echo \Awesomemotive\Rewardswp\Helpers\js_money_expr( 'remainingToUnlock', "'" . esc_js( $currency_symbol ) . "'" ); ?> + ' <?php echo esc_js( $more_to_unlock ); ?>'"></div>
					</template>
				</div>

				<!-- Buttons when coupon failed but product added -->
				<template x-if="productAddedButCouponFailed">
					<div>
						<!-- Primary CTA - Continue Shopping -->
						<a
							x-bind:href="isAdminPreview ? 'javascript:void(0)' : storeUrl"
							x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
							x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
							x-bind:class="{ 'mb-3': isWooCommerce, '!cursor-not-allowed': isAdminPreview }"
							class="<?php echo esc_attr( $button_lg_classes ); ?> no-underline block"
						>
							<?php esc_html_e( 'Continue shopping', 'rewardswp' ); ?>
						</a>

						<!-- Secondary CTA - View Cart (WooCommerce only) -->
						<template x-if="isWooCommerce">
							<a
								x-bind:href="isAdminPreview ? 'javascript:void(0)' : '<?php echo esc_url( $this->is_demo && ! empty( $this->demo_urls['cart'] ) ? $this->demo_urls['cart'] : $this->integration->get_cart_url() ); ?>'"
								x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
								x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
								x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
								class="<?php echo esc_attr( $secondary_button_classes ); ?> no-underline block"
							>
								<?php esc_html_e( 'View cart', 'rewardswp' ); ?>
							</a>
						</template>
					</div>
				</template>

				<!-- Buttons when coupon is applied successfully -->
				<template x-if="couponApplied && !isCartEmpty">
					<div>
						<!-- Primary CTA - Go to Checkout -->
						<a
							x-bind:href="isAdminPreview ? 'javascript:void(0)' : '<?php echo esc_url( $this->is_demo && ! empty( $this->demo_urls['checkout'] ) ? $this->demo_urls['checkout'] : $this->integration->get_checkout_url() ); ?>'"
							x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
							x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
							class="<?php echo esc_attr( $button_lg_classes ); ?> no-underline block"
							x-bind:class="{ 'mb-3': isWooCommerce, '!cursor-not-allowed': isAdminPreview }"
						>
							<?php esc_html_e( 'Go to checkout', 'rewardswp' ); ?>
						</a>

						<!-- Secondary CTA - View Cart (WooCommerce only) -->
						<template x-if="isWooCommerce">
							<a
								x-bind:href="isAdminPreview ? 'javascript:void(0)' : '<?php echo esc_url( $this->is_demo && ! empty( $this->demo_urls['cart'] ) ? $this->demo_urls['cart'] : $this->integration->get_cart_url() ); ?>'"
								x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
								x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
								x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
								class="<?php echo esc_attr( $secondary_button_classes ); ?> no-underline block"
							>
								<?php esc_html_e( 'View cart', 'rewardswp' ); ?>
							</a>
						</template>
					</div>
				</template>

				<!-- Button when coupon is applied but cart is empty -->
				<template x-if="couponApplied && isCartEmpty">
					<div>
						<!-- Primary CTA - Shop Now -->
						<a
							x-bind:href="isAdminPreview ? 'javascript:void(0)' : storeUrl"
							x-bind:data-preview-disabled="isAdminPreview ? 'true' : null"
							x-bind:data-tippy-content="isAdminPreview ? '<?php esc_attr_e( 'Disabled for admin preview', 'rewardswp' ); ?>' : null"
							x-bind:class="{ '!cursor-not-allowed': isAdminPreview }"
							class="<?php echo esc_attr( $button_lg_classes ); ?> no-underline block"
						>
							<?php esc_html_e( 'Shop now', 'rewardswp' ); ?>
						</a>
						<!-- No secondary button when cart is empty -->
					</div>
				</template>
			</div>

			<!-- Form State -->
			<div x-show="!showSuccess">
				<!-- Claim instruction -->
				<p class="text-sm text-gray-600 text-center mb-2 px-2">
					<?php esc_html_e( 'Enter your email to claim it now.', 'rewardswp' ); ?>
				</p>

				<!-- Email Form -->
				<form x-on:submit.prevent="if (!isAdminPreview && !isDemo) { claimReward() }" class="mb-3">
					<?php $this->anti_spam_service->render_field(); ?>
					<div class="mb-4">

						<label for="claim-email" class="block text-sm font-medium text-gray-700 mb-1.5 text-left sr-only"><?php esc_html_e( 'Email', 'rewardswp' ); ?></label>
						<input
							type="email"
							id="claim-email"
							x-model="formData.email"
							class="<?php echo esc_attr( $input_classes ); ?>"
							placeholder="<?php esc_attr_e( 'you@example.com', 'rewardswp' ); ?>"
							required
						>

					</div>

					<!-- Error State -->
					<div x-show="showError" x-transition:enter="transition-opacity duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" style="display: none;" class="mb-4">
						<div class="bg-red-50 border border-red-200 rounded-lg p-4">
							<div class="flex items-center">
								<svg class="size-5 text-red-400 mr-3 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
									<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
								</svg>
								<span class="text-sm text-red-700 text-left" x-html="errorMessage"></span>
							</div>
						</div>
					</div>

					<button
						type="submit"
						x-bind:disabled="isSubmitting"
						x-bind:class="isSubmitting ? 'opacity-50 cursor-not-allowed' : (isAdminPreview || isDemo) ? '!cursor-not-allowed' : 'hover:bg-rwp-brand-hover'"
						x-bind:data-preview-disabled="(isAdminPreview || isDemo) ? 'true' : null"
						x-bind:data-tippy-content="(isAdminPreview || isDemo) ? '<?php esc_attr_e( 'Disabled for preview', 'rewardswp' ); ?>' : null"
						class="<?php echo esc_attr( $this->get_button_classes( 'lg', true ) ); ?>"
					>
						<span
							class="[grid-area:stack] transition-opacity duration-200"
							x-bind:class="{ 'invisible': isSubmitting, 'visible': !isSubmitting }"
						><?php echo esc_html( __( 'Claim your gift', 'rewardswp' ) ); ?></span>
						<?php $this->icon_helper->render_button_spinner( 'isSubmitting' ); ?>
					</button>

				</form>
			</div>

		</div>








		<?php
		return ob_get_clean();
	}

	/**
	 * Render sharing options.
	 *
	 * @since 1.1.0
	 * @param array $sharing_options Sharing options array.
	 * @return string HTML content.
	 */
	private function render_sharing_options( array $sharing_options, array $args = [] ): string {
		// Use class property set by render_widget_wrapper - this is always set first
		$is_admin_preview = $this->is_admin_preview;

		// If no sharing options available (and not in admin preview), render nothing to avoid extra spacing.
		if ( empty( $sharing_options ) && ! $is_admin_preview ) {
			return '';
		}

		ob_start();

		$x_show_map = [
			'email'    => 'sharing.email',
			'twitter'  => 'sharing.x',
			'facebook' => 'sharing.facebook',
		];

		// Share options that should have click tracking for event logging.
		$trackable_share_options = [ 'email', 'twitter', 'facebook' ];
		?>
		<div x-data="{ showMore: false }" x-show="sharing.email || sharing.x || sharing.facebook" x-cloak>
			<div class="grid grid-cols-4 mt-6">
				<?php
				$desktop_count = 0;
				$max_visible   = 3; // Show up to 3 icons + "More" button
				$overflowing   = count( array_filter( $sharing_options, fn( $opt ) => ! ( $opt['mobile_only'] ?? false ) ) ) > $max_visible + 1;

				foreach ( $sharing_options as $option ) :
					$is_mobile_only = $option['mobile_only'] ?? false;
					if ( ! $is_mobile_only ) {
						++$desktop_count;
					}

					$link_attrs = sprintf(
						'class="flex flex-col items-center justify-center min-w-[2.75rem] min-h-[2.75rem] p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-50 transition-all duration-200 ease-out cursor-pointer rounded-lg %s"',
						$is_mobile_only ? 'sm:hidden' : ''
					);

					if ( $overflowing && $desktop_count > $max_visible && ! $is_mobile_only ) {
						// Hide overflowing desktop icons behind the "More" button
						continue;
					}

					$x_show_attr = '';
					if ( $is_admin_preview && isset( $x_show_map[ $option['id'] ] ) ) {
						$x_show_attr = 'x-show="' . esc_attr( $x_show_map[ $option['id'] ] ) . '"';
					}

					// Build preview attributes for admin preview mode - use PHP directly instead of Alpine x-bind.
					$preview_attrs  = '';
					$preview_class  = '';
					$base_classes   = 'flex flex-col items-center justify-center min-w-[2.75rem] min-h-[2.75rem] p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-50 transition-all duration-200 ease-out rounded-lg';
					$mobile_classes = $is_mobile_only ? ' sm:hidden' : '';

					if ( $is_admin_preview ) {
						$preview_attrs = sprintf(
							'data-preview-disabled="true" data-tippy-content="%s" x-on:click="$event.preventDefault();"',
							esc_attr__( 'Disabled for admin preview', 'rewardswp' )
						);
						$preview_class = ' !cursor-not-allowed';
					} else {
						$preview_class = ' cursor-pointer';
					}

					// Build click handler for event tracking.
					$click_handler = '';
					if ( in_array( $option['id'], $trackable_share_options, true ) ) {
						$option_id     = esc_attr( $option['id'] );
						$click_handler = "x-on:click=\"handleShareClick('{$option_id}', \$event)\"";
					}

					$link_target = $option['id'] === 'email' ? '_self' : '_blank';
					?>
					<a
						href="<?php echo esc_url( $option['url'] ); ?>"
						target="<?php echo esc_attr( $link_target ); ?>"
						rel="noopener noreferrer"
						class="<?php echo esc_attr( $base_classes . $mobile_classes . $preview_class ); ?>"
						<?php
						// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Attributes are escaped above.
						echo trim( $preview_attrs . ' ' . $x_show_attr . ' ' . $click_handler );
						?>
					>
							<?php $this->icon_helper->render_icon( $option['icon'], 'size-6 shrink-0' ); ?>
							<span class="text-xs mt-1"><?php echo esc_html( $option['label'] ); ?></span>
					</a>
				<?php endforeach; ?>

				<?php if ( $overflowing ) : ?>
					<button x-on:click="showMore = !showMore" class="flex flex-col items-center justify-center min-w-[2.75rem] min-h-[2.75rem] p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-50 transition-all duration-200 ease-out cursor-pointer rounded-lg">
						<?php $this->icon_helper->render_icon( 'ellipsis', 'size-6 shrink-0' ); ?>
						<span class="text-xs mt-1"><?php esc_html_e( 'More', 'rewardswp' ); ?></span>
					</button>
				<?php endif; ?>
			</div>

			<!-- Overflow Menu -->
			<div x-show="showMore" x-cloak class="mt-4 border-t border-gray-100 pt-4">
				<div class="grid grid-cols-4 gap-2">
					<?php
					$desktop_count = 0;
					foreach ( $sharing_options as $option ) :
						$is_mobile_only = $option['mobile_only'] ?? false;
						if ( ! $is_mobile_only ) {
							++$desktop_count;
						}

						if ( $desktop_count <= $max_visible && ! $is_mobile_only ) {
							continue; // Skip the ones already shown
						}

						$x_show_attr = '';
						if ( $is_admin_preview && isset( $x_show_map[ $option['id'] ] ) ) {
							$x_show_attr = 'x-show="' . esc_attr( $x_show_map[ $option['id'] ] ) . '"';
						}

						// Build preview attributes for admin preview mode.
						$preview_attrs = '';
						$preview_class = '';
						$base_classes  = 'flex flex-col items-center justify-center min-w-[2.75rem] min-h-[2.75rem] p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-50 transition-all duration-200 ease-out rounded-lg';

						if ( $is_admin_preview ) {
							$preview_attrs = sprintf(
								'data-preview-disabled="true" data-tippy-content="%s" x-on:click="$event.preventDefault();"',
								esc_attr__( 'Disabled for admin preview', 'rewardswp' )
							);
							$preview_class = ' !cursor-not-allowed';
						} else {
							$preview_class = ' cursor-pointer';
						}

						// Build click handler for event tracking.
						$click_handler = '';
						if ( in_array( $option['id'], $trackable_share_options, true ) ) {
							$option_id     = esc_attr( $option['id'] );
							$click_handler = "x-on:click=\"handleShareClick('{$option_id}', \$event)\"";
						}

						$link_target = $option['id'] === 'email' ? '_self' : '_blank';
						?>
						<a
							href="<?php echo esc_url( $option['url'] ); ?>"
							target="<?php echo esc_attr( $link_target ); ?>"
							rel="noopener noreferrer"
							class="<?php echo esc_attr( $base_classes . $preview_class ); ?>"
							<?php
							// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Attributes are escaped above.
							echo trim( $preview_attrs . ' ' . $x_show_attr . ' ' . $click_handler );
							?>
						>
							<?php $this->icon_helper->render_icon( $option['icon'], 'size-6 shrink-0' ); ?>
							<span class="text-xs mt-1"><?php echo esc_html( $option['label'] ); ?></span>
						</a>
					<?php endforeach; ?>
				</div>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Render Points section for non-members (guest view).
	 *
	 * @since 1.1.0
	 * @param array $config Widget configuration.
	 * @return string HTML content.
	 */
	private function render_points_section_for_non_members( array $config ): string {
		// Make class properties available as local variables for use in the template
		$card_classes              = $this->card_classes;
		$nav_item_classes          = $this->nav_item_classes;
		$button_classes            = $this->get_button_classes( 'lg' );
		$button_lg_classes         = $this->get_button_classes( 'lg' );
		$button_sm_classes         = $this->get_button_classes( 'sm' );
		$secondary_button_classes  = $this->secondary_button_classes;
		$icon_classes              = $this->icon_classes;
		$sub_panel_heading_classes = $this->sub_panel_heading_classes;

		ob_start();
		?>

		<div class="<?php echo esc_attr( $card_classes ); ?>">
			<!-- Points System Preview -->
			<div class="mb-6 text-center">
				<h3 class="text-base font-semibold text-rwp-widget-heading mb-2" x-text="currencyPlural"></h3>
				<p class="text-sm" x-text="'<?php /* translators: %s: currency name plural (e.g., "points") */ echo esc_js( __( 'Earn %s for completing different actions, and turn them into rewards.', 'rewardswp' ) ); ?>'.replace('%s', currencyPlural.toLowerCase())"></p>
			</div>


			<!-- Navigation Items -->
			<div>
				<button
					x-on:click="navigateToPanel('ways-to-earn')"
					class="<?php echo esc_attr( $nav_item_classes ); ?>"
				>
					<div class="flex items-center gap-4">
						<div class="size-8 rounded-lg flex items-center justify-center text-gray-700">
							<?php $this->icon_helper->render_icon( 'earn', 'size-8' ); ?>
						</div>
						<span class="text-sm text-rwp-widget-heading font-medium"><?php esc_html_e( 'Ways to earn', 'rewardswp' ); ?></span>
					</div>
					<svg class="size-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
						<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"></path>
					</svg>
				</button>

				<button
					x-on:click="navigateToPanel('ways-to-redeem')"
					class="<?php echo esc_attr( $nav_item_classes ); ?>"
				>
					<div class="flex items-center gap-4">
						<div class="size-8 rounded-lg flex items-center justify-center text-gray-700">
							<?php $this->icon_helper->render_icon( 'gift-free', 'size-8' ); ?>
						</div>
						<span class="text-sm text-rwp-widget-heading font-medium"><?php esc_html_e( 'Ways to redeem', 'rewardswp' ); ?></span>
					</div>
					<svg class="size-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
						<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"></path>
					</svg>
				</button>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Render Referrals section for non-members (guest view).
	 *
	 * @since 1.1.0
	 * @param array $config Widget configuration.
	 * @return string HTML content.
	 */
	private function render_referrals_section_for_non_members( array $config ): string {

		$card_classes        = $this->card_classes;
		$has_advocate_reward = ! empty( $config['referrals']['advocate_reward'] ) && is_array( $config['referrals']['advocate_reward'] ) && ! empty( $config['referrals']['advocate_reward']['type'] );
		$has_friend_reward   = ! empty( $config['referrals']['friend_reward'] ) && is_array( $config['referrals']['friend_reward'] ) && ! empty( $config['referrals']['friend_reward']['type'] );
		$advocate_info       = $has_advocate_reward ? $this->get_reward_info_from_config( $config['referrals']['advocate_reward'], $config['currency_symbol'] ) : null;
		$friend_info         = $has_friend_reward ? $this->get_reward_info_from_config( $config['referrals']['friend_reward'], $config['currency_symbol'] ) : null;

		ob_start();
		?>
		<div class="<?php echo esc_attr( $card_classes ); ?> text-center">
			<h2 class="text-base font-semibold text-rwp-widget-heading mb-2"><?php esc_html_e( 'Refer your friends', 'rewardswp' ); ?></h2>
			<!-- Description changes based on whether friend reward exists -->
			<?php if ( $has_friend_reward || $this->is_admin_preview ) : ?>
			<p class="text-sm mb-4"
				<?php
				if ( $this->is_admin_preview ) :
					?>
					x-show="friendReward && friendReward.type"<?php endif; ?>>
				<?php esc_html_e( 'Share your link with friends. They get a reward and you earn one when they purchase.', 'rewardswp' ); ?>
			</p>
			<?php endif; ?>
			<?php if ( ! $has_friend_reward || $this->is_admin_preview ) : ?>
			<p class="text-sm mb-4"
				<?php
				if ( $this->is_admin_preview ) :
					?>
					x-show="!friendReward || !friendReward.type"<?php endif; ?>>
				<?php esc_html_e( 'Share your link with friends and earn a reward when they purchase.', 'rewardswp' ); ?>
			</p>
			<?php endif; ?>

		<div class="py-3b">
			<!-- Advocate reward -->
			<?php if ( $has_advocate_reward || $this->is_admin_preview ) : ?>
			<div class="flex items-center justify-between px-4 py-3"
				<?php
				if ( $this->is_admin_preview ) :
					?>
					x-show="advocateReward && advocateReward.type"<?php endif; ?>>
				<div class="flex items-center gap-4">
					<div class="size-8 flex items-center justify-center shrink-0 text-gray-700"
						<?php
						if ( $this->is_admin_preview ) :
							?>
							x-html="getRewardIconHtml(advocateReward)"<?php endif; ?>>
						<?php if ( $has_advocate_reward ) : ?>
							<?php $this->icon_helper->render_icon( $advocate_info['icon'], 'size-8' ); ?>
						<?php endif; ?>
					</div>
					<div class="flex-1 text-left">
						<div class="font-medium text-rwp-widget-heading text-sm"><?php esc_html_e( 'You get', 'rewardswp' ); ?></div>
						<div class="text-sm"
							<?php
							if ( $this->is_admin_preview ) :
								?>
								x-text="getRewardDescription(advocateReward)"<?php endif; ?>>
							<?php if ( $has_advocate_reward ) : ?>
								<?php echo esc_html( $advocate_info['formatted_amount'] ); ?>
							<?php endif; ?>
						</div>
					</div>
				</div>
				<div class="size-5"></div>
			</div>
			<?php endif; ?>
			<!-- Friend reward -->
			<?php if ( $has_friend_reward || $this->is_admin_preview ) : ?>
			<div class="flex items-center justify-between px-4 py-3"
				<?php
				if ( $this->is_admin_preview ) :
					?>
					x-show="friendReward && friendReward.type"<?php endif; ?>>
				<div class="flex items-center gap-4">
					<div class="size-8 flex items-center justify-center flex-shrink-0 text-gray-700"
						<?php
						if ( $this->is_admin_preview ) :
							?>
							x-html="getRewardIconHtml(friendReward)"<?php endif; ?>>
						<?php if ( $has_friend_reward ) : ?>
							<?php $this->icon_helper->render_icon( $friend_info['icon'], 'size-8' ); ?>
						<?php endif; ?>
					</div>
					<div class="flex-1 text-left">
						<div class="font-medium text-rwp-widget-heading text-sm"><?php esc_html_e( 'They get', 'rewardswp' ); ?></div>
						<div class="text-sm"
							<?php
							if ( $this->is_admin_preview ) :
								?>
								x-text="getRewardDescription(friendReward)"<?php endif; ?>>
							<?php if ( $has_friend_reward ) : ?>
								<?php echo esc_html( $friend_info['formatted_amount'] ); ?>
							<?php endif; ?>
						</div>
					</div>
				</div>
				<div class="size-5"></div>
			</div>
			<?php endif; ?>
		</div>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Get the IconHelper instance.
	 *
	 * @since 1.1.0
	 * @return IconHelper
	 */
	public function get_icon_helper(): IconHelper {
		return $this->icon_helper;
	}
}
