<?php
declare(strict_types=1);

namespace Help4BuilderSuite\Modules\Builder;

use Help4BuilderSuite\Contracts\ModuleContract;
use Help4BuilderSuite\Support\AdminRequest;
use Help4BuilderSuite\Support\Settings;
use Help4BuilderSuite\Support\VideoPlayer;
use Help4BuilderSuite\Modules\Calendar\Workspace as CalendarWorkspace;
use Help4BuilderSuite\Modules\StaffDirectory\Workspace as StaffDirectoryWorkspace;
use WP_Admin_Bar;
use WP_Post;

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

final class Module implements ModuleContract
{
    private const MAX_COLUMNS = 24;
    private const MAX_SLOT = self::MAX_COLUMNS - 1;
    private const DOCUMENT_ORIGIN_META = '_h4_builder_document_origin';
    /**
     * @var string[]
     */
    private array $allowedBlockTypes = [
        'section',
        'heading',
        'text',
        'image',
        'button',
        'html',
        'shortcode',
        'product_reviews',
        'commerce_indicator',
        'google_business',
        'slider',
        'spacer',
        'divider',
        'video',
        'icon',
        'list',
        'quote',
        'cta',
        'gallery',
        'map',
        'faq',
        'collection',
        'calendar',
        'staff_directory',
        'counter',
        'progress',
        'menu',
        'container',
        'inner_section',
    ];
    private string $builderStudioHook = '';
    /**
     * @var array{colors: array<string, string>, fonts: array<string, string>}|null
     */
    private ?array $runtimeGlobalDesignTokens = null;
    /**
     * @var array<int, array<string, mixed>>|null
     */
    private ?array $runtimePreviewLayout = null;
    private ?string $runtimePreviewPageLayout = null;
    private ?string $runtimePreviewPageTitleMode = null;
    private ?string $runtimePreviewPageCss = null;
    private bool $previewContextActive = false;
    private int $previewContextPostId = 0;
    private string $previewContextPostType = '';
    private int $previewRepresentativePostId = 0;
    private string $previewRepresentativePostType = '';
    /**
     * @var array<int, array<string, mixed>>|null
     */
    private ?array $runtimeWidgetCatalog = null;
    /**
     * @var array<int, array{post_id:int,template_role:string,image_count:int}>
     */
    private array $renderContextStack = [];

    public function key(): string
    {
        return 'builder';
    }

    public function boot(): void
    {
        add_action('wp_enqueue_scripts', [VideoPlayer::class, 'registerAssets'], 1);
        add_action('admin_enqueue_scripts', [VideoPlayer::class, 'registerAssets'], 1);
        add_action('add_meta_boxes', [$this, 'registerMetaBox']);
        add_action('save_post', [$this, 'saveBuilderSettings'], 10, 2);
        add_filter('the_content', [$this, 'renderBuilderContent'], 999);
        add_filter('use_block_editor_for_post', [$this, 'disableBlockEditorForBuilderMode'], 20, 2);
        add_action('wp_enqueue_scripts', [$this, 'enqueueFrontendAssets'], 20);
        add_action('admin_enqueue_scripts', [$this, 'enqueueAdminAssets']);
        add_action('admin_menu', [$this, 'registerBuilderPagesMenu']);
        add_action('admin_menu', [$this, 'registerBuilderStudioPage']);
        add_action('admin_notices', [$this, 'renderBuilderNotice']);
        add_action('admin_bar_menu', [$this, 'registerAdminBarEditLink'], 80);
        add_action('admin_enqueue_scripts', [$this, 'enqueueBuilderStudioAssets']);
        add_action('wp_ajax_h4bs_builder_save_layout', [$this, 'ajaxSaveLayout']);
        add_action('wp_ajax_h4bs_builder_publish_page', [$this, 'ajaxPublishPage']);
        add_action('wp_ajax_h4bs_builder_layout_recovery', [$this, 'ajaxLayoutRecovery']);
        add_action('wp_ajax_h4bs_builder_render_preview', [$this, 'ajaxRenderPreview']);
        add_action('wp_ajax_h4bs_builder_store_draft_preview', [$this, 'ajaxStoreDraftPreview']);
        add_action('wp_ajax_h4bs_builder_import_site_kit', [$this, 'ajaxImportSiteKit']);
        add_action('wp_ajax_h4bs_builder_load_catalog', [$this, 'ajaxLoadBuilderCatalog']);
        add_action('wp_ajax_h4bs_builder_copy_source', [$this, 'ajaxCopyBuilderSource']);
        add_action('template_redirect', [$this, 'prepareDraftPreviewRequest'], -100);
        add_action('template_redirect', [$this, 'renderTemplateDraftPreviewDocument'], -90);
        add_filter('admin_body_class', [$this, 'builderStudioBodyClass']);
        add_action('current_screen', [$this, 'setBuilderStudioAdminTitle']);
        add_filter('body_class', [$this, 'builderPageBodyClasses']);
        add_filter('h4bs_builder_layout_has_semantic_h1', [$this, 'filterLayoutHasSemanticH1'], 10, 2);
        add_filter('h4bs_has_assigned_template_role', [$this, 'filterDraftHasAssignedTemplateRole'], 999, 2);
        add_action('wp_body_open', [$this, 'renderDraftAssignedHeader'], 6);
        add_action('wp_footer', [$this, 'renderDraftAssignedFooter'], 4);

        add_filter('post_row_actions', [$this, 'addDuplicateLink'], 10, 2);
        add_filter('page_row_actions', [$this, 'addDuplicateLink'], 10, 2);
        add_filter('manage_page_posts_columns', [$this, 'addBuilderStatusColumn']);
        add_action('manage_page_posts_custom_column', [$this, 'renderBuilderStatusColumn'], 10, 2);
        add_action('admin_action_h4bs_duplicate_post', [$this, 'handleDuplicate']);
        add_action('admin_action_h4bs_open_builder', [$this, 'handleOpenBuilder']);
        add_shortcode('h4_builder', [$this, 'renderBuilderShortcode']);
        add_action('init', [$this, 'registerWidgetFallbackShortcodes'], 90);
        add_filter('show_admin_bar', [$this, 'filterShowAdminBarInParityPreview'], 999);
        add_action('wp_enqueue_scripts', [$this, 'enqueueParityPreviewHeadFixes'], 1);
    }

    public function registerWidgetFallbackShortcodes(): void
    {
        foreach ($this->widgetCatalog() as $widget) {
            if (! is_array($widget)) {
                continue;
            }
            $shortcodePattern = isset($widget['shortcode']) && is_string($widget['shortcode'])
                ? (string) $widget['shortcode']
                : '';
            $tag = $this->extractShortcodeTag($shortcodePattern);
            if ($tag === '' || shortcode_exists($tag)) {
                continue;
            }

            add_shortcode(
                $tag,
                function ($atts = [], $content = '', $shortcodeTag = '') use ($widget, $tag): string {
                    $resolvedTag = is_string($shortcodeTag) && $shortcodeTag !== '' ? $shortcodeTag : $tag;
                    $safeAtts = is_array($atts) ? $atts : [];
                    $safeContent = is_string($content) ? $content : '';

                    return $this->renderWidgetFallbackShortcode($safeAtts, $safeContent, $resolvedTag, $widget);
                }
            );
        }
    }

    private function extractShortcodeTag(string $pattern): string
    {
        if (preg_match('/\[\s*([a-z0-9_-]+)/i', $pattern, $matches) !== 1) {
            return '';
        }
        $tag = sanitize_key((string) ($matches[1] ?? ''));
        if (! str_starts_with($tag, 'h4_')) {
            return '';
        }
        return $tag;
    }

    /**
     * @param array<string, mixed> $atts
     * @param array<string, mixed> $widget
     */
    private function renderWidgetFallbackShortcode(array $atts, string $content, string $tag, array $widget): string
    {
        $label = sanitize_text_field((string) ($widget['label'] ?? 'Widget'));
        if ($label === '') {
            $label = ucwords(str_replace(['h4_', '_', '-'], ['', ' ', ' '], $tag));
        }
        $category = sanitize_text_field((string) ($widget['category'] ?? 'Help4 Widget'));
        $slug = sanitize_html_class((string) ($widget['slug'] ?? $tag));

        $previewPairs = [];
        foreach ($atts as $key => $value) {
            if (! is_scalar($value)) {
                continue;
            }
            $previewPairs[] = sprintf('%s=%s', sanitize_key((string) $key), sanitize_text_field((string) $value));
            if (count($previewPairs) >= 3) {
                break;
            }
        }
        $settingsPreview = $previewPairs !== [] ? implode(' • ', $previewPairs) : __('Use widget settings to configure output.', 'help4-builder-suite');

        $contentPreview = trim(wp_strip_all_tags($content));
        if ($contentPreview !== '' && mb_strlen($contentPreview) > 160) {
            $contentPreview = mb_substr($contentPreview, 0, 160) . '...';
        }

        ob_start();
        ?>
        <section class="h4bs-widget-fallback h4bs-widget-fallback-<?php echo esc_attr($slug); ?>" data-h4bs-widget="<?php echo esc_attr($tag); ?>">
            <header class="h4bs-widget-fallback__head">
                <strong><?php echo esc_html($label); ?></strong>
                <span><?php echo esc_html($category); ?></span>
            </header>
            <p class="h4bs-widget-fallback__meta"><?php echo esc_html($settingsPreview); ?></p>
            <?php if ($contentPreview !== '') : ?>
                <p class="h4bs-widget-fallback__content"><?php echo esc_html($contentPreview); ?></p>
            <?php endif; ?>
        </section>
        <?php
        return (string) ob_get_clean();
    }

    public function filterShowAdminBarInParityPreview(bool $show): bool
    {
        if ($this->isParityPreviewRequest()) {
            return false;
        }
        return $show;
    }

    public function enqueueParityPreviewHeadFixes(): void
    {
        if (! $this->isParityPreviewRequest()) {
            return;
        }
        wp_register_style('h4bs-parity-preview-head-fixes', false, [], H4BS_VERSION);
        wp_enqueue_style('h4bs-parity-preview-head-fixes');
        wp_add_inline_style(
            'h4bs-parity-preview-head-fixes',
            'html,body{margin-top:0 !important;padding-top:0 !important;}'
            . '#wpadminbar,.wpadminbar{display:none !important;visibility:hidden !important;opacity:0 !important;}'
            . 'html.admin-bar,body.admin-bar{margin-top:0 !important;padding-top:0 !important;}'
        );
    }

    private function isParityPreviewRequest(): bool
    {
        if (is_admin()) {
            return false;
        }
        if (AdminRequest::queryHasValue('h4bs_builder_draft_preview')) {
            $postId = AdminRequest::queryAbsInt('h4bs_post_id');
            $nonce = AdminRequest::queryText('_wpnonce');
            return $postId > 0
                && current_user_can('edit_post', $postId)
                && wp_verify_nonce($nonce, $this->draftPreviewNonceAction($postId));
        }
        $mode = AdminRequest::queryKey('h4bs_preview_mode');
        if ($mode !== 'parity') {
            return false;
        }

        $postId = get_queried_object_id();
        return $postId > 0 && current_user_can('edit_post', $postId);
    }

    public function disableBlockEditorForBuilderMode(bool $useBlockEditor, $post): bool
    {
        if (! $post instanceof WP_Post) {
            return $useBlockEditor;
        }

        if ($this->isBuilderEnabled((int) $post->ID)) {
            return false;
        }

        if (AdminRequest::queryKey('h4bs_builder') === '1' && current_user_can('edit_post', (int) $post->ID)) {
            return false;
        }

        return $useBlockEditor;
    }

    public function registerMetaBox(string $postType): void
    {
        if (! post_type_supports($postType, 'editor')) {
            return;
        }
        if (in_array($postType, ['attachment', 'revision', 'nav_menu_item'], true)) {
            return;
        }

        add_meta_box(
            'h4bs-builder',
            __('Help4 Builder', 'help4-builder-suite'),
            [$this, 'renderMetaBox'],
            $postType,
            'normal',
            'high'
        );
    }

    public function renderMetaBox(WP_Post $post): void
    {
        wp_nonce_field('h4bs_builder_meta', 'h4bs_builder_meta_nonce');

        $mode = (string) get_post_meta($post->ID, '_h4_builder_mode', true);
        if (! in_array($mode, ['wp', 'help4'], true)) {
            $mode = 'wp';
        }

        $enabled = (string) get_post_meta($post->ID, '_h4_builder_enabled', true);
        $layout = (string) get_post_meta($post->ID, '_h4_builder_layout', true);
        $quarantinedLegacyStarter = $this->isLegacyAccidentalStarterLayout((int) $post->ID);
        if ($quarantinedLegacyStarter) {
            $mode = 'wp';
            $enabled = '0';
        }
        $explicitBuilderRequest = AdminRequest::queryKey('h4bs_builder') === '1';
        if ($layout === '' && ($explicitBuilderRequest || ($enabled === '1' && $mode === 'help4'))) {
            $layout = wp_json_encode($this->defaultLayout(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
        }
        if ($layout === '') {
            $layout = '[]';
        }

        $pageLayout = $this->pageLayoutForPost((int) $post->ID);
        $pageTitleMode = $this->pageTitleModeForPost((int) $post->ID);
        $pageCss = $this->pageCustomCss((int) $post->ID);
        $pageLayoutOptions = $this->pageLayoutOptions();
        ?>
        <input type="hidden" name="h4_builder_intent" value="0" data-h4bs-builder-intent>
        <?php if ($quarantinedLegacyStarter) : ?>
            <div class="notice notice-warning inline">
                <p><?php esc_html_e('Builder found an old starter document on this custom content item. The site or add-on template remains in control until you deliberately open and save this item in Builder Studio.', 'help4-builder-suite'); ?></p>
            </div>
        <?php endif; ?>
        <p>
            <label for="h4_builder_mode"><strong><?php esc_html_e('Editor Mode', 'help4-builder-suite'); ?></strong></label><br>
            <select id="h4_builder_mode" name="h4_builder_mode">
                <option value="wp" <?php selected($mode, 'wp'); ?>><?php esc_html_e('WordPress Editor', 'help4-builder-suite'); ?></option>
                <option value="help4" <?php selected($mode, 'help4'); ?>><?php esc_html_e('Help4 Drag/Drop Builder', 'help4-builder-suite'); ?></option>
            </select>
            <label for="h4_builder_page_layout" style="margin-left:1rem;"><strong><?php esc_html_e('Page Layout', 'help4-builder-suite'); ?></strong></label>
            <select id="h4_builder_page_layout" name="h4_builder_page_layout">
                <?php foreach ($pageLayoutOptions as $layoutValue => $layoutLabel) : ?>
                    <option value="<?php echo esc_attr($layoutValue); ?>" <?php selected($pageLayout, $layoutValue); ?>>
                        <?php echo esc_html($layoutLabel); ?>
                    </option>
                <?php endforeach; ?>
            </select>
            <label for="h4_builder_page_title" style="margin-left:1rem;"><strong><?php esc_html_e('Page Title', 'help4-builder-suite'); ?></strong></label>
            <select id="h4_builder_page_title" name="h4_builder_page_title">
                <?php foreach ($this->pageTitleModeOptions() as $titleModeValue => $titleModeLabel) : ?>
                    <option value="<?php echo esc_attr($titleModeValue); ?>" <?php selected($pageTitleMode, $titleModeValue); ?>><?php echo esc_html($titleModeLabel); ?></option>
                <?php endforeach; ?>
            </select>
            <label style="margin-left:1rem;">
                <input type="checkbox" name="h4_builder_enabled" value="1" <?php checked($enabled === '1'); ?>>
                <?php esc_html_e('Enable Help4 Builder Rendering', 'help4-builder-suite'); ?>
            </label>
        </p>
        <p>
            <a class="button button-primary" href="<?php echo esc_url($this->builderActionUrl((int) $post->ID)); ?>">
                <?php esc_html_e('Launch Help4 WordPress Builder', 'help4-builder-suite'); ?>
            </a>
            <span class="description" style="margin-left:8px;">
                <?php esc_html_e('Designer-friendly drag-and-drop interface with live preview and viewport presets.', 'help4-builder-suite'); ?>
            </span>
        </p>

        <div id="h4bs-builder-panel" class="h4bs-builder-admin" data-h4bs-builder-admin>
            <div class="h4bs-builder-palette">
                <button type="button" class="button button-secondary" data-h4bs-add-block="heading">Heading</button>
                <button type="button" class="button button-secondary" data-h4bs-add-block="text">Text</button>
                <button type="button" class="button button-secondary" data-h4bs-add-block="image">Image</button>
                <button type="button" class="button button-secondary" data-h4bs-add-block="button">Button</button>
                <button type="button" class="button button-secondary" data-h4bs-add-block="shortcode">Shortcode</button>
                <button type="button" class="button button-secondary" data-h4bs-add-block="slider">Slider</button>
                <button type="button" class="button button-secondary" data-h4bs-add-block="html">HTML</button>
            </div>
            <p>
                <strong><?php esc_html_e('Quick start:', 'help4-builder-suite'); ?></strong>
                <?php esc_html_e('Use "Launch Help4 WordPress Builder" above for the drag-and-drop interface. The JSON area below is advanced mode only.', 'help4-builder-suite'); ?>
            </p>
            <p><?php esc_html_e('Designers should use the visual builder. Developers can use JSON for bulk import/export workflows.', 'help4-builder-suite'); ?></p>
            <p class="h4bs-builder-estimate-line">
                <strong><?php esc_html_e('Estimated Total Page Load', 'help4-builder-suite'); ?>:</strong>
                <span data-h4bs-builder-estimate><?php esc_html_e('Calculating...', 'help4-builder-suite'); ?></span>
            </p>
        </div>

        <p>
            <label for="h4_builder_layout"><strong><?php esc_html_e('Layout JSON', 'help4-builder-suite'); ?></strong></label>
            <textarea id="h4_builder_layout" name="h4_builder_layout" rows="16" style="width:100%;font-family:Consolas,Menlo,monospace;"><?php echo esc_textarea($layout); ?></textarea>
        </p>
        <p>
            <label for="h4_builder_custom_css"><strong><?php esc_html_e('Page / Template Custom CSS', 'help4-builder-suite'); ?></strong></label>
            <textarea id="h4_builder_custom_css" name="h4_builder_custom_css" rows="10" style="width:100%;font-family:Consolas,Menlo,monospace;"><?php echo esc_textarea($pageCss); ?></textarea>
            <span class="description"><?php esc_html_e('Use :builder for this Builder root. Media queries, pseudo-elements, keyframes, and reduced-motion rules are supported.', 'help4-builder-suite'); ?></span>
        </p>
        <?php
    }

    public function saveBuilderSettings(int $postId, WP_Post $post): void
    {
        $postData = is_array($_POST) ? wp_unslash($_POST) : [];
        if (! isset($postData['h4bs_builder_meta_nonce'])) {
            return;
        }
        if (! wp_verify_nonce(sanitize_text_field((string) $postData['h4bs_builder_meta_nonce']), 'h4bs_builder_meta')) {
            return;
        }
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }
        if (! current_user_can('edit_post', $postId)) {
            return;
        }
        if ($post->post_type === 'revision') {
            return;
        }

        $storedMode = sanitize_key((string) get_post_meta($postId, '_h4_builder_mode', true));
        if (! in_array($storedMode, ['wp', 'help4'], true)) {
            $storedMode = 'wp';
        }
        $storedEnabled = (string) get_post_meta($postId, '_h4_builder_enabled', true) === '1';
        $storedLayout = $this->sanitizeLayout($this->layoutForPost($postId));
        $quarantinedLegacyStarter = $this->isLegacyAccidentalStarterLayout($postId);
        $storedPageLayout = $this->pageLayoutForPost($postId);
        $storedPageTitleMode = $this->pageTitleModeForPost($postId);
        $storedPageCss = $this->pageCustomCss($postId);

        $mode = isset($postData['h4_builder_mode']) ? sanitize_key((string) $postData['h4_builder_mode']) : $storedMode;
        if (! in_array($mode, ['wp', 'help4'], true)) {
            $mode = 'wp';
        }
        $enabled = ! empty($postData['h4_builder_enabled']);
        $pageLayout = $this->normalizePageLayout(
            isset($postData['h4_builder_page_layout']) ? (string) $postData['h4_builder_page_layout'] : $storedPageLayout
        );
        $pageTitleMode = $this->normalizePageTitleMode(
            isset($postData['h4_builder_page_title']) ? (string) $postData['h4_builder_page_title'] : $storedPageTitleMode
        );
        $rawLayout = isset($postData['h4_builder_layout']) ? (string) $postData['h4_builder_layout'] : '[]';
        $layout = $this->sanitizeLayout($rawLayout);
        $rawCss = isset($postData['h4_builder_custom_css']) ? (string) $postData['h4_builder_custom_css'] : '';
        $pageCss = $this->sanitizePageCustomCss($rawCss);
        $explicitIntent = isset($postData['h4_builder_intent']) && (string) $postData['h4_builder_intent'] === '1';

        // Quarantined fields are deliberately rendered as WordPress/disabled.
        // A passive post update must preserve the original metadata byte for
        // byte; only a deliberate Builder control change may adopt it.
        if ($quarantinedLegacyStarter && ! $explicitIntent) {
            return;
        }

        $builderFieldsChanged = $storedMode !== $mode
            || $storedEnabled !== $enabled
            || $storedPageLayout !== $pageLayout
            || $storedPageTitleMode !== $pageTitleMode
            || ! hash_equals($this->builderValueHash($storedLayout), $this->builderValueHash($layout))
            || ! hash_equals($this->builderValueHash($storedPageCss), $this->builderValueHash($pageCss));

        // A normal WordPress update includes this meta box's nonce and fields.
        // Do not let that passive form submission create a Builder document.
        if (! $explicitIntent && ! $builderFieldsChanged) {
            return;
        }

        if ($enabled && ! $layout && ! $storedLayout) {
            $layout = $this->sanitizeLayout($this->defaultLayout());
        }

        update_post_meta($postId, '_h4_builder_mode', $mode);
        update_post_meta($postId, '_h4_builder_page_layout', $pageLayout);
        update_post_meta($postId, '_h4_builder_page_title', $pageTitleMode);
        update_post_meta($postId, '_h4_builder_enabled', $enabled ? '1' : '0');
        update_post_meta($postId, '_h4_builder_layout', wp_slash((string) wp_json_encode($layout, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)));
        update_post_meta($postId, self::DOCUMENT_ORIGIN_META, 'post_editor');
        $this->persistPageCustomCss($postId, $pageCss);
    }

    public function enqueueFrontendAssets(): void
    {
        $layoutPostIds = $this->builderOutputPostIdsForCurrentRequest();
        if (! $layoutPostIds) {
            return;
        }

        $layouts = [];
        foreach ($layoutPostIds as $layoutPostId) {
            $layouts[$layoutPostId] = $this->sanitizeLayout($this->layoutForPost($layoutPostId));
        }

        $frontendDependencies = [];
        if ($this->layoutsUseDashicons($layouts)) {
            wp_enqueue_style('dashicons');
            $frontendDependencies[] = 'dashicons';
        }
        wp_enqueue_style(
            'h4bs-frontend',
            H4BS_URL . 'assets/css/frontend.css',
            $frontendDependencies,
            H4BS_VERSION
        );
        wp_enqueue_style(
            'h4bs-builder',
            H4BS_URL . 'assets/css/builder.css',
            ['h4bs-frontend'],
            H4BS_VERSION
        );
        wp_enqueue_style(
            'h4bs-builder-module-skins',
            H4BS_URL . 'assets/css/builder-module-skins.css',
            ['h4bs-builder'],
            H4BS_VERSION
        );
        wp_enqueue_script(
            'h4bs-builder-frontend',
            H4BS_URL . 'assets/js/builder-frontend.js',
            [],
            H4BS_VERSION,
            true
        );
        foreach ($this->moduleSkinCatalog() as $moduleContract) {
            if (! $this->moduleContractAppliesToLayouts($moduleContract, $layouts, $layoutPostIds)) {
                continue;
            }
            $styleHandle = sanitize_key((string) ($moduleContract['style_handle'] ?? ''));
            if ($styleHandle !== '' && wp_style_is($styleHandle, 'registered')) {
                wp_enqueue_style($styleHandle);
            }
            foreach ((array) ($moduleContract['script_handles'] ?? []) as $scriptHandleRaw) {
                $scriptHandle = sanitize_key((string) $scriptHandleRaw);
                if ($scriptHandle !== '' && wp_script_is($scriptHandle, 'registered')) {
                    wp_enqueue_script($scriptHandle);
                }
            }
        }
        foreach ($layouts as $layout) {
            $this->enqueueTypographyAssets($layout, false);
        }
        $customCss = $this->compiledCustomCssForPosts($layoutPostIds);
        if ($customCss !== '') {
            wp_add_inline_style('h4bs-builder-module-skins', $customCss);
        }
    }

    /**
     * @return array<int, int>
     */
    private function builderOutputPostIdsForCurrentRequest(): array
    {
        $postIds = [];
        if ($this->previewContextActive && $this->previewContextPostId > 0) {
            $postIds[] = $this->previewContextPostId;
        } elseif (is_singular()) {
            $queriedId = get_queried_object_id();
            if ($queriedId > 0 && $this->isBuilderEnabled($queriedId)) {
                $postIds[] = $queriedId;
            }
        }

        $assigned = apply_filters('h4bs_builder_assigned_template_ids', []);
        if (is_array($assigned)) {
            foreach ($assigned as $templateIdRaw) {
                $templateId = absint($templateIdRaw);
                if ($templateId > 0 && $this->isBuilderEnabled($templateId)) {
                    $postIds[] = $templateId;
                }
            }
        }

        return array_values(array_unique(array_filter(array_map('absint', $postIds))));
    }

    /**
     * @param array<string, mixed> $contract
     * @param array<int, array<int, array<string, mixed>>> $layouts
     * @param array<int, int> $layoutPostIds
     */
    private function moduleContractAppliesToLayouts(array $contract, array $layouts, array $layoutPostIds): bool
    {
        $shortcodeTags = array_values(
            array_filter(array_map('sanitize_key', (array) ($contract['shortcode_tags'] ?? [])))
        );
        $active = $shortcodeTags === [];
        if (! $active) {
            foreach ($layouts as $layout) {
                if ($this->valueContainsShortcodeTags($layout, $shortcodeTags)) {
                    $active = true;
                    break;
                }
            }
        }

        return (bool) apply_filters(
            'h4bs_builder_module_contract_active',
            $active,
            $contract,
            $layoutPostIds,
            $layouts
        );
    }

    /**
     * @param mixed $value
     * @param array<int, string> $shortcodeTags
     */
    private function valueContainsShortcodeTags($value, array $shortcodeTags): bool
    {
        if (is_string($value)) {
            foreach ($shortcodeTags as $shortcodeTag) {
                if ($shortcodeTag !== '' && has_shortcode($value, $shortcodeTag)) {
                    return true;
                }
            }
            return false;
        }
        if (! is_array($value)) {
            return false;
        }
        foreach ($value as $nestedValue) {
            if ($this->valueContainsShortcodeTags($nestedValue, $shortcodeTags)) {
                return true;
            }
        }
        return false;
    }

    public function enqueueAdminAssets(string $hook): void
    {
        if (! in_array($hook, ['post.php', 'post-new.php'], true)) {
            return;
        }

        wp_enqueue_style(
            'h4bs-builder-admin',
            H4BS_URL . 'assets/css/builder-admin.css',
            [],
            H4BS_VERSION
        );

        wp_enqueue_script(
            'h4bs-builder-admin',
            H4BS_URL . 'assets/js/builder-admin.js',
            [],
            H4BS_VERSION,
            true
        );
    }

    public function renderBuilderContent(string $content): string
    {
        if (is_admin() || ! is_singular() || ! in_the_loop()) {
            return $content;
        }

        $postId = get_queried_object_id();
        if (! $postId || ! $this->isBuilderEnabled($postId)) {
            return $content;
        }

        $loopPostId = absint(get_the_ID());
        if ($loopPostId > 0 && $loopPostId !== absint($postId)) {
            return $content;
        }

        if (preg_match('/\bclass=["\'][^"\']*\bh4bs-builder-page\b[^"\']*["\']/i', $content) === 1) {
            return $content;
        }

        $layout = $this->layoutForPost($postId);
        if (! $layout) {
            return $content;
        }
        $layout = $this->sanitizeLayout($layout);
        $pageLayout = $this->pageLayoutForPost($postId);

        ob_start();
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Scoped declarations are assembled from sanitized Builder settings.
        echo $this->buildLayoutDesignVariantStyles($layout);
        $globalStyle = $this->globalDesignStyle();
        echo '<div class="h4bs-builder-page h4bs-builder-page-layout-' . esc_attr($pageLayout) . '" data-h4bs-layout-id="' . esc_attr((string) $postId) . '" data-h4bs-version="' . esc_attr(H4BS_VERSION) . '" data-h4bs-build="' . esc_attr(H4BS_BUILD_HASH) . '"';
        if ($globalStyle !== '') {
            echo ' style="' . esc_attr($globalStyle) . '"';
        }
        echo '>';
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- renderLayoutBlocks scopes context and renderBlock sanitizes controlled Builder markup.
        echo $this->renderLayoutBlocks($layout, $postId);
        echo '</div>';
        return (string) ob_get_clean();
    }

    /**
     * @param array<string, mixed> $atts
     */
    public function renderBuilderShortcode(array $atts): string
    {
        $atts = shortcode_atts(['id' => 0], $atts, 'h4_builder');
        $postId = absint($atts['id']);
        if (! $postId) {
            $postId = get_the_ID();
        }
        if (! $postId) {
            return '';
        }

        $layout = $this->layoutForPost($postId);
        if (! $layout) {
            return '';
        }
        $layout = $this->sanitizeLayout($layout);
        $pageLayout = $this->pageLayoutForPost($postId);

        ob_start();
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Scoped declarations are assembled from sanitized Builder settings.
        echo $this->buildLayoutDesignVariantStyles($layout);
        $globalStyle = $this->globalDesignStyle();
        echo '<div class="h4bs-builder-page h4bs-builder-page-layout-' . esc_attr($pageLayout) . '" data-h4bs-layout-id="' . esc_attr((string) $postId) . '" data-h4bs-version="' . esc_attr(H4BS_VERSION) . '" data-h4bs-build="' . esc_attr(H4BS_BUILD_HASH) . '"';
        if ($globalStyle !== '') {
            echo ' style="' . esc_attr($globalStyle) . '"';
        }
        echo '>';
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- renderLayoutBlocks scopes context and renderBlock sanitizes controlled Builder markup.
        echo $this->renderLayoutBlocks($layout, $postId);
        echo '</div>';
        return (string) ob_get_clean();
    }

    public function registerBuilderPagesMenu(): void
    {
        add_submenu_page(
            'h4bs-settings',
            __('Page design', 'help4-builder-suite'),
            __('Page design', 'help4-builder-suite'),
            'edit_pages',
            'h4bs-builder-pages',
            [$this, 'redirectBuilderPagesToWorkHome']
        );
    }

    public function redirectBuilderPagesToWorkHome(): void
    {
        $target = admin_url('admin.php?page=h4bs-settings#h4bs-your-pages');
        echo '<div class="wrap"><p>' . esc_html__('Opening the unified Page design workspace…', 'help4-builder-suite') . '</p></div><script>window.location.replace(' . wp_json_encode($target) . ');</script>';
        exit;
    }

    public function registerBuilderStudioPage(): void
    {
        $hook = add_submenu_page(
            null,
            __('Help4 WordPress Builder', 'help4-builder-suite'),
            __('Help4 WordPress Builder', 'help4-builder-suite'),
            'edit_posts',
            'h4bs-builder-studio',
            [$this, 'renderBuilderStudioPage']
        );

        if (is_string($hook)) {
            $this->builderStudioHook = $hook;
        }
    }

    public function enqueueBuilderStudioAssets(string $hook): void
    {
        if ($hook !== $this->builderStudioHook) {
            return;
        }

        $postId = AdminRequest::queryAbsInt('post');
        if ($postId <= 0 || ! current_user_can('edit_post', $postId)) {
            return;
        }
        $hasSavedLayout = metadata_exists('post', $postId, '_h4_builder_layout');

        wp_enqueue_style(
            'h4bs-builder-studio',
            H4BS_URL . 'assets/css/builder-studio.css',
            [],
            H4BS_VERSION
        );
        wp_enqueue_style(
            'h4bs-builder-studio-redesign',
            H4BS_URL . 'assets/css/builder-studio-redesign.css',
            ['h4bs-builder-studio'],
            H4BS_VERSION
        );
        // Keep live preview parity with frontend output.
        wp_enqueue_style(
            'h4bs-builder-preview-base',
            H4BS_URL . 'assets/css/builder.css',
            [],
            H4BS_VERSION
        );
        wp_enqueue_style(
            'h4bs-builder-preview-frontend',
            H4BS_URL . 'assets/css/frontend.css',
            [],
            H4BS_VERSION
        );
        wp_enqueue_style(
            'h4bs-builder-preview-module-skins',
            H4BS_URL . 'assets/css/builder-module-skins.css',
            ['h4bs-builder-preview-base'],
            H4BS_VERSION
        );

        $activeTheme = wp_get_theme();
        if ($activeTheme instanceof \WP_Theme) {
            $stylesheetUri = get_stylesheet_uri();
            if (is_string($stylesheetUri) && $stylesheetUri !== '') {
                wp_enqueue_style(
                    'h4bs-builder-preview-theme-style',
                    $stylesheetUri,
                    [],
                    (string) $activeTheme->get('Version')
                );
            }

            if ($activeTheme->get_stylesheet() !== $activeTheme->get_template()) {
                $templateStyle = trailingslashit(get_template_directory_uri()) . 'style.css';
                if (is_string($templateStyle) && $templateStyle !== '') {
                    wp_enqueue_style(
                        'h4bs-builder-preview-theme-template-style',
                        $templateStyle,
                        [],
                        (string) $activeTheme->get('Version')
                    );
                }
            }
        }
        wp_enqueue_media();
        wp_enqueue_style('dashicons');
        wp_enqueue_style(
            'h4bs-fontawesome-free',
            H4BS_URL . 'assets/vendor/fontawesome/css/help4-fa-local.css',
            [],
            H4BS_VERSION
        );
        VideoPlayer::enqueueAssets(true);

        wp_enqueue_script(
            'h4bs-builder-studio',
            H4BS_URL . 'assets/js/builder-studio.js',
            ['jquery', 'jquery-ui-sortable', 'jquery-ui-draggable'],
            H4BS_VERSION,
            true
        );
        wp_enqueue_script(
            'h4bs-slider',
            H4BS_URL . 'assets/js/slider.js',
            [],
            H4BS_VERSION,
            true
        );
        wp_enqueue_script(
            'h4bs-search',
            H4BS_URL . 'assets/js/search.js',
            [],
            H4BS_VERSION,
            true
        );
        wp_enqueue_script(
            'h4bs-builder-frontend',
            H4BS_URL . 'assets/js/builder-frontend.js',
            [],
            H4BS_VERSION,
            true
        );

        $layout = $this->layoutForPost($postId);
        if (! $layout) {
            $layout = $this->defaultLayout();
        }
        $layout = $this->sanitizeLayout($layout);
        $this->enqueueTypographyAssets($layout, true);
        $postType = (string) get_post_type($postId);
        $postStatus = (string) get_post_status($postId);
        $postStatusObject = get_post_status_object($postStatus);
        $postTypeObject = get_post_type_object($postType);
        $publishCapability = $postTypeObject instanceof \WP_Post_Type && isset($postTypeObject->cap->publish_posts)
            ? (string) $postTypeObject->cap->publish_posts
            : 'publish_posts';
        $pageLayout = $this->pageLayoutForPost($postId);
        $previewTemplateSlots = $this->previewTemplateSlots($postId, $postType, $pageLayout);
        $initialDraft = $this->draftPreviewState(
            $postId,
            $layout,
            $pageLayout,
            $this->pageTitleModeForPost($postId),
            $this->pageCustomCss($postId),
            $this->globalDesignTokens()
        );
        set_transient($this->draftPreviewTransientKey($postId), $initialDraft, 10 * MINUTE_IN_SECONDS);
        $initialDraftUrl = $this->draftPreviewUrl($postId, (string) $initialDraft['fingerprint']);

        wp_localize_script(
            'h4bs-builder-studio',
            'h4bsBuilderStudioData',
            [
                'postId'          => $postId,
                'postTitle'       => get_the_title($postId),
                'postType'        => $postType,
                'postStatus'      => $postStatus,
                'postStatusLabel' => $postStatusObject instanceof \stdClass && isset($postStatusObject->label)
                    ? (string) $postStatusObject->label
                    : ucfirst($postStatus ?: 'draft'),
                'canPublish'      => $publishCapability !== '' && current_user_can($publishCapability),
                'isTemplate'      => $postType === 'h4_template',
                'isFreshPage'     => ! $hasSavedLayout,
                'copySources'     => $this->builderCopySources($postId),
                'layout'          => $layout,
                'layoutHash'      => $this->builderValueHash($layout),
                'pageLayout'      => $pageLayout,
                'pageTitleMode'   => $this->pageTitleModeForPost($postId),
                'pageTitleModeOptions' => $this->pageTitleModeOptions(),
                'pageTitleVisible' => $this->shouldShowPageTitle($postId, $layout, $pageLayout),
                'pageCss'         => $this->pageCustomCss($postId),
                'pageCssHash'     => $this->builderValueHash($this->pageCustomCss($postId)),
                'pageLayoutOptions' => $this->pageLayoutOptions(),
                'globalDesignTokens' => $this->globalDesignTokens(),
                'globalDesignRevision' => $this->globalDesignRevision(),
                'globalDesignHash' => $this->globalDesignHash(),
                'templateConditions' => $this->loadTemplateConditions($postId),
                'templateRoles'   => $this->templateRoleCatalog(),
                'templateScopes'  => $this->templateScopeCatalog(),
                'previewTemplateSlots' => $previewTemplateSlots,
                'assignedTemplateEditors' => $this->assignedTemplateEditors($previewTemplateSlots),
                'ajaxUrl'         => admin_url('admin-ajax.php'),
                'nonce'           => wp_create_nonce('h4bs_builder_studio_save'),
                'previewUrl'      => get_permalink($postId) ?: '',
                'draftPreviewUrl' => $initialDraftUrl,
                'wpEditorUrl'     => get_edit_post_link($postId, '') ?: '',
                'builderStudioBaseUrl' => admin_url('admin.php?page=h4bs-builder-studio&post='),
                'wpEditorBaseUrl' => admin_url('post.php?action=edit&post='),
                'defaultType'     => 'heading',
                'blockTypes'      => $this->blockTypeCatalog(),
                'templatePresets' => [],
                'siteKits'        => [],
                'themePacks'      => [],
                'customThemePacks' => $this->customThemePacks(),
                'moduleCatalog'   => $this->moduleSkinCatalog(),
                'previewAssetManifest' => $this->previewAssetManifest(),
                'defaultIcons'    => [],
                'activeThemePack' => sanitize_key((string) get_option('h4bs_active_theme_pack', '')),
                'widgetCatalog'   => $this->widgetCatalog(),
                'helpCatalog'     => $this->builderDocumentationCatalog(),
                'deferredCatalogs' => ['templates', 'site-kits', 'themes'],
                'viewportPresets' => $this->viewportPresets(),
                'defaultViewport' => 'fhd_1080p',
                'help4Version'    => H4BS_VERSION,
                'buildHash'       => H4BS_BUILD_HASH,
                'layoutSchemaVersion' => '1.0',
                'basePageLoadMs'  => max(250, (int) get_option('h4bs_page_load_baseline_ms', 900)),
            ]
        );
    }

    public function builderStudioBodyClass(string $classes): string
    {
        if (! is_admin()) {
            return $classes;
        }
        $page = AdminRequest::queryKey('page');
        if ($page !== 'h4bs-builder-studio') {
            return $classes;
        }
        return trim($classes . ' h4bs-builder-studio-body');
    }

    public function setBuilderStudioAdminTitle($screen): void
    {
        if (! is_admin() || AdminRequest::queryKey('page') !== 'h4bs-builder-studio') {
            return;
        }

        $GLOBALS['title'] = __('Help4 WordPress Builder', 'help4-builder-suite');
    }

    /**
     * @param array<int, string> $classes
     * @return array<int, string>
     */
    public function builderPageBodyClasses(array $classes): array
    {
        if (is_admin()) {
            return $classes;
        }

        $layoutPostIds = $this->builderOutputPostIdsForCurrentRequest();
        if (! $layoutPostIds) {
            return $classes;
        }

        $layout = 'template';
        $postId = get_queried_object_id();
        if (is_singular() && $postId > 0 && $this->isBuilderEnabled($postId)) {
            $layout = $this->pageLayoutForPost($postId);
        }
        $classes[] = 'h4bs-builder-active';
        $classes[] = 'h4bs-page-layout-' . sanitize_html_class($layout);
        if (is_singular() && $postId > 0 && $this->isBuilderEnabled($postId)) {
            $classes[] = $this->shouldShowPageTitle($postId)
                ? 'h4bs-page-title-visible'
                : 'h4bs-page-title-hidden';
        }

        $filtered = array_values(
            array_unique(
                array_filter(
                    array_map(
                        static function ($className): string {
                            return sanitize_html_class((string) $className);
                        },
                        $classes
                    )
                )
            )
        );

        return $filtered;
    }

    public function renderBuilderStudioPage(): void
    {
        $postId = AdminRequest::queryAbsInt('post');
        if ($postId <= 0) {
            wp_die(esc_html__('Missing page id.', 'help4-builder-suite'));
        }
        if (! current_user_can('edit_post', $postId)) {
            wp_die(esc_html__('You do not have permission to edit this page.', 'help4-builder-suite'));
        }

        $post = get_post($postId);
        if (! $post instanceof WP_Post) {
            wp_die(esc_html__('Page not found.', 'help4-builder-suite'));
        }
        $postType = (string) $post->post_type;
        $isTemplate = $postType === 'h4_template';
        $postStatus = (string) get_post_status($postId);
        $postStatusObject = get_post_status_object($postStatus);
        $postStatusLabel = $postStatusObject instanceof \stdClass && isset($postStatusObject->label)
            ? (string) $postStatusObject->label
            : ucfirst($postStatus ?: 'draft');
        $postTypeObject = get_post_type_object($postType);
        $publishCapability = $postTypeObject instanceof \WP_Post_Type && isset($postTypeObject->cap->publish_posts)
            ? (string) $postTypeObject->cap->publish_posts
            : 'publish_posts';
        $canPublish = $publishCapability !== '' && current_user_can($publishCapability);
        $saveActionLabel = match ($postStatus) {
            'publish' => __('Update Live', 'help4-builder-suite'),
            'private' => __('Update Private', 'help4-builder-suite'),
            default   => __('Save Draft', 'help4-builder-suite'),
        };
        $reviewPreviewUrl = $postStatus === 'publish'
            ? (get_permalink($postId) ?: '#')
            : (get_preview_post_link($post) ?: (get_permalink($postId) ?: '#'));
        $exitUrl = $isTemplate
            ? admin_url('admin.php?page=h4bs-theme-builder')
            : admin_url('edit.php?post_type=page');
        $pageLayout = $this->pageLayoutForPost($postId);
        $pageLayoutOptions = $this->pageLayoutOptions();
        $pageLayoutLabel = isset($pageLayoutOptions[$pageLayout]) ? (string) $pageLayoutOptions[$pageLayout] : (string) $pageLayoutOptions['default'];
        $documentation = $this->builderDocumentationCatalog();
        $documentationHubUrl = (string) ($documentation['hub']['url'] ?? 'https://help4wordpress.com/help4-builder-tutorials/');
        $documentationQuickLinks = isset($documentation['quick_links']) && is_array($documentation['quick_links'])
            ? $documentation['quick_links']
            : [];
        $documentationQuickLinkIcons = [
            'click-to-edit'   => 'dashicons-edit-large',
            'first-page'      => 'dashicons-welcome-add-page',
            'editing'         => 'dashicons-edit-page',
            'find-settings'   => 'dashicons-search',
            'responsive'      => 'dashicons-smartphone',
            'structure'       => 'dashicons-columns',
            'navigator'       => 'dashicons-editor-ol',
            'templates'       => 'dashicons-layout',
            'beginner-templates' => 'dashicons-screenoptions',
            'site-kits'       => 'dashicons-admin-multisite',
            'globals'         => 'dashicons-admin-appearance',
            'header-footer'   => 'dashicons-menu-alt3',
            'images'          => 'dashicons-format-image',
            'video'           => 'dashicons-video-alt3',
            'forms'           => 'dashicons-feedback',
            'dynamic-data'    => 'dashicons-database',
            'custom-css'      => 'dashicons-editor-code',
            'commerce'        => 'dashicons-cart',
            'seo'             => 'dashicons-chart-line',
            'accessibility'   => 'dashicons-universal-access-alt',
            'recovery'        => 'dashicons-backup',
            'publishing'      => 'dashicons-cloud-upload',
            'email-campaign'  => 'dashicons-email-alt',
            'email-reports'   => 'dashicons-chart-bar',
            'quick-help'      => 'dashicons-editor-help',
            'troubleshooting' => 'dashicons-sos',
        ];
        $documentationContextUrl = static function (string $context) use ($documentation, $documentationHubUrl): string {
            $entry = $documentation['contexts'][$context] ?? [];
            return is_array($entry) && ! empty($entry['url']) ? (string) $entry['url'] : $documentationHubUrl;
        };
        ?>
        <div class="h4bs-builder-studio-wrap">
            <div class="h4bs-builder-studio-header">
                <div class="h4bs-builder-brand-lockup">
                    <img src="<?php echo esc_url(H4BS_URL . 'assets/images/help4-favicon.svg'); ?>" alt="" width="32" height="32">
                    <div>
                        <h1><?php esc_html_e('Help4 Builder', 'help4-builder-suite'); ?></h1>
                        <p>
                            <?php
                            printf(
                                /* translators: %s: page title */
                                esc_html__('Editing %s', 'help4-builder-suite'),
                                esc_html(get_the_title($postId) ?: __('(no title)', 'help4-builder-suite'))
                            );
                            ?>
                        </p>
                    </div>
                </div>
                <div class="h4bs-builder-studio-header-actions">
                    <span class="h4bs-page-status-badge h4bs-page-status-badge--<?php echo esc_attr(sanitize_html_class($postStatus ?: 'draft')); ?>" data-h4bs-page-status>
                        <?php
                        printf(
                            /* translators: %s: current WordPress publication status. */
                            esc_html__('Page: %s', 'help4-builder-suite'),
                            esc_html($postStatusLabel)
                        );
                        ?>
                    </span>
                    <a class="button h4bs-header-icon-btn" href="<?php echo esc_url($exitUrl); ?>" data-h4bs-save-before-leave="exit" title="<?php esc_attr_e('Exit Builder', 'help4-builder-suite'); ?>">
                        <span class="dashicons dashicons-arrow-left-alt2" aria-hidden="true"></span>
                        <span class="h4bs-compact-label" aria-hidden="true"><?php esc_html_e('Exit', 'help4-builder-suite'); ?></span>
                        <span class="screen-reader-text"><?php esc_html_e('Exit Builder', 'help4-builder-suite'); ?></span>
                    </a>
                    <a class="button h4bs-header-icon-btn" href="<?php echo esc_url(get_edit_post_link($postId, '') ?: admin_url('post.php?post=' . $postId . '&action=edit')); ?>" data-h4bs-save-before-leave="wordpress-editor" title="<?php esc_attr_e('Edit words and images; page design stays unchanged', 'help4-builder-suite'); ?>">
                        <span class="dashicons dashicons-edit" aria-hidden="true"></span>
                        <span class="h4bs-compact-label" aria-hidden="true"><?php esc_html_e('Content', 'help4-builder-suite'); ?></span>
                        <span class="screen-reader-text"><?php esc_html_e('Edit words and images; page design stays unchanged', 'help4-builder-suite'); ?></span>
                    </a>
                    <details class="h4bs-builder-help-menu" data-h4bs-quick-help>
                        <summary class="button h4bs-header-icon-btn" data-h4bs-quick-help-toggle aria-controls="h4bs-builder-quick-help" aria-expanded="false" title="<?php esc_attr_e('Open Quick Help', 'help4-builder-suite'); ?>">
                            <span class="dashicons dashicons-editor-help" aria-hidden="true"></span>
                            <span class="h4bs-compact-label" aria-hidden="true"><?php esc_html_e('Help', 'help4-builder-suite'); ?></span>
                            <span class="screen-reader-text"><?php esc_html_e('Open Quick Help', 'help4-builder-suite'); ?></span>
                        </summary>
                        <div id="h4bs-builder-quick-help" class="h4bs-builder-quick-help-panel" aria-labelledby="h4bs-builder-quick-help-title">
                            <div class="h4bs-builder-quick-help-head">
                                <strong id="h4bs-builder-quick-help-title"><?php esc_html_e('Quick Help', 'help4-builder-suite'); ?></strong>
                                <a href="<?php echo esc_url($documentationHubUrl); ?>" target="_blank" rel="noopener">
                                    <?php esc_html_e('All tutorials', 'help4-builder-suite'); ?>
                                    <span class="dashicons dashicons-external" aria-hidden="true"></span>
                                </a>
                            </div>
                            <?php
                            $quickHelpGuideCount = count($documentationQuickLinks);
                            /* translators: %d: total number of Quick Help task guides. */
                            $quickHelpStatusAll = __('%d task guides available.', 'help4-builder-suite');
                            /* translators: %d: number of matching Quick Help task guides. */
                            $quickHelpStatusOne = __('%d task guide found.', 'help4-builder-suite');
                            /* translators: %d: number of matching Quick Help task guides. */
                            $quickHelpStatusMany = __('%d task guides found.', 'help4-builder-suite');
                            /* translators: %d: total number of Quick Help task guides. */
                            $quickHelpAvailablePattern = _n('%d task guide available.', '%d task guides available.', $quickHelpGuideCount, 'help4-builder-suite');
                            $quickHelpAvailableStatus = sprintf($quickHelpAvailablePattern, $quickHelpGuideCount);
                            ?>
                            <div class="h4bs-builder-quick-help-search">
                                <label class="screen-reader-text" for="h4bs-builder-quick-help-search"><?php esc_html_e('Search Quick Help task guides', 'help4-builder-suite'); ?></label>
                                <div class="h4bs-builder-quick-help-search-field">
                                    <span class="dashicons dashicons-search" aria-hidden="true"></span>
                                    <input id="h4bs-builder-quick-help-search" type="search" autocomplete="off" placeholder="<?php esc_attr_e('Search tasks, widgets, or settings', 'help4-builder-suite'); ?>" data-h4bs-quick-help-search>
                                    <button type="button" class="button-link" data-h4bs-quick-help-clear hidden title="<?php esc_attr_e('Clear Quick Help search', 'help4-builder-suite'); ?>">
                                        <span class="dashicons dashicons-no-alt" aria-hidden="true"></span>
                                        <span class="screen-reader-text"><?php esc_html_e('Clear Quick Help search', 'help4-builder-suite'); ?></span>
                                    </button>
                                </div>
                                <p
                                    class="h4bs-builder-quick-help-status"
                                    data-h4bs-quick-help-status
                                    data-status-all="<?php echo esc_attr($quickHelpStatusAll); ?>"
                                    data-status-one="<?php echo esc_attr($quickHelpStatusOne); ?>"
                                    data-status-many="<?php echo esc_attr($quickHelpStatusMany); ?>"
                                    aria-live="polite"
                                ><?php echo esc_html($quickHelpAvailableStatus); ?></p>
                            </div>
                            <nav class="h4bs-builder-quick-help-links" aria-label="<?php esc_attr_e('Help4 Builder task guides', 'help4-builder-suite'); ?>">
                                <?php foreach ($documentationQuickLinks as $quickLinkKey => $quickLink) : ?>
                                    <?php
                                    if (! is_array($quickLink) || empty($quickLink['url']) || empty($quickLink['label'])) {
                                        continue;
                                    }
                                    $quickLinkIcon = $documentationQuickLinkIcons[(string) $quickLinkKey] ?? 'dashicons-book';
                                    $quickLinkSearch = strtolower(str_replace('-', ' ', (string) $quickLinkKey) . ' ' . (string) $quickLink['label']);
                                    ?>
                                    <a href="<?php echo esc_url((string) $quickLink['url']); ?>" target="_blank" rel="noopener" data-h4bs-quick-help-link data-h4bs-quick-help-text="<?php echo esc_attr($quickLinkSearch); ?>">
                                        <span class="dashicons <?php echo esc_attr($quickLinkIcon); ?>" aria-hidden="true"></span>
                                        <span><?php echo esc_html((string) $quickLink['label']); ?></span>
                                    </a>
                                <?php endforeach; ?>
                            </nav>
                            <p class="h4bs-builder-quick-help-empty" data-h4bs-quick-help-empty hidden><?php esc_html_e('No task guide matched. Try a shorter word or open all tutorials.', 'help4-builder-suite'); ?></p>
                        </div>
                    </details>
                    <details class="h4bs-builder-tools-menu" data-h4bs-tools-menu>
                        <summary class="button h4bs-header-icon-btn" title="<?php esc_attr_e('More Builder Tools', 'help4-builder-suite'); ?>">
                            <span class="dashicons dashicons-menu-alt" aria-hidden="true"></span>
                            <span class="h4bs-compact-label" aria-hidden="true"><?php esc_html_e('Tools', 'help4-builder-suite'); ?></span>
                            <span class="screen-reader-text"><?php esc_html_e('More Builder Tools', 'help4-builder-suite'); ?></span>
                        </summary>
                        <div class="h4bs-builder-tools-panel">
                            <button type="button" class="button h4bs-header-icon-btn" data-h4bs-toggle-canvas-focus aria-pressed="false" title="<?php esc_attr_e('Focus Canvas', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-editor-expand" aria-hidden="true"></span>
                                <span class="screen-reader-text" data-h4bs-canvas-focus-label><?php esc_html_e('Focus Canvas', 'help4-builder-suite'); ?></span>
                            </button>
                            <button type="button" class="button h4bs-header-icon-btn" data-h4bs-open-navigator aria-expanded="false" aria-controls="h4bs-builder-navigator" title="<?php esc_attr_e('Navigator', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-editor-ol" aria-hidden="true"></span>
                                <span class="screen-reader-text"><?php esc_html_e('Navigator', 'help4-builder-suite'); ?></span>
                            </button>
                            <button type="button" class="button h4bs-header-icon-btn" data-h4bs-open-mini-preview title="<?php esc_attr_e('Mini Preview', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-format-image" aria-hidden="true"></span>
                                <span class="screen-reader-text"><?php esc_html_e('Mini Preview', 'help4-builder-suite'); ?></span>
                            </button>
                            <button type="button" class="button h4bs-header-icon-btn" data-h4bs-open-page-settings title="<?php esc_attr_e('Page Settings', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-admin-generic" aria-hidden="true"></span>
                                <span class="screen-reader-text"><?php esc_html_e('Page Settings', 'help4-builder-suite'); ?></span>
                            </button>
                            <button type="button" class="button h4bs-header-icon-btn" data-h4bs-compare-parity title="<?php esc_attr_e('Compare Draft and Public Computed Styles', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-chart-area" aria-hidden="true"></span>
                                <span class="screen-reader-text"><?php esc_html_e('Compare Draft and Public Computed Styles', 'help4-builder-suite'); ?></span>
                            </button>
                            <button type="button" class="button h4bs-header-icon-btn" data-h4bs-copy-page-spec title="<?php esc_attr_e('Copy Page Spec', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-clipboard" aria-hidden="true"></span>
                                <span class="screen-reader-text"><?php esc_html_e('Copy Page Spec', 'help4-builder-suite'); ?></span>
                            </button>
                            <button type="button" class="button h4bs-header-icon-btn" data-h4bs-paste-page-spec title="<?php esc_attr_e('Paste Page Spec', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-migrate" aria-hidden="true"></span>
                                <span class="screen-reader-text"><?php esc_html_e('Paste Page Spec', 'help4-builder-suite'); ?></span>
                            </button>
                            <a class="button h4bs-header-icon-btn" href="#" data-h4bs-edit-assigned-template="header" data-h4bs-template-label="header" hidden title="<?php esc_attr_e('Edit Assigned Header Template', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-arrow-up-alt2" aria-hidden="true"></span>
                                <span class="screen-reader-text"><?php esc_html_e('Edit Assigned Header Template', 'help4-builder-suite'); ?></span>
                            </a>
                            <a class="button h4bs-header-icon-btn" href="#" data-h4bs-edit-assigned-template="footer" data-h4bs-template-label="footer" hidden title="<?php esc_attr_e('Edit Assigned Footer Template', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-arrow-down-alt2" aria-hidden="true"></span>
                                <span class="screen-reader-text"><?php esc_html_e('Edit Assigned Footer Template', 'help4-builder-suite'); ?></span>
                            </a>
                            <?php if ($isTemplate) : ?>
                                <a class="button h4bs-header-icon-btn" href="<?php echo esc_url(admin_url('post.php?post=' . $postId . '&action=edit#h4bs-template-conditions')); ?>" data-h4bs-save-before-leave="conditions-panel" title="<?php esc_attr_e('Open Conditions Panel', 'help4-builder-suite'); ?>">
                                    <span class="dashicons dashicons-filter" aria-hidden="true"></span>
                                    <span class="screen-reader-text"><?php esc_html_e('Open Conditions Panel', 'help4-builder-suite'); ?></span>
                                </a>
                            <?php endif; ?>
                        </div>
                    </details>
                    <details class="h4bs-builder-command-menu" data-h4bs-command-menu>
                        <summary class="button h4bs-header-icon-btn" title="<?php esc_attr_e('Editing and publishing actions', 'help4-builder-suite'); ?>">
                            <span class="dashicons dashicons-admin-tools" aria-hidden="true"></span>
                            <span class="h4bs-compact-label" aria-hidden="true"><?php esc_html_e('Actions', 'help4-builder-suite'); ?></span>
                            <span class="screen-reader-text"><?php esc_html_e('Editing and publishing actions', 'help4-builder-suite'); ?></span>
                        </summary>
                        <div class="h4bs-builder-command-panel">
                            <button type="button" class="button h4bs-header-command-btn" data-h4bs-open-quick-actions aria-label="<?php esc_attr_e('Search Builder actions', 'help4-builder-suite'); ?>" title="<?php esc_attr_e('Search Builder actions', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-search" aria-hidden="true"></span>
                                <span><?php esc_html_e('Find an action', 'help4-builder-suite'); ?></span>
                                <kbd aria-hidden="true"><?php esc_html_e('Ctrl / Cmd K', 'help4-builder-suite'); ?></kbd>
                            </button>
                            <button type="button" class="button h4bs-header-command-btn" data-h4bs-add-element title="<?php esc_attr_e('Add Element', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-plus-alt2" aria-hidden="true"></span>
                                <span><?php esc_html_e('Add Element', 'help4-builder-suite'); ?></span>
                            </button>
                            <button type="button" class="button h4bs-header-icon-btn" data-h4bs-undo title="<?php esc_attr_e('Undo', 'help4-builder-suite'); ?>" disabled>
                                <span class="dashicons dashicons-undo" aria-hidden="true"></span>
                                <span class="h4bs-compact-label" aria-hidden="true"><?php esc_html_e('Undo', 'help4-builder-suite'); ?></span>
                                <span class="screen-reader-text"><?php esc_html_e('Undo', 'help4-builder-suite'); ?></span>
                            </button>
                            <button type="button" class="button h4bs-header-icon-btn" data-h4bs-redo title="<?php esc_attr_e('Redo', 'help4-builder-suite'); ?>" disabled>
                                <span class="dashicons dashicons-redo" aria-hidden="true"></span>
                                <span class="h4bs-compact-label" aria-hidden="true"><?php esc_html_e('Redo', 'help4-builder-suite'); ?></span>
                                <span class="screen-reader-text"><?php esc_html_e('Redo', 'help4-builder-suite'); ?></span>
                            </button>
                            <button type="button" class="button h4bs-header-command-btn" data-h4bs-open-review title="<?php esc_attr_e('Review page readiness, responsive views, and publication state', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-yes-alt" aria-hidden="true"></span>
                                <span><?php esc_html_e('Review', 'help4-builder-suite'); ?></span>
                            </button>
                            <button type="button" class="button button-primary h4bs-header-command-btn is-primary" data-h4bs-save-layout title="<?php echo esc_attr($saveActionLabel); ?>">
                                <span class="dashicons dashicons-saved" aria-hidden="true"></span>
                                <span data-h4bs-save-command-label><?php echo esc_html($saveActionLabel); ?></span>
                            </button>
                        </div>
                    </details>
                </div>
            </div>

            <div id="h4bs-builder-studio" class="h4bs-builder-studio" data-h4bs-builder-studio data-h4bs-version="<?php echo esc_attr(H4BS_VERSION); ?>" data-h4bs-build="<?php echo esc_attr(H4BS_BUILD_HASH); ?>">
                <main class="h4bs-studio-main">
                    <div class="h4bs-studio-toolbar">
                        <div class="h4bs-studio-device-switch">
                            <div class="h4bs-viewport-icons" data-h4bs-viewport-icons></div>
                            <button type="button" class="button h4bs-actual-page-preview" data-h4bs-toggle-parity aria-pressed="false" title="<?php esc_attr_e('Compare the saved page using its real theme, global templates, fonts, and frontend assets', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-visibility" aria-hidden="true"></span>
                                <span data-h4bs-parity-label><?php esc_html_e('View actual draft', 'help4-builder-suite'); ?></span>
                            </button>
                            <span class="h4bs-exact-select-help" data-h4bs-parity-selection-help role="status" aria-live="polite" hidden>
                                <span class="dashicons dashicons-edit" aria-hidden="true"></span>
                                <span><?php esc_html_e('Preparing the current draft...', 'help4-builder-suite'); ?></span>
                            </span>
                            <span class="h4bs-preview-asset-status" data-h4bs-preview-asset-status role="status" aria-live="polite" hidden></span>
                            <details class="h4bs-preview-settings-disclosure">
                                <summary>
                                    <span class="dashicons dashicons-admin-settings" aria-hidden="true"></span>
                                    <?php esc_html_e('Preview settings', 'help4-builder-suite'); ?>
                                </summary>
                                <div class="h4bs-preview-settings-body">
                                    <div class="h4bs-viewport-select-row">
                                        <label for="h4bs-viewport-select"><strong><?php esc_html_e('Preset', 'help4-builder-suite'); ?></strong></label>
                                        <select id="h4bs-viewport-select" data-h4bs-viewport-select></select>
                                        <span class="h4bs-viewport-live" data-h4bs-viewport-live></span>
                                    </div>
                                    <div class="h4bs-viewport-frame-row" role="group" aria-label="<?php esc_attr_e('Canvas preview controls', 'help4-builder-suite'); ?>">
                                        <span class="h4bs-viewport-frame-label"><strong><?php esc_html_e('Canvas', 'help4-builder-suite'); ?></strong></span>
                                        <button type="button" class="button button-small h4bs-frame-toggle" data-h4bs-preview-sizing="workspace" title="<?php esc_attr_e('Use the available workspace width so content stays readable while editing', 'help4-builder-suite'); ?>">
                                            <span class="dashicons dashicons-editor-contract" aria-hidden="true"></span>
                                            <?php esc_html_e('Fit Workspace', 'help4-builder-suite'); ?>
                                        </button>
                                        <button type="button" class="button button-small h4bs-frame-toggle" data-h4bs-preview-sizing="exact" title="<?php esc_attr_e('Scale the complete selected device into the workspace for final responsive checks', 'help4-builder-suite'); ?>">
                                            <span class="dashicons dashicons-visibility" aria-hidden="true"></span>
                                            <?php esc_html_e('Exact Device', 'help4-builder-suite'); ?>
                                        </button>
                                        <span class="h4bs-viewport-control-divider" aria-hidden="true"></span>
                                        <button type="button" class="button button-small h4bs-frame-toggle" data-h4bs-frame-mode="framed" title="<?php esc_attr_e('Show the selected device frame', 'help4-builder-suite'); ?>">
                                            <span class="dashicons dashicons-smartphone" aria-hidden="true"></span>
                                            <?php esc_html_e('Frame', 'help4-builder-suite'); ?>
                                        </button>
                                        <button type="button" class="button button-small h4bs-frame-toggle" data-h4bs-frame-mode="clean" title="<?php esc_attr_e('Hide the decorative device frame', 'help4-builder-suite'); ?>">
                                            <span class="dashicons dashicons-editor-expand" aria-hidden="true"></span>
                                            <?php esc_html_e('Clean', 'help4-builder-suite'); ?>
                                        </button>
                                        <a class="h4bs-context-help h4bs-toolbar-help" href="<?php echo esc_url($documentationContextUrl('responsive')); ?>" target="_blank" rel="noopener">
                                            <span class="dashicons dashicons-editor-help" aria-hidden="true"></span>
                                            <?php esc_html_e('Responsive guide', 'help4-builder-suite'); ?>
                                        </a>
                                    </div>
                                </div>
                            </details>
                        </div>
                        <div class="h4bs-studio-feedback-row">
                            <p class="h4bs-studio-save-status" role="status" aria-live="polite">
                                <span class="dashicons dashicons-saved" aria-hidden="true"></span>
                                <strong><?php esc_html_e('Save status', 'help4-builder-suite'); ?>:</strong>
                                <span data-h4bs-dirty-state><?php esc_html_e('All changes saved.', 'help4-builder-suite'); ?></span>
                            </p>
                            <details class="h4bs-studio-diagnostics">
                                <summary>
                                    <span class="dashicons dashicons-dashboard" aria-hidden="true"></span>
                                    <?php esc_html_e('Page checks', 'help4-builder-suite'); ?>
                                </summary>
                                <div class="h4bs-studio-diagnostics-panel">
                                    <p class="h4bs-studio-status">
                                        <strong><?php esc_html_e('Estimated Total Page Load', 'help4-builder-suite'); ?>:</strong>
                                        <span data-h4bs-builder-estimate><?php esc_html_e('Calculating...', 'help4-builder-suite'); ?></span>
                                        <span class="h4bs-studio-separator">|</span>
                                        <span data-h4bs-page-layout-label>
                                            <?php
                                            printf(
                                                /* translators: %s: page layout label */
                                                esc_html__('Layout: %s', 'help4-builder-suite'),
                                                esc_html($pageLayoutLabel)
                                            );
                                            ?>
                                        </span>
                                        <span class="h4bs-studio-separator">|</span>
                                        <span data-h4bs-parity-status><?php esc_html_e('Parity Mode: Off', 'help4-builder-suite'); ?></span>
                                        <span class="h4bs-studio-separator">|</span>
                                        <span data-h4bs-build-identity>
                                            <?php
                                            printf(
                                                /* translators: 1: plugin version, 2: short immutable build hash */
                                                esc_html__('Build %1$s (%2$s)', 'help4-builder-suite'),
                                                esc_html(H4BS_VERSION),
                                                esc_html(substr(H4BS_BUILD_HASH, 0, 12))
                                            );
                                            ?>
                                        </span>
                                    </p>
                                    <details class="h4bs-performance-coach" data-h4bs-performance-coach>
                                        <summary class="h4bs-performance-coach-head">
                                            <strong><?php esc_html_e('Performance Coach', 'help4-builder-suite'); ?></strong>
                                            <span data-h4bs-performance-summary><?php esc_html_e('Analyzing this layout...', 'help4-builder-suite'); ?></span>
                                        </summary>
                                        <div class="h4bs-performance-coach-body">
                                            <a class="h4bs-context-help h4bs-performance-help" href="<?php echo esc_url($documentationContextUrl('performance')); ?>" target="_blank" rel="noopener">
                                                <span class="dashicons dashicons-editor-help" aria-hidden="true"></span>
                                                <?php esc_html_e('Improve page speed', 'help4-builder-suite'); ?>
                                            </a>
                                            <ul class="h4bs-performance-coach-tips" data-h4bs-performance-tips></ul>
                                        </div>
                                    </details>
                                </div>
                            </details>
                        </div>
                    </div>

                    <div class="h4bs-selection-bar" data-h4bs-selection-actions hidden aria-live="polite">
                        <div class="h4bs-selection-summary">
                            <span class="dashicons dashicons-edit-page" aria-hidden="true"></span>
                            <span><?php esc_html_e('Selected', 'help4-builder-suite'); ?>:</span>
                            <strong data-h4bs-selection-label></strong>
                        </div>
                        <div class="h4bs-selection-controls" role="group" aria-label="<?php esc_attr_e('Selected element actions', 'help4-builder-suite'); ?>">
                            <button type="button" class="button button-small" data-h4bs-selection-action="edit">
                                <span class="dashicons dashicons-edit" aria-hidden="true"></span>
                                <?php esc_html_e('Edit', 'help4-builder-suite'); ?>
                            </button>
                            <button type="button" class="button button-small" data-h4bs-selection-action="before">
                                <span class="dashicons dashicons-insert-before" aria-hidden="true"></span>
                                <?php esc_html_e('Add Before', 'help4-builder-suite'); ?>
                            </button>
                            <button type="button" class="button button-small" data-h4bs-selection-action="after">
                                <span class="dashicons dashicons-insert-after" aria-hidden="true"></span>
                                <?php esc_html_e('Add After', 'help4-builder-suite'); ?>
                            </button>
                            <span class="h4bs-selection-inside" data-h4bs-selection-inside hidden>
                                <label>
                                    <span class="screen-reader-text"><?php esc_html_e('Destination column', 'help4-builder-suite'); ?></span>
                                    <select data-h4bs-selection-slot aria-label="<?php esc_attr_e('Destination column', 'help4-builder-suite'); ?>"></select>
                                </label>
                                <button type="button" class="button button-small" data-h4bs-selection-action="inside">
                                    <span class="dashicons dashicons-plus-alt2" aria-hidden="true"></span>
                                    <?php esc_html_e('Add Inside', 'help4-builder-suite'); ?>
                                </button>
                            </span>
                            <span class="h4bs-selection-divider" aria-hidden="true"></span>
                            <button type="button" class="button button-small h4bs-selection-icon" data-h4bs-selection-action="up" title="<?php esc_attr_e('Move earlier', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-arrow-up-alt2" aria-hidden="true"></span>
                                <span class="screen-reader-text"><?php esc_html_e('Move earlier', 'help4-builder-suite'); ?></span>
                            </button>
                            <button type="button" class="button button-small h4bs-selection-icon" data-h4bs-selection-action="down" title="<?php esc_attr_e('Move later', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-arrow-down-alt2" aria-hidden="true"></span>
                                <span class="screen-reader-text"><?php esc_html_e('Move later', 'help4-builder-suite'); ?></span>
                            </button>
                            <button type="button" class="button button-small h4bs-selection-icon" data-h4bs-selection-action="duplicate" title="<?php esc_attr_e('Duplicate element', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-admin-page" aria-hidden="true"></span>
                                <span class="screen-reader-text"><?php esc_html_e('Duplicate element', 'help4-builder-suite'); ?></span>
                            </button>
                            <button type="button" class="button button-small h4bs-selection-icon is-danger" data-h4bs-selection-action="delete" title="<?php esc_attr_e('Delete element', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-trash" aria-hidden="true"></span>
                                <span class="screen-reader-text"><?php esc_html_e('Delete element', 'help4-builder-suite'); ?></span>
                            </button>
                        </div>
                        <p class="h4bs-selection-status" data-h4bs-selection-status role="status" aria-live="polite"></p>
                    </div>

                    <section class="h4bs-studio-preview">
                        <h3><?php esc_html_e('Live Preview', 'help4-builder-suite'); ?></h3>
                        <div class="h4bs-preview-stage" data-h4bs-preview-stage>
                            <div class="h4bs-preview-scale" data-h4bs-preview-scale>
                                <div class="h4bs-preview-shell is-desktop" data-h4bs-preview-shell>
                                    <div class="h4bs-preview-canvas" data-h4bs-preview-canvas></div>
                                </div>
                            </div>
                            <section class="h4bs-start-journey" data-h4bs-start-journey aria-labelledby="h4bs-start-journey-title" hidden>
                                <div class="h4bs-start-journey-head">
                                    <span class="dashicons dashicons-welcome-widgets-menus" aria-hidden="true"></span>
                                    <div>
                                        <h2 id="h4bs-start-journey-title"><?php esc_html_e('How do you want to start?', 'help4-builder-suite'); ?></h2>
                                        <p><?php esc_html_e('Choose one starting point. Nothing is written until you save.', 'help4-builder-suite'); ?></p>
                                    </div>
                                </div>
                                <div class="h4bs-start-journey-actions">
                                    <button type="button" class="h4bs-start-choice" data-h4bs-start-action="blank">
                                        <span class="dashicons dashicons-media-default" aria-hidden="true"></span>
                                        <strong><?php esc_html_e('Blank Page', 'help4-builder-suite'); ?></strong>
                                        <small><?php esc_html_e('Empty canvas', 'help4-builder-suite'); ?></small>
                                    </button>
                                    <button type="button" class="h4bs-start-choice" data-h4bs-start-action="template">
                                        <span class="dashicons dashicons-layout" aria-hidden="true"></span>
                                        <strong><?php esc_html_e('Page Design', 'help4-builder-suite'); ?></strong>
                                        <small><?php esc_html_e('Premade layout', 'help4-builder-suite'); ?></small>
                                    </button>
                                    <button type="button" class="h4bs-start-choice" data-h4bs-start-action="site-kit">
                                        <span class="dashicons dashicons-admin-multisite" aria-hidden="true"></span>
                                        <strong><?php esc_html_e('Starter Site', 'help4-builder-suite'); ?></strong>
                                        <small><?php esc_html_e('Pages and site parts', 'help4-builder-suite'); ?></small>
                                    </button>
                                    <button type="button" class="h4bs-start-choice" data-h4bs-start-action="copy" aria-expanded="false" aria-controls="h4bs-copy-source-panel">
                                        <span class="dashicons dashicons-admin-page" aria-hidden="true"></span>
                                        <strong><?php esc_html_e('Copy Page', 'help4-builder-suite'); ?></strong>
                                        <small><?php esc_html_e('Existing Builder page', 'help4-builder-suite'); ?></small>
                                    </button>
                                </div>
                                <div id="h4bs-copy-source-panel" class="h4bs-copy-source-panel" data-h4bs-copy-source-panel hidden>
                                    <label for="h4bs-copy-source-select"><strong><?php esc_html_e('Page to copy', 'help4-builder-suite'); ?></strong></label>
                                    <div class="h4bs-copy-source-row">
                                        <select id="h4bs-copy-source-select" data-h4bs-copy-source-select>
                                            <option value=""><?php esc_html_e('Choose a Builder page', 'help4-builder-suite'); ?></option>
                                        </select>
                                        <button type="button" class="button button-primary" data-h4bs-copy-source-confirm disabled><?php esc_html_e('Use This Page', 'help4-builder-suite'); ?></button>
                                    </div>
                                </div>
                                <p class="h4bs-start-journey-status" data-h4bs-start-status role="status" aria-live="polite"></p>
                            </section>
                        </div>
                    </section>
                </main>

                <aside class="h4bs-studio-side" data-h4bs-side-panel>
                    <div class="h4bs-side-panel-head">
                        <div class="h4bs-side-head-copy">
                            <h2 data-h4bs-side-heading><?php esc_html_e('Elements', 'help4-builder-suite'); ?></h2>
                            <p data-h4bs-side-description><?php esc_html_e('Drag into canvas or click to insert.', 'help4-builder-suite'); ?></p>
                            <a class="h4bs-context-help" href="<?php echo esc_url($documentationContextUrl('elements')); ?>" target="_blank" rel="noopener" data-h4bs-side-help>
                                <span class="dashicons dashicons-editor-help" aria-hidden="true"></span>
                                <span data-h4bs-side-help-label><?php esc_html_e('Learn about elements', 'help4-builder-suite'); ?></span>
                            </a>
                        </div>
                        <button type="button" class="button button-small h4bs-side-toggle" data-h4bs-side-toggle aria-expanded="true">
                            <span class="dashicons dashicons-arrow-left-alt2" aria-hidden="true"></span>
                            <span class="h4bs-side-toggle-label"><?php esc_html_e('Collapse', 'help4-builder-suite'); ?></span>
                        </button>
                    </div>

                    <div class="h4bs-side-panel-body" data-h4bs-side-body>
                        <div class="h4bs-insert-target" data-h4bs-insert-target hidden>
                            <span class="dashicons dashicons-plus-alt2" aria-hidden="true"></span>
                            <span data-h4bs-insert-target-label></span>
                            <button type="button" class="button-link" data-h4bs-insert-target-cancel><?php esc_html_e('Cancel', 'help4-builder-suite'); ?></button>
                        </div>
                        <nav class="h4bs-side-library-tabs" role="tablist" aria-label="<?php esc_attr_e('Builder library', 'help4-builder-suite'); ?>">
                            <button type="button" class="button button-small h4bs-side-library-tab is-active" data-h4bs-library-tab="elements" role="tab" aria-selected="true" title="<?php esc_attr_e('Elements', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-screenoptions" aria-hidden="true"></span>
                                <?php esc_html_e('Elements', 'help4-builder-suite'); ?>
                            </button>
                            <button type="button" class="button button-small h4bs-side-library-tab" data-h4bs-library-tab="templates" role="tab" aria-selected="false" title="<?php esc_attr_e('Templates', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-layout" aria-hidden="true"></span>
                                <?php esc_html_e('Templates', 'help4-builder-suite'); ?>
                            </button>
                            <button type="button" class="button button-small h4bs-side-library-tab" data-h4bs-library-tab="site-kits" role="tab" aria-selected="false" title="<?php esc_attr_e('Site Kits', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-admin-multisite" aria-hidden="true"></span>
                                <?php esc_html_e('Site Kits', 'help4-builder-suite'); ?>
                            </button>
                            <button type="button" class="button button-small h4bs-side-library-tab" data-h4bs-library-tab="themes" role="tab" aria-selected="false" title="<?php esc_attr_e('Themes', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-art" aria-hidden="true"></span>
                                <?php esc_html_e('Themes', 'help4-builder-suite'); ?>
                            </button>
                            <button type="button" class="button button-small h4bs-side-library-tab" data-h4bs-library-tab="widgets" role="tab" aria-selected="false" title="<?php esc_attr_e('Widgets', 'help4-builder-suite'); ?>">
                                <span class="dashicons dashicons-screenoptions" aria-hidden="true"></span>
                                <?php esc_html_e('Widgets', 'help4-builder-suite'); ?>
                            </button>
                        </nav>

                        <section class="h4bs-side-library-panel is-active" data-h4bs-library-panel="elements" role="tabpanel">
                            <input type="search" class="h4bs-element-search" placeholder="<?php esc_attr_e('Search elements...', 'help4-builder-suite'); ?>" data-h4bs-element-search>
                            <div class="h4bs-studio-element-grid" data-h4bs-element-buttons></div>
                        </section>

                        <section class="h4bs-side-library-panel" data-h4bs-library-panel="templates" role="tabpanel" hidden>
                            <div class="h4bs-template-library-intro">
                                <strong><?php esc_html_e('Choose a design you can see', 'help4-builder-suite'); ?></strong>
                                <span><?php esc_html_e('Preview first. Nothing changes until you choose Use This Design, and nothing is written until you save.', 'help4-builder-suite'); ?></span>
                            </div>
                            <div class="h4bs-template-use-cases" data-h4bs-template-use-cases aria-label="<?php esc_attr_e('Filter designs by purpose', 'help4-builder-suite'); ?>"></div>
                            <div class="h4bs-template-filter-row">
                                <input type="search" class="h4bs-template-search" placeholder="<?php esc_attr_e('Search templates...', 'help4-builder-suite'); ?>" data-h4bs-template-search>
                                <select class="h4bs-template-category" data-h4bs-template-category>
                                    <option value="all"><?php esc_html_e('All Categories', 'help4-builder-suite'); ?></option>
                                </select>
                            </div>
                            <p class="h4bs-template-results" data-h4bs-template-results role="status" aria-live="polite"></p>
                            <div class="h4bs-studio-templates" data-h4bs-template-list></div>
                            <div class="h4bs-template-preview-modal" data-h4bs-template-preview-modal role="dialog" aria-modal="true" aria-labelledby="h4bs-template-preview-title" hidden>
                                <div class="h4bs-template-preview-dialog">
                                    <header class="h4bs-template-preview-head">
                                        <div>
                                            <span class="h4bs-template-preview-eyebrow" data-h4bs-template-preview-category></span>
                                            <h2 id="h4bs-template-preview-title" data-h4bs-template-preview-title><?php esc_html_e('Design Preview', 'help4-builder-suite'); ?></h2>
                                        </div>
                                        <button type="button" class="button h4bs-template-preview-close" data-h4bs-template-preview-close aria-label="<?php esc_attr_e('Close design preview', 'help4-builder-suite'); ?>">
                                            <span class="dashicons dashicons-no-alt" aria-hidden="true"></span>
                                        </button>
                                    </header>
                                    <div class="h4bs-template-preview-toolbar">
                                        <div class="h4bs-template-preview-devices" role="group" aria-label="<?php esc_attr_e('Preview size', 'help4-builder-suite'); ?>">
                                            <button type="button" class="button is-active" data-h4bs-template-preview-device="desktop" aria-pressed="true">
                                                <span class="dashicons dashicons-desktop" aria-hidden="true"></span>
                                                <span><?php esc_html_e('Desktop', 'help4-builder-suite'); ?></span>
                                            </button>
                                            <button type="button" class="button" data-h4bs-template-preview-device="tablet" aria-pressed="false">
                                                <span class="dashicons dashicons-tablet" aria-hidden="true"></span>
                                                <span><?php esc_html_e('Tablet', 'help4-builder-suite'); ?></span>
                                            </button>
                                            <button type="button" class="button" data-h4bs-template-preview-device="mobile" aria-pressed="false">
                                                <span class="dashicons dashicons-smartphone" aria-hidden="true"></span>
                                                <span><?php esc_html_e('Mobile', 'help4-builder-suite'); ?></span>
                                            </button>
                                        </div>
                                        <span data-h4bs-template-preview-count></span>
                                    </div>
                                    <div class="h4bs-template-preview-content">
                                        <div class="h4bs-template-preview-stage" data-h4bs-template-preview-stage></div>
                                        <aside class="h4bs-template-preview-summary">
                                            <p data-h4bs-template-preview-description></p>
                                            <h3><?php esc_html_e('What is included', 'help4-builder-suite'); ?></h3>
                                            <ul data-h4bs-template-preview-includes></ul>
                                            <p class="h4bs-template-preview-safety"><?php esc_html_e('Sample text and empty media are ready to replace. This preview does not change or save your page.', 'help4-builder-suite'); ?></p>
                                        </aside>
                                    </div>
                                    <footer class="h4bs-template-preview-actions">
                                        <button type="button" class="button" data-h4bs-template-preview-cancel><?php esc_html_e('Keep Browsing', 'help4-builder-suite'); ?></button>
                                        <button type="button" class="button" data-h4bs-template-preview-secondary hidden></button>
                                        <button type="button" class="button button-primary" data-h4bs-template-preview-use><?php esc_html_e('Use This Design', 'help4-builder-suite'); ?></button>
                                    </footer>
                                </div>
                            </div>
                        </section>

                        <section class="h4bs-side-library-panel" data-h4bs-library-panel="site-kits" role="tabpanel" hidden>
                            <input type="search" class="h4bs-site-kit-search" placeholder="<?php esc_attr_e('Search site kits...', 'help4-builder-suite'); ?>" data-h4bs-site-kit-search>
                            <label class="h4bs-site-kit-style-option">
                                <input type="checkbox" value="1" data-h4bs-site-kit-apply-styles>
                                <span><?php esc_html_e('Apply the kit style tokens sitewide', 'help4-builder-suite'); ?></span>
                            </label>
                            <p class="h4bs-site-kit-safety-note">
                                <?php esc_html_e('Kits create reviewable draft pages, draft templates, and a kit-owned navigation menu. They never publish content, replace existing pages, or select a new front page.', 'help4-builder-suite'); ?>
                            </p>
                            <div class="h4bs-site-kit-status" data-h4bs-site-kit-status role="status" aria-live="polite"></div>
                            <div class="h4bs-studio-site-kits" data-h4bs-site-kit-list></div>
                        </section>

                        <section class="h4bs-side-library-panel" data-h4bs-library-panel="themes" role="tabpanel" hidden>
                            <input type="search" class="h4bs-theme-search" placeholder="<?php esc_attr_e('Search theme packs...', 'help4-builder-suite'); ?>" data-h4bs-theme-search>
                            <div class="h4bs-theme-pack-tools">
                                <button type="button" class="button button-small" data-h4bs-save-theme-pack>
                                    <span class="dashicons dashicons-plus-alt2" aria-hidden="true"></span>
                                    <?php esc_html_e('Save Style', 'help4-builder-suite'); ?>
                                </button>
                                <button type="button" class="button button-small" data-h4bs-import-theme-pack>
                                    <span class="dashicons dashicons-upload" aria-hidden="true"></span>
                                    <?php esc_html_e('Import', 'help4-builder-suite'); ?>
                                </button>
                                <button type="button" class="button button-small" data-h4bs-export-theme-pack>
                                    <span class="dashicons dashicons-download" aria-hidden="true"></span>
                                    <?php esc_html_e('Export', 'help4-builder-suite'); ?>
                                </button>
                            </div>
                            <div class="h4bs-studio-themes" data-h4bs-theme-list></div>
                        </section>

                        <section class="h4bs-side-library-panel" data-h4bs-library-panel="widgets" role="tabpanel" hidden>
                            <div class="h4bs-widget-library">
                                <input type="search" placeholder="<?php esc_attr_e('Search widgets...', 'help4-builder-suite'); ?>" data-h4bs-widget-search>
                                <div data-h4bs-widget-list></div>
                            </div>
                        </section>

                        <p class="h4bs-modal-workflow-note">
                            <?php esc_html_e('Drag into the canvas or click to insert. Select any canvas element to edit it in the inspector.', 'help4-builder-suite'); ?>
                        </p>
                    </div>
                </aside>

                <aside class="h4bs-studio-inspector-empty" data-h4bs-inspector-empty>
                    <div class="h4bs-inspector-empty-head">
                        <strong><?php esc_html_e('Element Settings', 'help4-builder-suite'); ?></strong>
                        <span><?php esc_html_e('Inspector', 'help4-builder-suite'); ?></span>
                    </div>
                    <div class="h4bs-inspector-empty-tabs" aria-hidden="true">
                        <span class="is-active"><?php esc_html_e('Content', 'help4-builder-suite'); ?></span>
                        <span><?php esc_html_e('Design', 'help4-builder-suite'); ?></span>
                        <span><?php esc_html_e('Advanced', 'help4-builder-suite'); ?></span>
                    </div>
                    <div class="h4bs-inspector-empty-body">
                        <span class="dashicons dashicons-edit-page" aria-hidden="true"></span>
                        <strong><?php esc_html_e('Select an element', 'help4-builder-suite'); ?></strong>
                        <p><?php esc_html_e('Click a block in the canvas to edit content, spacing, typography, responsive visibility, and advanced settings without covering your page.', 'help4-builder-suite'); ?></p>
                        <button type="button" class="button" data-h4bs-open-page-settings>
                            <span class="dashicons dashicons-admin-generic" aria-hidden="true"></span>
                            <?php esc_html_e('Open Page Settings', 'help4-builder-suite'); ?>
                        </button>
                        <a class="h4bs-context-help" href="<?php echo esc_url($documentationContextUrl('elements')); ?>" target="_blank" rel="noopener">
                            <span class="dashicons dashicons-editor-help" aria-hidden="true"></span>
                            <?php esc_html_e('Open the editor controls guide', 'help4-builder-suite'); ?>
                        </a>
                        <nav class="h4bs-related-help-links" aria-label="<?php esc_attr_e('Related Builder guides', 'help4-builder-suite'); ?>">
                            <a href="<?php echo esc_url($documentationContextUrl('first-page')); ?>" target="_blank" rel="noopener"><?php esc_html_e('First page', 'help4-builder-suite'); ?></a>
                            <a href="<?php echo esc_url($documentationContextUrl('responsive')); ?>" target="_blank" rel="noopener"><?php esc_html_e('Mobile design', 'help4-builder-suite'); ?></a>
                            <a href="<?php echo esc_url($documentationContextUrl('templates')); ?>" target="_blank" rel="noopener"><?php esc_html_e('Templates', 'help4-builder-suite'); ?></a>
                            <a href="<?php echo esc_url($documentationContextUrl('page-history')); ?>" target="_blank" rel="noopener"><?php esc_html_e('Undo and recovery', 'help4-builder-suite'); ?></a>
                        </nav>
                    </div>
                </aside>
            </div>

            <div class="h4bs-editor-modal-backdrop" data-h4bs-modal-backdrop hidden></div>
            <div class="h4bs-editor-modal" data-h4bs-editor-modal role="region" aria-labelledby="h4bs-builder-inspector-title" hidden>
                <div
                    class="h4bs-editor-modal-resize"
                    data-h4bs-inspector-width
                    role="separator"
                    aria-label="<?php esc_attr_e('Resize element settings panel', 'help4-builder-suite'); ?>"
                    aria-orientation="vertical"
                    aria-valuemin="360"
                    aria-valuemax="560"
                    aria-valuenow="420"
                    tabindex="0"
                    title="<?php esc_attr_e('Drag to resize. Use Left and Right Arrow keys for precise control.', 'help4-builder-suite'); ?>"
                ><span aria-hidden="true"></span></div>
                <div class="h4bs-editor-modal-header" data-h4bs-modal-drag>
                    <strong id="h4bs-builder-inspector-title" data-h4bs-modal-title><?php esc_html_e('Element Settings', 'help4-builder-suite'); ?></strong>
                    <span class="h4bs-inspector-live-status" data-h4bs-inspector-status role="status" aria-live="polite"><?php esc_html_e('Live preview', 'help4-builder-suite'); ?></span>
                    <a class="h4bs-context-help h4bs-context-help--compact" href="<?php echo esc_url($documentationContextUrl('elements')); ?>" target="_blank" rel="noopener" data-h4bs-context-help>
                        <span class="dashicons dashicons-editor-help" aria-hidden="true"></span>
                        <span data-h4bs-context-help-label><?php esc_html_e('Help', 'help4-builder-suite'); ?></span>
                    </a>
                    <button type="button" class="button-link" data-h4bs-modal-cancel aria-label="<?php esc_attr_e('Close editor', 'help4-builder-suite'); ?>">&times;</button>
                </div>
                <div class="h4bs-editor-modal-body" data-h4bs-modal-body></div>
                <nav class="h4bs-related-help-links h4bs-editor-related-help" aria-label="<?php esc_attr_e('Related element editing guides', 'help4-builder-suite'); ?>">
                    <span><?php esc_html_e('Related:', 'help4-builder-suite'); ?></span>
                    <a href="<?php echo esc_url($documentationContextUrl('responsive')); ?>" target="_blank" rel="noopener"><?php esc_html_e('Responsive settings', 'help4-builder-suite'); ?></a>
                    <a href="<?php echo esc_url($documentationContextUrl('page-history')); ?>" target="_blank" rel="noopener"><?php esc_html_e('Recover edits', 'help4-builder-suite'); ?></a>
                    <a href="<?php echo esc_url($documentationContextUrl('publishing')); ?>" target="_blank" rel="noopener"><?php esc_html_e('Save and publish', 'help4-builder-suite'); ?></a>
                </nav>
                <div class="h4bs-editor-modal-footer">
                    <button type="button" class="button h4bs-modal-review-button" data-h4bs-modal-review>
                        <span class="dashicons dashicons-visibility" aria-hidden="true"></span>
                        <?php esc_html_e('Review Current Draft', 'help4-builder-suite'); ?>
                    </button>
                    <button type="button" class="button h4bs-modal-delete-button" data-h4bs-modal-delete hidden><?php esc_html_e('Remove Element', 'help4-builder-suite'); ?></button>
                    <button type="button" class="button" data-h4bs-modal-cancel><?php esc_html_e('Cancel', 'help4-builder-suite'); ?></button>
                    <button type="button" class="button button-primary" data-h4bs-modal-save><?php esc_html_e('Save Element Changes', 'help4-builder-suite'); ?></button>
                </div>
            </div>

            <div id="h4bs-builder-navigator" class="h4bs-navigator-modal" data-h4bs-navigator-modal aria-hidden="true" hidden>
                <div class="h4bs-navigator-modal-header" data-h4bs-navigator-drag>
                    <strong><?php esc_html_e('Navigator', 'help4-builder-suite'); ?></strong>
                    <button type="button" class="button-link" data-h4bs-close-navigator aria-label="<?php esc_attr_e('Close navigator', 'help4-builder-suite'); ?>">&times;</button>
                </div>
                <div class="h4bs-navigator-modal-body">
                    <p><?php esc_html_e('Navigator order mirrors the live page structure. Click any row to open element settings.', 'help4-builder-suite'); ?></p>
                    <p><a class="h4bs-context-help" href="<?php echo esc_url($documentationContextUrl('navigator')); ?>" target="_blank" rel="noopener"><span class="dashicons dashicons-editor-help" aria-hidden="true"></span><?php esc_html_e('Navigator and layers guide', 'help4-builder-suite'); ?></a></p>
                    <ul class="h4bs-block-list" data-h4bs-block-list></ul>
                </div>
            </div>

            <div class="h4bs-mini-preview-modal" data-h4bs-mini-preview-modal hidden>
                <div class="h4bs-mini-preview-modal-header" data-h4bs-mini-preview-drag>
                    <strong><?php esc_html_e('Mini Preview', 'help4-builder-suite'); ?></strong>
                    <button type="button" class="button-link" data-h4bs-close-mini-preview aria-label="<?php esc_attr_e('Close mini preview', 'help4-builder-suite'); ?>">&times;</button>
                </div>
                <div class="h4bs-mini-preview-modal-body">
                    <p><?php esc_html_e('Pixel preview mirror of the current viewport. Drag inside this card to move through long pages.', 'help4-builder-suite'); ?></p>
                    <p><a class="h4bs-context-help" href="<?php echo esc_url($documentationContextUrl('mini-preview')); ?>" target="_blank" rel="noopener"><span class="dashicons dashicons-editor-help" aria-hidden="true"></span><?php esc_html_e('Live preview workflow', 'help4-builder-suite'); ?></a></p>
                    <div class="h4bs-mini-preview-frame" data-h4bs-mini-preview-frame>
                        <div class="h4bs-mini-preview-scale" data-h4bs-mini-preview-scale>
                            <div class="h4bs-mini-preview-shell" data-h4bs-mini-preview-shell></div>
                        </div>
                        <div class="h4bs-mini-preview-window" data-h4bs-mini-preview-window></div>
                    </div>
                    <p class="h4bs-mini-preview-meta">
                        <span data-h4bs-mini-preview-label><?php esc_html_e('Viewport: --', 'help4-builder-suite'); ?></span>
                        <span data-h4bs-mini-preview-scroll><?php esc_html_e('Scroll: 0%', 'help4-builder-suite'); ?></span>
                    </p>
                </div>
            </div>

            <div class="h4bs-quick-actions-modal" data-h4bs-quick-actions-modal hidden>
                <section class="h4bs-quick-actions-dialog" data-h4bs-quick-actions-dialog role="dialog" aria-modal="true" aria-labelledby="h4bs-quick-actions-title" tabindex="-1">
                    <header class="h4bs-quick-actions-header">
                        <div>
                            <span class="h4bs-quick-actions-eyebrow"><?php esc_html_e('Tell Builder what you want to do', 'help4-builder-suite'); ?></span>
                            <h2 id="h4bs-quick-actions-title"><?php esc_html_e('Quick Actions', 'help4-builder-suite'); ?></h2>
                        </div>
                        <button type="button" class="button-link h4bs-quick-actions-close" data-h4bs-quick-actions-close aria-label="<?php esc_attr_e('Close Quick Actions', 'help4-builder-suite'); ?>">&times;</button>
                    </header>
                    <div class="h4bs-quick-actions-body">
                        <label class="screen-reader-text" for="h4bs-quick-actions-search"><?php esc_html_e('Search Builder actions', 'help4-builder-suite'); ?></label>
                        <div class="h4bs-quick-actions-search-wrap">
                            <span class="dashicons dashicons-search" aria-hidden="true"></span>
                            <input id="h4bs-quick-actions-search" type="search" autocomplete="off" placeholder="<?php esc_attr_e('Try text, photo, mobile, colors, header, or publish', 'help4-builder-suite'); ?>" data-h4bs-quick-actions-search aria-controls="h4bs-quick-actions-list" aria-autocomplete="list">
                            <kbd aria-hidden="true"><?php esc_html_e('Esc', 'help4-builder-suite'); ?></kbd>
                        </div>
                        <p class="h4bs-quick-actions-status screen-reader-text" data-h4bs-quick-actions-status aria-live="polite"></p>
                        <div id="h4bs-quick-actions-list" class="h4bs-quick-actions-list" data-h4bs-quick-actions-list role="listbox" aria-label="<?php esc_attr_e('Matching Builder actions', 'help4-builder-suite'); ?>"></div>
                        <p class="h4bs-quick-actions-empty" data-h4bs-quick-actions-empty hidden><?php esc_html_e('No action matched. Try one word such as text, photo, mobile, color, header, or help.', 'help4-builder-suite'); ?></p>
                    </div>
                    <footer class="h4bs-quick-actions-footer">
                        <span><kbd aria-hidden="true">&uarr;</kbd><kbd aria-hidden="true">&darr;</kbd> <?php esc_html_e('choose', 'help4-builder-suite'); ?></span>
                        <span><kbd aria-hidden="true">Enter</kbd> <?php esc_html_e('open', 'help4-builder-suite'); ?></span>
                        <span><?php esc_html_e('Actions use the same Builder controls and save protections.', 'help4-builder-suite'); ?></span>
                    </footer>
                </section>
            </div>

            <div class="h4bs-review-modal" data-h4bs-review-modal hidden>
                <section class="h4bs-review-dialog" role="dialog" aria-modal="true" aria-labelledby="h4bs-review-title" tabindex="-1">
                    <header class="h4bs-review-header">
                        <div>
                            <span class="h4bs-review-eyebrow"><?php esc_html_e('Before this page goes live', 'help4-builder-suite'); ?></span>
                            <h2 id="h4bs-review-title"><?php esc_html_e('Review and publish', 'help4-builder-suite'); ?></h2>
                        </div>
                        <button type="button" class="button-link h4bs-review-close" data-h4bs-review-close aria-label="<?php esc_attr_e('Close review', 'help4-builder-suite'); ?>">&times;</button>
                    </header>
                    <div class="h4bs-review-body">
                        <div class="h4bs-review-summary" data-h4bs-review-summary>
                            <span class="h4bs-review-status" data-h4bs-review-post-status><?php echo esc_html($postStatusLabel); ?></span>
                            <strong data-h4bs-review-summary-title><?php esc_html_e('Checking this page...', 'help4-builder-suite'); ?></strong>
                            <p data-h4bs-review-summary-text><?php esc_html_e('Builder is checking content, accessibility basics, links, and responsive review state.', 'help4-builder-suite'); ?></p>
                        </div>
                        <div class="h4bs-review-layout">
                            <section class="h4bs-review-checks" aria-labelledby="h4bs-review-checks-title">
                                <h3 id="h4bs-review-checks-title"><?php esc_html_e('Page readiness', 'help4-builder-suite'); ?></h3>
                                <ul data-h4bs-review-checks></ul>
                            </section>
                            <section class="h4bs-review-devices" aria-labelledby="h4bs-review-devices-title">
                                <h3 id="h4bs-review-devices-title"><?php esc_html_e('Check every screen', 'help4-builder-suite'); ?></h3>
                                <p><?php esc_html_e('Open each view and confirm the text, images, and buttons look right before publishing.', 'help4-builder-suite'); ?></p>
                                <div class="h4bs-review-device-buttons" role="group" aria-label="<?php esc_attr_e('Responsive review views', 'help4-builder-suite'); ?>">
                                    <button type="button" class="button" data-h4bs-review-device="monitor" aria-pressed="false"><span class="dashicons dashicons-desktop" aria-hidden="true"></span><?php esc_html_e('Desktop', 'help4-builder-suite'); ?></button>
                                    <button type="button" class="button" data-h4bs-review-device="tablet" aria-pressed="false"><span class="dashicons dashicons-tablet" aria-hidden="true"></span><?php esc_html_e('Tablet', 'help4-builder-suite'); ?></button>
                                    <button type="button" class="button" data-h4bs-review-device="mobile" aria-pressed="false"><span class="dashicons dashicons-smartphone" aria-hidden="true"></span><?php esc_html_e('Mobile', 'help4-builder-suite'); ?></button>
                                </div>
                            </section>
                        </div>
                    </div>
                    <footer class="h4bs-review-footer">
                        <p class="h4bs-review-action-status" data-h4bs-review-action-status role="status" aria-live="polite"></p>
                        <div class="h4bs-review-actions">
                            <button type="button" class="button" data-h4bs-review-close><?php esc_html_e('Keep Editing', 'help4-builder-suite'); ?></button>
                            <a class="button" href="<?php echo esc_url($reviewPreviewUrl); ?>" target="_blank" rel="noopener" data-h4bs-review-preview><?php esc_html_e('Open Saved Preview', 'help4-builder-suite'); ?></a>
                            <button type="button" class="button" data-h4bs-review-save<?php echo $postStatus === 'publish' ? ' hidden' : ''; ?>><?php echo esc_html($saveActionLabel); ?></button>
                            <button type="button" class="button button-primary" data-h4bs-review-publish<?php echo ! $canPublish && $postStatus !== 'publish' ? ' hidden' : ''; ?>>
                                <?php echo esc_html($postStatus === 'publish' ? __('Update Live', 'help4-builder-suite') : __('Publish Page', 'help4-builder-suite')); ?>
                            </button>
                        </div>
                    </footer>
                </section>
            </div>

            <footer class="h4bs-builder-brandbar">
                <span><?php esc_html_e('Thank you for creating with WordPress.', 'help4-builder-suite'); ?></span>
                <span>
                    <a href="<?php echo esc_url($documentationHubUrl); ?>" target="_blank" rel="noopener">
                        <?php
                        printf(
                            /* translators: %s: plugin version */
                            esc_html__('Help4 WordPress Builder v%s', 'help4-builder-suite'),
                            esc_html(H4BS_VERSION)
                        );
                        ?>
                    </a>
                </span>
            </footer>
        </div>
        <?php
    }

    public function ajaxSaveLayout(): void
    {
        check_ajax_referer('h4bs_builder_studio_save', 'nonce');

        $postId = isset($_POST['post_id']) ? absint(wp_unslash($_POST['post_id'])) : 0;
        if ($postId <= 0 || ! current_user_can('edit_post', $postId)) {
            wp_send_json_error(['message' => __('Permission denied.', 'help4-builder-suite')], 403);
        }

        // Older Studio builds always saved the page payload, so an omitted flag remains page-save compatible.
        $saveLayout = ! isset($_POST['save_layout']) || rest_sanitize_boolean(wp_unslash($_POST['save_layout']));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitizeLayout validates the decoded Builder document and preserves safe HTML content.
        $rawLayout = isset($_POST['layout']) ? (string) wp_unslash($_POST['layout']) : '[]';
        $rawPageLayout = isset($_POST['page_layout']) ? sanitize_key((string) wp_unslash($_POST['page_layout'])) : 'default';
        $rawPageTitleMode = isset($_POST['page_title_mode']) ? sanitize_key((string) wp_unslash($_POST['page_title_mode'])) : 'auto';
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitizePageCustomCss applies the Builder CSS allowlist before storage or rendering.
        $rawPageCss = isset($_POST['page_css']) ? (string) wp_unslash($_POST['page_css']) : '';
        $rawGlobalDesignTokens = isset($_POST['global_design_tokens']) ? sanitize_textarea_field((string) wp_unslash($_POST['global_design_tokens'])) : '';
        $rawCustomThemePacks = isset($_POST['custom_theme_packs']) ? sanitize_textarea_field((string) wp_unslash($_POST['custom_theme_packs'])) : '[]';
        $activeThemePackSlug = isset($_POST['active_theme_pack'])
            ? sanitize_key((string) wp_unslash($_POST['active_theme_pack']))
            : '';
        $saveGlobals = isset($_POST['save_globals']) && rest_sanitize_boolean(wp_unslash($_POST['save_globals']));
        $submittedGlobalRevision = isset($_POST['global_revision']) ? absint(wp_unslash($_POST['global_revision'])) : 0;
        $rawTemplateConditions = isset($_POST['template_conditions']) ? sanitize_textarea_field((string) wp_unslash($_POST['template_conditions'])) : '';
        $submittedLayoutHash = isset($_POST['layout_hash'])
            ? sanitize_text_field((string) wp_unslash($_POST['layout_hash']))
            : '';
        $submittedPageCssHash = isset($_POST['page_css_hash'])
            ? sanitize_text_field((string) wp_unslash($_POST['page_css_hash']))
            : '';

        $oldLayout = $this->sanitizeLayout($this->layoutForPost($postId));
        // Studio displays the starter layout when a newly enabled page has no
        // stored document. Use that same document for the first-save revision
        // check so the initial edit is not mistaken for a concurrent change.
        if (! $oldLayout) {
            $oldLayout = $this->sanitizeLayout($this->defaultLayout());
        }
        $oldPageLayout = $this->pageLayoutForPost($postId);
        $oldPageTitleMode = $this->pageTitleModeForPost($postId);
        $oldPageCss = $this->pageCustomCss($postId);
        $pageCss = $saveLayout ? $this->sanitizePageCustomCss($rawPageCss) : $oldPageCss;
        $oldTemplateConditions = $this->loadTemplateConditions($postId);
        $isTemplate = get_post_type($postId) === 'h4_template';

        $layout = $saveLayout ? $this->sanitizeLayout($rawLayout) : $oldLayout;
        $pageLayout = $saveLayout ? $this->normalizePageLayout($rawPageLayout) : $oldPageLayout;
        $pageTitleMode = $saveLayout ? $this->normalizePageTitleMode($rawPageTitleMode) : $oldPageTitleMode;
        $templateConditions = $oldTemplateConditions;
        if ($saveLayout && $isTemplate) {
            $templateConditions = $this->sanitizeTemplateConditions($rawTemplateConditions);
        }

        $oldStoredGlobalTokens = $this->sanitizeGlobalDesignTokens(get_option('h4bs_global_design_tokens', []));
        $oldCustomThemePacks = $this->customThemePacks();
        $oldActiveThemePackSlug = $this->activeThemePackSlug();
        $nextStoredGlobalTokens = $saveGlobals
            ? $this->sanitizeGlobalDesignTokens($rawGlobalDesignTokens)
            : $oldStoredGlobalTokens;
        $nextCustomThemePacks = $saveGlobals
            ? $this->sanitizeThemePackCatalog($rawCustomThemePacks, true)
            : $oldCustomThemePacks;
        $nextActiveThemePackSlug = $saveGlobals ? $activeThemePackSlug : $oldActiveThemePackSlug;
        if ($saveGlobals && $nextActiveThemePackSlug !== '') {
            $activePackExists = false;
            foreach (array_merge($this->themePackCatalog(), $nextCustomThemePacks) as $themePack) {
                if (
                    is_array($themePack)
                    && (string) ($themePack['slug'] ?? '') === $nextActiveThemePackSlug
                    && (empty($themePack['custom']) || in_array($themePack, $nextCustomThemePacks, true))
                ) {
                    $activePackExists = true;
                    break;
                }
            }
            if (! $activePackExists) {
                $nextActiveThemePackSlug = '';
            }
        }

        $oldGlobalSettingsHash = $this->builderValueHash(
            [
                'tokens' => $oldStoredGlobalTokens,
                'custom_theme_packs' => $oldCustomThemePacks,
                'active_theme_pack' => $oldActiveThemePackSlug,
            ]
        );
        $nextGlobalSettingsHash = $this->builderValueHash(
            [
                'tokens' => $nextStoredGlobalTokens,
                'custom_theme_packs' => $nextCustomThemePacks,
                'active_theme_pack' => $nextActiveThemePackSlug,
            ]
        );
        $globalsChanged = $saveGlobals && $oldGlobalSettingsHash !== $nextGlobalSettingsHash;
        $currentGlobalRevision = $this->globalDesignRevision();
        if ($globalsChanged && $submittedGlobalRevision !== $currentGlobalRevision) {
            wp_send_json_error(
                [
                    'code' => 'global_revision_conflict',
                    'message' => __('Global Builder styles changed in another session.', 'help4-builder-suite'),
                    'global_revision' => $currentGlobalRevision,
                    'global_hash' => $this->globalDesignHash(),
                    'global_design_tokens' => $this->globalDesignTokens(),
                ],
                409
            );
        }

        $oldGlobalHash = $this->globalDesignHash();
        $oldLayoutHash = $this->builderValueHash($oldLayout);
        $newLayoutHash = $this->builderValueHash($layout);
        $layoutChanged = $saveLayout && $oldLayoutHash !== $newLayoutHash;
        if ($layoutChanged && $submittedLayoutHash !== '' && ! hash_equals($oldLayoutHash, $submittedLayoutHash)) {
            wp_send_json_error(
                [
                    'code' => 'layout_revision_conflict',
                    'message' => __('This page changed in another Builder session. Reload before saving so no work is overwritten.', 'help4-builder-suite'),
                    'layout_hash' => $oldLayoutHash,
                ],
                409
            );
        }
        if ($layoutChanged) {
            $structureErrors = $this->layoutStructureErrors($layout);
            if ($structureErrors) {
                wp_send_json_error(
                    [
                        'code' => 'invalid_layout_structure',
                        'message' => __('The page layout could not be saved because its element structure is incomplete.', 'help4-builder-suite'),
                        'errors' => $structureErrors,
                        'layout_hash' => $oldLayoutHash,
                    ],
                    422
                );
            }
        }
        $pageLayoutChanged = $saveLayout && $oldPageLayout !== $pageLayout;
        $pageTitleModeChanged = $saveLayout && $oldPageTitleMode !== $pageTitleMode;
        $pageCssChanged = $saveLayout && ! hash_equals($this->builderValueHash($oldPageCss), $this->builderValueHash($pageCss));
        if ($pageCssChanged && $submittedPageCssHash !== '' && ! hash_equals($this->builderValueHash($oldPageCss), $submittedPageCssHash)) {
            wp_send_json_error(
                [
                    'code' => 'page_css_revision_conflict',
                    'message' => __('This page CSS changed in another Builder session. Reload before saving.', 'help4-builder-suite'),
                    'page_css_hash' => $this->builderValueHash($oldPageCss),
                ],
                409
            );
        }
        $templateConditionsChanged = $saveLayout
            && $isTemplate
            && $this->builderValueHash($oldTemplateConditions) !== $this->builderValueHash($templateConditions);
        $builderStateChanged = $saveLayout
            && (
                (string) get_post_meta($postId, '_h4_builder_mode', true) !== 'help4'
                || (string) get_post_meta($postId, '_h4_builder_enabled', true) !== '1'
            );

        if ($saveLayout) {
            if ((string) get_post_meta($postId, '_h4_builder_mode', true) !== 'help4') {
                update_post_meta($postId, '_h4_builder_mode', 'help4');
            }
            if ((string) get_post_meta($postId, '_h4_builder_enabled', true) !== '1') {
                update_post_meta($postId, '_h4_builder_enabled', '1');
            }
            update_post_meta($postId, self::DOCUMENT_ORIGIN_META, 'builder_studio');
            if ($pageLayoutChanged) {
                update_post_meta($postId, '_h4_builder_page_layout', $pageLayout);
            }
            if ($pageTitleModeChanged) {
                update_post_meta($postId, '_h4_builder_page_title', $pageTitleMode);
            }
            if ($pageCssChanged) {
                $this->persistPageCustomCss($postId, $pageCss);
            }
            if ($layoutChanged || ! metadata_exists('post', $postId, '_h4_builder_layout')) {
                $this->storeLayoutRecoveryPoint($postId, $oldLayout, $oldLayoutHash);
                update_post_meta($postId, '_h4_builder_layout', wp_slash((string) wp_json_encode($layout, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)));
            }
            if ($templateConditionsChanged) {
                $this->persistTemplateConditions($postId, $templateConditions);
            }
        }
        if ($globalsChanged) {
            update_option('h4bs_global_design_tokens', $nextStoredGlobalTokens, false);
            update_option('h4bs_custom_theme_packs', $nextCustomThemePacks, false);
            update_option('h4bs_active_theme_pack', $nextActiveThemePackSlug, false);
            update_option('h4bs_global_design_revision', $currentGlobalRevision + 1, false);
        }

        $changeSet = [
            'post_id' => $postId,
            'layout_changed' => $layoutChanged,
            'page_layout_changed' => $pageLayoutChanged,
            'page_title_mode_changed' => $pageTitleModeChanged,
            'page_css_changed' => $pageCssChanged,
            'template_conditions_changed' => $templateConditionsChanged,
            'builder_state_changed' => $builderStateChanged,
            'globals_changed' => $globalsChanged,
            'layout_requested' => $saveLayout,
            'globals_requested' => $saveGlobals,
            'old_layout_hash' => $oldLayoutHash,
            'new_layout_hash' => $newLayoutHash,
            'old_global_hash' => $oldGlobalHash,
            'new_global_hash' => $this->globalDesignHash(),
            'old_global_settings_hash' => $oldGlobalSettingsHash,
            'new_global_settings_hash' => $globalsChanged
                ? $this->builderValueHash(
                    [
                        'tokens' => $this->sanitizeGlobalDesignTokens(get_option('h4bs_global_design_tokens', [])),
                        'custom_theme_packs' => $this->customThemePacks(),
                        'active_theme_pack' => $this->activeThemePackSlug(),
                    ]
                )
                : $oldGlobalSettingsHash,
            'global_revision' => $this->globalDesignRevision(),
        ];
        $layoutWritten = $layoutChanged || $pageLayoutChanged || $pageTitleModeChanged || $pageCssChanged || $templateConditionsChanged || $builderStateChanged;
        if ($layoutWritten) {
            do_action('h4bs_builder_layout_saved', $postId, $changeSet);
        }
        if ($globalsChanged) {
            do_action('h4bs_builder_global_tokens_saved', $this->globalDesignTokens(), $changeSet);
        }
        $purgeResult = $this->purgeBuilderCaches($postId, $changeSet);

        wp_send_json_success(
            [
                'saved'      => true,
                'no_op'      => ! $layoutWritten && ! $globalsChanged,
                'updated_at' => current_time('mysql'),
                'layout'     => $layout,
                'layout_hash' => $newLayoutHash,
                'page_layout' => $pageLayout,
                'page_title_mode' => $pageTitleMode,
                'page_title_visible' => $this->shouldShowPageTitle($postId, $layout, $pageLayout, $pageTitleMode),
                'page_css' => $pageCss,
                'page_css_hash' => $this->builderValueHash($pageCss),
                'global_design_tokens' => $this->globalDesignTokens(),
                'active_theme_pack' => $this->activeThemePackSlug(),
                'custom_theme_packs' => $this->customThemePacks(),
                'template_conditions' => $templateConditions,
                'saved_layout' => $saveLayout,
                'saved_globals' => $saveGlobals,
                'layout_written' => $layoutWritten,
                'globals_written' => $globalsChanged,
                'global_revision' => $this->globalDesignRevision(),
                'global_hash' => $this->globalDesignHash(),
                'global_settings_hash' => (string) $changeSet['new_global_settings_hash'],
                'change_set' => $changeSet,
                'purge_result' => $purgeResult,
            ]
        );
    }

    public function ajaxPublishPage(): void
    {
        check_ajax_referer('h4bs_builder_studio_save', 'nonce');

        $postId = isset($_POST['post_id']) ? absint(wp_unslash($_POST['post_id'])) : 0;
        if ($postId <= 0 || ! current_user_can('edit_post', $postId)) {
            wp_send_json_error(['message' => __('Permission denied.', 'help4-builder-suite')], 403);
        }

        $post = get_post($postId);
        if (! $post instanceof WP_Post) {
            wp_send_json_error(['message' => __('Page not found.', 'help4-builder-suite')], 404);
        }

        $postTypeObject = get_post_type_object((string) $post->post_type);
        $publishCapability = $postTypeObject instanceof \WP_Post_Type && isset($postTypeObject->cap->publish_posts)
            ? (string) $postTypeObject->cap->publish_posts
            : 'publish_posts';
        if ($publishCapability === '' || ! current_user_can($publishCapability)) {
            wp_send_json_error(['message' => __('You do not have permission to publish this content.', 'help4-builder-suite')], 403);
        }

        $oldStatus = (string) get_post_status($postId);
        if ($oldStatus !== 'publish') {
            $updated = wp_update_post(
                [
                    'ID' => $postId,
                    'post_status' => 'publish',
                ],
                true
            );
            if (is_wp_error($updated)) {
                wp_send_json_error(
                    [
                        'message' => __('WordPress could not publish this page.', 'help4-builder-suite'),
                        'code' => sanitize_key((string) $updated->get_error_code()),
                    ],
                    500
                );
            }
        }

        $status = (string) get_post_status($postId);
        $statusObject = get_post_status_object($status);
        $statusLabel = $statusObject instanceof \stdClass && isset($statusObject->label)
            ? (string) $statusObject->label
            : ucfirst($status);
        $changeSet = [
            'post_id' => $postId,
            'old_status' => $oldStatus,
            'new_status' => $status,
            'post_status_changed' => $oldStatus !== $status,
        ];
        if ($oldStatus !== $status) {
            do_action('h4bs_builder_post_status_changed', $postId, $changeSet);
        }
        $purgeResult = $this->purgeBuilderCaches($postId, $changeSet);

        wp_send_json_success(
            [
                'post_id' => $postId,
                'post_status' => $status,
                'post_status_label' => $statusLabel,
                'published' => $status === 'publish',
                'permalink' => get_permalink($postId) ?: '',
                'purge_result' => $purgeResult,
            ]
        );
    }

    public function ajaxLoadBuilderCatalog(): void
    {
        check_ajax_referer('h4bs_builder_studio_save', 'nonce');

        $postId = isset($_POST['post_id']) ? absint(wp_unslash($_POST['post_id'])) : 0;
        if ($postId <= 0 || ! current_user_can('edit_post', $postId)) {
            wp_send_json_error(['message' => __('Permission denied.', 'help4-builder-suite')], 403);
        }

        $catalog = isset($_POST['catalog'])
            ? sanitize_key((string) wp_unslash($_POST['catalog']))
            : '';
        try {
            $items = match ($catalog) {
                'templates' => $this->templatePresets(),
                'site-kits' => $this->siteKitCatalog(),
                'themes' => $this->themePackCatalog(),
                default => null,
            };
        } catch (\Throwable $error) {
            do_action('h4bs_builder_catalog_load_failed', $catalog, $postId, $error);
            wp_send_json_error(
                ['message' => __('This Builder library could not be loaded. Reload Studio and try again.', 'help4-builder-suite')],
                500
            );
        }

        if (! is_array($items)) {
            wp_send_json_error(['message' => __('Unknown Builder library.', 'help4-builder-suite')], 400);
        }

        wp_send_json_success(
            [
                'catalog' => $catalog,
                'items' => array_values($items),
            ]
        );
    }

    public function ajaxCopyBuilderSource(): void
    {
        check_ajax_referer('h4bs_builder_studio_save', 'nonce');

        $postId = isset($_POST['post_id']) ? absint(wp_unslash($_POST['post_id'])) : 0;
        $sourceId = isset($_POST['source_id']) ? absint(wp_unslash($_POST['source_id'])) : 0;
        if (
            $postId <= 0
            || $sourceId <= 0
            || $postId === $sourceId
            || ! current_user_can('edit_post', $postId)
            || ! current_user_can('edit_post', $sourceId)
        ) {
            wp_send_json_error(['message' => __('That Builder page cannot be copied.', 'help4-builder-suite')], 403);
        }

        $targetType = (string) get_post_type($postId);
        $sourceType = (string) get_post_type($sourceId);
        if ($targetType === '' || $sourceType !== $targetType || ! $this->isBuilderEnabled($sourceId)) {
            wp_send_json_error(['message' => __('Choose a Builder page of the same content type.', 'help4-builder-suite')], 400);
        }

        $layout = $this->sanitizeLayout($this->layoutForPost($sourceId));
        if ($layout === []) {
            wp_send_json_error(['message' => __('That Builder page has no layout to copy.', 'help4-builder-suite')], 400);
        }

        wp_send_json_success(
            [
                'source_id' => $sourceId,
                'source_title' => get_the_title($sourceId),
                'layout' => $layout,
                'page_layout' => $this->pageLayoutForPost($sourceId),
                'page_css' => $this->pageCustomCss($sourceId),
                'block_count' => count($layout),
            ]
        );
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function builderCopySources(int $postId): array
    {
        $postType = (string) get_post_type($postId);
        if ($postType === '' || ! post_type_exists($postType)) {
            return [];
        }

        $posts = get_posts(
            [
                'post_type' => $postType,
                'post_status' => ['publish', 'draft', 'pending', 'private', 'future'],
                'posts_per_page' => 36,
                'orderby' => 'modified',
                'order' => 'DESC',
                'no_found_rows' => true,
                'suppress_filters' => false,
            ]
        );

        $sources = [];
        foreach ($posts as $post) {
            if (
                ! $post instanceof WP_Post
                || $post->ID === $postId
                || ! current_user_can('edit_post', $post->ID)
                || ! $this->isBuilderEnabled($post->ID)
            ) {
                continue;
            }
            $layout = $this->sanitizeLayout($this->layoutForPost($post->ID));
            if ($layout === []) {
                continue;
            }
            $sources[] = [
                'id' => (int) $post->ID,
                'title' => get_the_title($post) ?: __('Untitled Builder page', 'help4-builder-suite'),
                'status' => sanitize_key((string) $post->post_status),
                'modified' => get_post_modified_time('c', true, $post) ?: '',
                'block_count' => count($layout),
            ];
            if (count($sources) >= 12) {
                break;
            }
        }

        return $sources;
    }

    public function ajaxImportSiteKit(): void
    {
        check_ajax_referer('h4bs_builder_studio_save', 'nonce');

        if (! current_user_can('edit_pages') || ! current_user_can('edit_theme_options')) {
            wp_send_json_error(['message' => __('Permission denied.', 'help4-builder-suite')], 403);
        }

        $kitSlug = isset($_POST['site_kit'])
            ? sanitize_key((string) wp_unslash($_POST['site_kit']))
            : '';
        $applyStyles = isset($_POST['apply_styles'])
            && rest_sanitize_boolean(wp_unslash($_POST['apply_styles']));
        $kit = $this->findSiteKit($kitSlug);
        if ($kit === null) {
            wp_send_json_error(['message' => __('That site kit is not available.', 'help4-builder-suite')], 404);
        }
        $submittedGlobalRevision = isset($_POST['global_revision'])
            ? absint(wp_unslash($_POST['global_revision']))
            : 0;
        if ($applyStyles && $submittedGlobalRevision !== $this->globalDesignRevision()) {
            wp_send_json_error(
                [
                    'code' => 'global_revision_conflict',
                    'message' => __('Global styles changed in another session. Reload Studio before importing a kit style.', 'help4-builder-suite'),
                    'global_revision' => $this->globalDesignRevision(),
                ],
                409
            );
        }

        $presetMap = $this->templatePresetMap();
        $preflightErrors = [];
        foreach (array_merge((array) ($kit['pages'] ?? []), (array) ($kit['templates'] ?? [])) as $item) {
            if (! is_array($item)) {
                $preflightErrors[] = 'invalid_item';
                continue;
            }
            $templateSlug = sanitize_key((string) ($item['template_slug'] ?? ''));
            if ($templateSlug === '' || ! isset($presetMap[$templateSlug])) {
                $preflightErrors[] = 'missing_template:' . $templateSlug;
                continue;
            }
            $layout = $this->sanitizeLayout($presetMap[$templateSlug]['layout'] ?? []);
            foreach ($this->layoutStructureErrors($layout) as $structureError) {
                $preflightErrors[] = $templateSlug . ':' . $structureError;
            }
        }
        if ($preflightErrors !== []) {
            wp_send_json_error(
                [
                    'message' => __('The site kit failed its layout preflight and no content was created.', 'help4-builder-suite'),
                    'errors' => array_values(array_unique($preflightErrors)),
                ],
                422
            );
        }

        $createdPostIds = [];
        $createdMenuItemIds = [];
        $results = [
            'pages' => [],
            'templates' => [],
        ];
        $pageIds = [];
        foreach ((array) ($kit['pages'] ?? []) as $item) {
            $result = $this->createOrReuseSiteKitPost($kit, $item, $presetMap, 'page');
            if (is_wp_error($result)) {
                $this->rollbackSiteKitImport($createdPostIds, $createdMenuItemIds, 0);
                wp_send_json_error(
                    [
                        'message' => $result->get_error_message(),
                        'code' => $result->get_error_code(),
                    ],
                    500
                );
            }
            if (! empty($result['created'])) {
                $createdPostIds[] = (int) $result['post_id'];
            }
            $itemKey = sanitize_key((string) ($item['key'] ?? ''));
            if ($itemKey !== '') {
                $pageIds[$itemKey] = (int) $result['post_id'];
            }
            $results['pages'][] = $result;
        }

        $menuResult = $this->createOrReuseSiteKitMenu($kit, $pageIds);
        if (is_wp_error($menuResult)) {
            $this->rollbackSiteKitImport($createdPostIds, $createdMenuItemIds, 0);
            wp_send_json_error(
                [
                    'message' => $menuResult->get_error_message(),
                    'code' => $menuResult->get_error_code(),
                ],
                500
            );
        }
        $menuId = (int) ($menuResult['menu_id'] ?? 0);
        $menuCreated = ! empty($menuResult['created']);
        $createdMenuItemIds = array_values(array_map('absint', (array) ($menuResult['created_item_ids'] ?? [])));
        $menuSlug = sanitize_title((string) ($menuResult['menu_slug'] ?? ''));

        foreach ((array) ($kit['templates'] ?? []) as $item) {
            $result = $this->createOrReuseSiteKitPost($kit, $item, $presetMap, 'h4_template', $menuSlug);
            if (is_wp_error($result)) {
                $this->rollbackSiteKitImport(
                    $createdPostIds,
                    $createdMenuItemIds,
                    $menuCreated ? $menuId : 0
                );
                wp_send_json_error(
                    [
                        'message' => $result->get_error_message(),
                        'code' => $result->get_error_code(),
                    ],
                    500
                );
            }
            if (! empty($result['created'])) {
                $createdPostIds[] = (int) $result['post_id'];
            }
            $results['templates'][] = $result;
        }

        $styleResult = [
            'requested' => $applyStyles,
            'changed' => false,
            'theme_pack' => sanitize_key((string) ($kit['theme_pack'] ?? '')),
        ];
        if ($applyStyles) {
            $styleResult = $this->applySiteKitStyleTokens($kit, $createdPostIds);
        }

        do_action(
            'h4bs_builder_site_kit_imported',
            $kitSlug,
            [
                'results' => $results,
                'menu' => $menuResult,
                'styles' => $styleResult,
                'user_id' => get_current_user_id(),
            ]
        );

        wp_send_json_success(
            [
                'message' => __('Draft site kit installed. Review each item before publishing or assigning it.', 'help4-builder-suite'),
                'site_kit' => $kitSlug,
                'pages' => $results['pages'],
                'templates' => $results['templates'],
                'menu' => $menuResult,
                'styles' => $styleResult,
                'created_count' => count($createdPostIds),
                'reused_count' => count($results['pages']) + count($results['templates']) - count($createdPostIds),
            ]
        );
    }

    /**
     * @param array<string, mixed> $kit
     * @param array<string, mixed> $item
     * @param array<string, array<string, mixed>> $presetMap
     * @return array<string, mixed>|\WP_Error
     */
    private function createOrReuseSiteKitPost(
        array $kit,
        array $item,
        array $presetMap,
        string $postType,
        string $menuSlug = ''
    ) {
        $kitSlug = sanitize_key((string) ($kit['slug'] ?? ''));
        $itemKey = sanitize_key((string) ($item['key'] ?? ''));
        $templateSlug = sanitize_key((string) ($item['template_slug'] ?? ''));
        if ($kitSlug === '' || $itemKey === '' || ! isset($presetMap[$templateSlug])) {
            return new \WP_Error('invalid_site_kit_item', __('The site kit contains an invalid item.', 'help4-builder-suite'));
        }

        $existingPostId = $this->findSiteKitPostId($postType, $kitSlug, $itemKey);
        if ($existingPostId > 0) {
            $postId = $existingPostId;
            return $this->siteKitPostResult($postId, false, 'reused');
        }

        $title = sanitize_text_field((string) ($item['title'] ?? $itemKey));
        $desiredSlug = sanitize_title((string) ($item['slug'] ?? $itemKey));
        $conflict = $desiredSlug !== '' ? get_page_by_path($desiredSlug, OBJECT, $postType) : null;
        if ($conflict instanceof WP_Post) {
            $desiredSlug = sanitize_title($kitSlug . '-' . $desiredSlug);
        }

        $postId = wp_insert_post(
            [
                'post_type' => $postType,
                'post_status' => 'draft',
                'post_title' => $title,
                'post_name' => $desiredSlug,
                'post_content' => '',
            ],
            true
        );
        if (is_wp_error($postId) || ! $postId) {
            return is_wp_error($postId)
                ? $postId
                : new \WP_Error('site_kit_insert_failed', __('A site kit draft could not be created.', 'help4-builder-suite'));
        }
        $postId = (int) $postId;

        $preset = $presetMap[$templateSlug];
        $layout = $this->prepareSiteKitLayout(
            $this->sanitizeLayout($preset['layout'] ?? [])
        );
        if ($postType === 'page' && ! $this->layoutHasHeadingLevelOne($layout)) {
            array_unshift(
                $layout,
                [
                    'schema_version' => '1.0',
                    'id' => sanitize_key('h4kit_' . $kitSlug . '_' . $itemKey . '_title'),
                    'type' => 'heading',
                    'settings' => [
                        'text' => $title,
                        'tag' => 'h1',
                        'design_text_align' => 'center',
                        'design_padding_top' => '42',
                        'design_padding_right' => '20',
                        'design_padding_bottom' => '18',
                        'design_padding_left' => '20',
                    ],
                    'visibility' => [],
                    'parent_id' => '',
                    'slot' => 0,
                    'extensions' => [],
                ]
            );
        }
        if ($menuSlug !== '') {
            foreach ($layout as &$block) {
                if ((string) ($block['type'] ?? '') !== 'menu') {
                    continue;
                }
                $block['settings']['menu_slug'] = $menuSlug;
            }
            unset($block);
        }
        $pageLayout = $this->normalizePageLayout(
            (string) ($item['page_layout'] ?? ($preset['page_layout'] ?? 'full_width'))
        );
        update_post_meta($postId, '_h4_builder_mode', 'help4');
        update_post_meta($postId, '_h4_builder_enabled', '1');
        update_post_meta($postId, self::DOCUMENT_ORIGIN_META, 'builder_studio');
        update_post_meta($postId, '_h4_builder_page_layout', $pageLayout);
        update_post_meta(
            $postId,
            '_h4_builder_layout',
            wp_slash((string) wp_json_encode($layout, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE))
        );
        update_post_meta($postId, '_h4bs_site_kit_managed', '1');
        update_post_meta($postId, '_h4bs_site_kit_slug', $kitSlug);
        update_post_meta($postId, '_h4bs_site_kit_item_key', $itemKey);
        update_post_meta($postId, '_h4bs_site_kit_schema', '1.0');
        $this->registerSiteKitPost($postType, $kitSlug, $itemKey, $postId);

        if ($postType === 'h4_template') {
            $conditions = [
                'role' => sanitize_key((string) ($item['role'] ?? 'general')),
                'scope' => sanitize_key((string) ($item['scope'] ?? 'entire_site')),
                'include_ids' => '',
                'exclude_ids' => '',
                'include_types' => '',
                'exclude_types' => '',
                'user_roles' => '',
                'priority' => max(-100, min(999, (int) ($item['priority'] ?? 10))),
            ];
            $this->persistTemplateConditions($postId, $conditions);
        }

        return $this->siteKitPostResult($postId, true, 'created');
    }

    private function findSiteKitPostId(string $postType, string $kitSlug, string $itemKey): int
    {
        $registry = $this->siteKitRegistry();
        $registryKey = $this->siteKitRegistryKey($postType, $kitSlug, $itemKey);
        $registeredId = absint($registry[$registryKey] ?? 0);
        if ($this->isOwnedSiteKitPost($registeredId, $postType, $kitSlug, $itemKey)) {
            return $registeredId;
        }
        if ($registeredId > 0) {
            unset($registry[$registryKey]);
            update_option('h4bs_site_kit_registry', $registry, false);
        }

        $candidateIds = get_posts(
            [
                'post_type' => $postType,
                'post_status' => ['publish', 'private', 'draft', 'pending', 'future'],
                'fields' => 'ids',
                'posts_per_page' => 500,
                'orderby' => 'modified',
                'order' => 'DESC',
                'no_found_rows' => true,
            ]
        );
        if ($candidateIds !== []) {
            update_meta_cache('post', $candidateIds);
        }
        foreach ($candidateIds as $candidateId) {
            $candidateId = absint($candidateId);
            if (! $this->isOwnedSiteKitPost($candidateId, $postType, $kitSlug, $itemKey)) {
                continue;
            }
            $this->registerSiteKitPost($postType, $kitSlug, $itemKey, $candidateId);
            return $candidateId;
        }
        return 0;
    }

    private function isOwnedSiteKitPost(
        int $postId,
        string $postType,
        string $kitSlug,
        string $itemKey
    ): bool {
        return $postId > 0
            && get_post_type($postId) === $postType
            && get_post_status($postId) !== false
            && (string) get_post_meta($postId, '_h4bs_site_kit_managed', true) === '1'
            && (string) get_post_meta($postId, '_h4bs_site_kit_slug', true) === $kitSlug
            && (string) get_post_meta($postId, '_h4bs_site_kit_item_key', true) === $itemKey;
    }

    /**
     * @return array<string, int>
     */
    private function siteKitRegistry(): array
    {
        $raw = get_option('h4bs_site_kit_registry', []);
        $source = is_array($raw) ? $raw : [];
        $registry = [];
        foreach ($source as $key => $postId) {
            $safeKey = preg_replace('/[^a-z0-9:_-]/', '', strtolower((string) $key)) ?? '';
            $safePostId = absint($postId);
            if ($safeKey !== '' && $safePostId > 0) {
                $registry[$safeKey] = $safePostId;
            }
        }
        return array_slice($registry, -1000, null, true);
    }

    private function siteKitRegistryKey(string $postType, string $kitSlug, string $itemKey): string
    {
        return sanitize_key($postType) . ':' . sanitize_key($kitSlug) . ':' . sanitize_key($itemKey);
    }

    private function registerSiteKitPost(string $postType, string $kitSlug, string $itemKey, int $postId): void
    {
        if ($postId <= 0) {
            return;
        }
        $registry = $this->siteKitRegistry();
        $registry[$this->siteKitRegistryKey($postType, $kitSlug, $itemKey)] = $postId;
        update_option('h4bs_site_kit_registry', array_slice($registry, -1000, null, true), false);
    }

    /**
     * @param array<int, array<string, mixed>> $layout
     */
    private function layoutHasHeadingLevelOne(array $layout): bool
    {
        foreach ($layout as $block) {
            if (
                is_array($block)
                && (string) ($block['type'] ?? '') === 'heading'
                && strtolower((string) ($block['settings']['tag'] ?? '')) === 'h1'
            ) {
                return true;
            }
        }
        return false;
    }

    /**
     * Fill missing breakpoint values on kit-owned copies without changing the
     * source preset or overriding values a designer already supplied.
     *
     * @param array<int, array<string, mixed>> $layout
     * @return array<int, array<string, mixed>>
     */
    private function prepareSiteKitLayout(array $layout): array
    {
        foreach ($layout as &$block) {
            if (! is_array($block) || ! in_array((string) ($block['type'] ?? ''), ['section', 'container', 'inner_section'], true)) {
                continue;
            }
            $settings = is_array($block['settings'] ?? null) ? $block['settings'] : [];
            $columns = max(1, min(self::MAX_COLUMNS, (int) ($settings['columns'] ?? 1)));
            if (! isset($settings['columns_tablet']) || (string) $settings['columns_tablet'] === '') {
                $settings['columns_tablet'] = (string) min(2, $columns);
            }
            if (! isset($settings['columns_mobile']) || (string) $settings['columns_mobile'] === '') {
                $settings['columns_mobile'] = '1';
            }
            $gap = max(0, min(160, (int) ($settings['gap'] ?? 20)));
            if (! isset($settings['gap_tablet']) || (string) $settings['gap_tablet'] === '') {
                $settings['gap_tablet'] = (string) min(24, $gap);
            }
            if (! isset($settings['gap_mobile']) || (string) $settings['gap_mobile'] === '') {
                $settings['gap_mobile'] = (string) min(16, $gap);
            }
            if (! isset($settings['content_width_mode_mobile']) || (string) $settings['content_width_mode_mobile'] === '') {
                $settings['content_width_mode_mobile'] = 'full';
            }
            foreach (['left', 'right'] as $side) {
                $desktopKey = 'design_padding_' . $side;
                $mobileKey = $desktopKey . '_mobile';
                if (! isset($settings[$mobileKey]) || (string) $settings[$mobileKey] === '') {
                    $desktop = isset($settings[$desktopKey]) ? (int) $settings[$desktopKey] : 20;
                    $settings[$mobileKey] = (string) max(14, min(20, $desktop));
                }
            }
            $block['settings'] = $settings;
        }
        unset($block);
        return $layout;
    }

    /**
     * @return array<string, mixed>
     */
    private function siteKitPostResult(int $postId, bool $created, string $status): array
    {
        $postStatus = (string) get_post_status($postId);
        return [
            'post_id' => $postId,
            'title' => get_the_title($postId),
            'post_type' => (string) get_post_type($postId),
            'post_status' => $postStatus,
            'created' => $created,
            'status' => $status,
            'builder_url' => $this->builderStudioUrl($postId),
            'edit_url' => get_edit_post_link($postId, '') ?: '',
            'publish_locked' => ! in_array($postStatus, ['draft', 'pending', 'auto-draft'], true),
        ];
    }

    /**
     * @param array<string, mixed> $kit
     * @param array<string, int> $pageIds
     * @return array<string, mixed>|\WP_Error
     */
    private function createOrReuseSiteKitMenu(array $kit, array $pageIds)
    {
        $kitSlug = sanitize_key((string) ($kit['slug'] ?? ''));
        $menuSlug = sanitize_title('h4-' . $kitSlug . '-navigation');
        $menuObject = wp_get_nav_menu_object($menuSlug);
        $created = false;
        if ($menuObject && (string) get_term_meta((int) $menuObject->term_id, '_h4bs_site_kit_slug', true) !== $kitSlug) {
            $menuObject = false;
            $menuSlug = sanitize_title('h4-' . $kitSlug . '-starter-navigation');
        }
        if (! $menuObject) {
            $menuName = sprintf(
                /* translators: %s: site kit label */
                __('Help4 %s Navigation', 'help4-builder-suite'),
                sanitize_text_field((string) ($kit['label'] ?? $kitSlug))
            );
            $nameConflict = wp_get_nav_menu_object($menuName);
            if ($nameConflict && (string) get_term_meta((int) $nameConflict->term_id, '_h4bs_site_kit_slug', true) !== $kitSlug) {
                $menuName .= ' - ' . $kitSlug;
            } elseif ($nameConflict) {
                $menuObject = $nameConflict;
            }
        }
        if (! $menuObject) {
            $menuId = wp_create_nav_menu($menuName);
            if (is_wp_error($menuId)) {
                return $menuId;
            }
            $menuObject = wp_get_nav_menu_object((int) $menuId);
            if (! $menuObject) {
                return new \WP_Error('site_kit_menu_failed', __('The site kit navigation menu could not be loaded.', 'help4-builder-suite'));
            }
            $termUpdate = wp_update_term((int) $menuObject->term_id, 'nav_menu', ['slug' => $menuSlug]);
            if (is_wp_error($termUpdate)) {
                wp_delete_nav_menu((int) $menuObject->term_id);
                return $termUpdate;
            }
            update_term_meta((int) $menuObject->term_id, '_h4bs_site_kit_slug', $kitSlug);
            update_term_meta((int) $menuObject->term_id, '_h4bs_site_kit_schema', '1.0');
            $created = true;
        }

        $menuId = (int) $menuObject->term_id;
        $existingTargets = [];
        $menuItems = wp_get_nav_menu_items($menuId, ['post_status' => 'any']);
        foreach (is_array($menuItems) ? $menuItems : [] as $menuItem) {
            if ($menuItem instanceof WP_Post) {
                $existingTargets[absint($menuItem->object_id)] = true;
            }
        }
        $createdItemIds = [];
        foreach ($pageIds as $pageId) {
            $pageId = absint($pageId);
            if ($pageId <= 0 || isset($existingTargets[$pageId])) {
                continue;
            }
            $menuItemId = wp_update_nav_menu_item(
                $menuId,
                0,
                [
                    'menu-item-object-id' => $pageId,
                    'menu-item-object' => 'page',
                    'menu-item-type' => 'post_type',
                    'menu-item-title' => get_the_title($pageId),
                    'menu-item-status' => 'publish',
                ]
            );
            if (is_wp_error($menuItemId)) {
                foreach ($createdItemIds as $createdItemId) {
                    wp_delete_post($createdItemId, true);
                }
                if ($created) {
                    wp_delete_nav_menu($menuId);
                }
                return $menuItemId;
            }
            $createdItemIds[] = (int) $menuItemId;
        }

        $menuObject = wp_get_nav_menu_object($menuId);
        return [
            'menu_id' => $menuId,
            'menu_slug' => $menuObject ? (string) $menuObject->slug : $menuSlug,
            'menu_name' => $menuObject ? (string) $menuObject->name : '',
            'created' => $created,
            'created_item_ids' => $createdItemIds,
            'item_count' => count($pageIds),
        ];
    }

    /**
     * @param array<int, int> $createdPostIds
     * @param array<int, int> $createdMenuItemIds
     */
    private function rollbackSiteKitImport(array $createdPostIds, array $createdMenuItemIds, int $createdMenuId): void
    {
        foreach (array_reverse($createdMenuItemIds) as $menuItemId) {
            wp_delete_post(absint($menuItemId), true);
        }
        if ($createdMenuId > 0) {
            wp_delete_nav_menu($createdMenuId);
        }
        foreach (array_reverse($createdPostIds) as $postId) {
            $postId = absint($postId);
            if (
                $postId > 0
                && (string) get_post_meta($postId, '_h4bs_site_kit_managed', true) === '1'
                && get_post_status($postId) === 'draft'
            ) {
                wp_delete_post($postId, true);
            }
        }
        if ($createdPostIds !== []) {
            $removed = array_fill_keys(array_map('absint', $createdPostIds), true);
            $registry = array_filter(
                $this->siteKitRegistry(),
                static fn (int $postId): bool => ! isset($removed[$postId])
            );
            update_option('h4bs_site_kit_registry', $registry, false);
        }
    }

    /**
     * @param array<string, mixed> $kit
     * @param array<int, int> $affectedPostIds
     * @return array<string, mixed>
     */
    private function applySiteKitStyleTokens(array $kit, array $affectedPostIds): array
    {
        $themePackSlug = sanitize_key((string) ($kit['theme_pack'] ?? ''));
        $themePack = $this->findThemePack($themePackSlug);
        if ($themePack === null) {
            return [
                'requested' => true,
                'changed' => false,
                'theme_pack' => $themePackSlug,
                'message' => __('The kit drafts were created, but its style pack is unavailable.', 'help4-builder-suite'),
            ];
        }

        $oldTokens = $this->sanitizeGlobalDesignTokens(get_option('h4bs_global_design_tokens', []));
        $nextTokens = $oldTokens;
        foreach ((array) ($themePack['tokens'] ?? []) as $group => $values) {
            $safeGroup = sanitize_key((string) $group);
            if (! isset($nextTokens[$safeGroup]) || ! is_array($values)) {
                continue;
            }
            foreach ($values as $key => $value) {
                $safeKey = sanitize_key((string) $key);
                if ($safeKey !== '' && array_key_exists($safeKey, $nextTokens[$safeGroup])) {
                    $nextTokens[$safeGroup][$safeKey] = (string) $value;
                }
            }
        }
        $nextTokens = $this->sanitizeGlobalDesignTokens($nextTokens);
        $oldHash = $this->builderValueHash($oldTokens);
        $newHash = $this->builderValueHash($nextTokens);
        $oldActive = $this->activeThemePackSlug();
        $changed = ! hash_equals($oldHash, $newHash) || $oldActive !== $themePackSlug;
        if (! $changed) {
            return [
                'requested' => true,
                'changed' => false,
                'theme_pack' => $themePackSlug,
                'global_revision' => $this->globalDesignRevision(),
            ];
        }

        update_option('h4bs_global_design_tokens', $nextTokens, false);
        update_option('h4bs_active_theme_pack', $themePackSlug, false);
        update_option('h4bs_global_design_revision', $this->globalDesignRevision() + 1, false);
        $changeSet = [
            'post_id' => absint($affectedPostIds[0] ?? 0),
            'layout_changed' => false,
            'page_layout_changed' => false,
            'page_css_changed' => false,
            'template_conditions_changed' => false,
            'builder_state_changed' => false,
            'globals_changed' => true,
            'old_global_hash' => $oldHash,
            'new_global_hash' => $this->globalDesignHash(),
            'global_revision' => $this->globalDesignRevision(),
        ];
        do_action('h4bs_builder_global_tokens_saved', $this->globalDesignTokens(), $changeSet);
        $purgeResult = $this->purgeBuilderCaches(absint($affectedPostIds[0] ?? 0), $changeSet);

        return [
            'requested' => true,
            'changed' => true,
            'theme_pack' => $themePackSlug,
            'global_revision' => $this->globalDesignRevision(),
            'purge_result' => $purgeResult,
        ];
    }

    public function ajaxLayoutRecovery(): void
    {
        check_ajax_referer('h4bs_builder_studio_save', 'nonce');

        $postId = isset($_POST['post_id']) ? absint(wp_unslash($_POST['post_id'])) : 0;
        if ($postId <= 0 || ! current_user_can('edit_post', $postId)) {
            wp_send_json_error(['message' => __('Permission denied.', 'help4-builder-suite')], 403);
        }

        wp_send_json_success(
            [
                'entries' => $this->layoutRecoveryEntries($postId),
                'current_layout_hash' => $this->builderValueHash($this->sanitizeLayout($this->layoutForPost($postId))),
            ]
        );
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function layoutRecoveryEntries(int $postId): array
    {
        $history = get_post_meta($postId, '_h4_builder_layout_recovery', true);
        $history = is_array($history) ? array_slice($history, 0, 5) : [];
        $entries = [];
        foreach ($history as $entry) {
            if (! is_array($entry)) {
                continue;
            }
            $layout = $this->sanitizeLayout($entry['layout'] ?? []);
            if (! $layout || $this->layoutStructureErrors($layout)) {
                continue;
            }
            $hash = $this->builderValueHash($layout);
            $storedHash = sanitize_text_field((string) ($entry['hash'] ?? ''));
            if ($storedHash !== '' && ! hash_equals($hash, $storedHash)) {
                continue;
            }
            $userId = absint($entry['user_id'] ?? 0);
            $user = $userId > 0 ? get_userdata($userId) : false;
            $entries[] = [
                'created_at' => sanitize_text_field((string) ($entry['created_at'] ?? '')),
                'user_id' => $userId,
                'user_name' => $user ? sanitize_text_field((string) $user->display_name) : __('Unknown editor', 'help4-builder-suite'),
                'hash' => $hash,
                'element_count' => count($layout),
                'layout' => $layout,
            ];
        }
        return $entries;
    }

    public function ajaxRenderPreview(): void
    {
        check_ajax_referer('h4bs_builder_studio_save', 'nonce');

        $postId = isset($_POST['post_id']) ? absint(wp_unslash($_POST['post_id'])) : 0;
        if ($postId <= 0 || ! current_user_can('edit_post', $postId)) {
            wp_send_json_error(['message' => __('Permission denied.', 'help4-builder-suite')], 403);
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitizeLayout validates the decoded Builder document and preserves safe HTML content.
        $rawLayout = isset($_POST['layout']) ? (string) wp_unslash($_POST['layout']) : '[]';
        $rawPageLayout = isset($_POST['page_layout']) ? sanitize_key((string) wp_unslash($_POST['page_layout'])) : 'default';
        $rawPageTitleMode = isset($_POST['page_title_mode']) ? sanitize_key((string) wp_unslash($_POST['page_title_mode'])) : 'auto';
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitizePageCustomCss applies the Builder CSS allowlist before preview rendering.
        $rawPageCss = isset($_POST['page_css']) ? (string) wp_unslash($_POST['page_css']) : '';
        $rawGlobalDesignTokens = isset($_POST['global_design_tokens']) ? sanitize_textarea_field((string) wp_unslash($_POST['global_design_tokens'])) : '';

        $layout = $this->sanitizeLayout($rawLayout);
        $pageLayout = $this->normalizePageLayout($rawPageLayout);
        $postType = sanitize_key((string) get_post_type($postId));
        $previewTemplateSlots = $this->previewTemplateSlots($postId, $postType, $pageLayout);
        $globalDesignTokens = $this->sanitizeGlobalDesignTokens($rawGlobalDesignTokens);

        $this->runtimeGlobalDesignTokens = $globalDesignTokens;
        $this->runtimePreviewPageCss = $this->sanitizePageCustomCss($rawPageCss);
        $this->runtimePreviewPageTitleMode = $this->normalizePageTitleMode($rawPageTitleMode);
        $this->enablePreviewContext($postId, $postType);
        try {
            $html = $this->renderPreviewCanvasHtml(
                $layout,
                $pageLayout,
                $postType,
                $previewTemplateSlots
            );
        } finally {
            $this->disablePreviewContext();
            $this->runtimeGlobalDesignTokens = null;
            $this->runtimePreviewPageCss = null;
            $this->runtimePreviewPageTitleMode = null;
        }

        wp_send_json_success(
            [
                'html' => $html,
                'preview_template_slots' => $previewTemplateSlots,
            ]
        );
    }

    public function ajaxStoreDraftPreview(): void
    {
        check_ajax_referer('h4bs_builder_studio_save', 'nonce');

        $postId = isset($_POST['post_id']) ? absint(wp_unslash($_POST['post_id'])) : 0;
        if ($postId <= 0 || ! current_user_can('edit_post', $postId)) {
            wp_send_json_error(['message' => __('Permission denied.', 'help4-builder-suite')], 403);
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitizeLayout validates the decoded Builder document and preserves safe HTML content.
        $rawLayout = isset($_POST['layout']) ? (string) wp_unslash($_POST['layout']) : '[]';
        $rawPageLayout = isset($_POST['page_layout']) ? sanitize_key((string) wp_unslash($_POST['page_layout'])) : 'default';
        $rawPageTitleMode = isset($_POST['page_title_mode']) ? sanitize_key((string) wp_unslash($_POST['page_title_mode'])) : 'auto';
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitizePageCustomCss applies the Builder CSS allowlist before draft storage.
        $rawPageCss = isset($_POST['page_css']) ? (string) wp_unslash($_POST['page_css']) : '';
        $rawGlobalDesignTokens = isset($_POST['global_design_tokens']) ? sanitize_textarea_field((string) wp_unslash($_POST['global_design_tokens'])) : '';
        $draft = $this->draftPreviewState(
            $postId,
            $this->sanitizeLayout($rawLayout),
            $this->normalizePageLayout($rawPageLayout),
            $this->normalizePageTitleMode($rawPageTitleMode),
            $this->sanitizePageCustomCss($rawPageCss),
            $this->sanitizeGlobalDesignTokens($rawGlobalDesignTokens)
        );
        set_transient($this->draftPreviewTransientKey($postId), $draft, 10 * MINUTE_IN_SECONDS);

        wp_send_json_success(
            [
                'url' => $this->draftPreviewUrl($postId, $draft['fingerprint']),
                'fingerprint' => $draft['fingerprint'],
                'asset_manifest' => $this->previewAssetManifest(),
            ]
        );
    }

    public function prepareDraftPreviewRequest(): void
    {
        if (is_admin() || ! isset($_GET['h4bs_builder_draft_preview'])) {
            return;
        }
        $postId = isset($_GET['h4bs_post_id']) ? absint(wp_unslash($_GET['h4bs_post_id'])) : 0;
        $nonce = isset($_GET['_wpnonce'])
            ? sanitize_text_field((string) wp_unslash($_GET['_wpnonce']))
            : '';
        if (
            $postId <= 0
            || ! is_user_logged_in()
            || ! current_user_can('edit_post', $postId)
            || ! wp_verify_nonce($nonce, $this->draftPreviewNonceAction($postId))
        ) {
            wp_die(esc_html__('Draft preview access denied.', 'help4-builder-suite'), '', ['response' => 403]);
        }

        $draft = get_transient($this->draftPreviewTransientKey($postId));
        if (! is_array($draft) || (int) ($draft['user_id'] ?? 0) !== get_current_user_id()) {
            wp_die(esc_html__('This Builder draft preview expired. Make another edit to refresh it.', 'help4-builder-suite'), '', ['response' => 410]);
        }
        $requestedFingerprint = isset($_GET['h4bs_draft_fingerprint'])
            ? sanitize_text_field((string) wp_unslash($_GET['h4bs_draft_fingerprint']))
            : '';
        $storedFingerprint = sanitize_text_field((string) ($draft['fingerprint'] ?? ''));
        if (
            $requestedFingerprint === ''
            || $storedFingerprint === ''
            || ! hash_equals($storedFingerprint, $requestedFingerprint)
        ) {
            wp_die(esc_html__('This Builder draft revision is no longer current. Refresh the preview and try again.', 'help4-builder-suite'), '', ['response' => 409]);
        }

        $this->runtimePreviewLayout = $this->sanitizeLayout($draft['layout'] ?? []);
        $this->runtimePreviewPageLayout = $this->normalizePageLayout((string) ($draft['page_layout'] ?? 'default'));
        $this->runtimePreviewPageTitleMode = $this->normalizePageTitleMode((string) ($draft['page_title_mode'] ?? 'auto'));
        $this->runtimePreviewPageCss = $this->sanitizePageCustomCss((string) ($draft['page_css'] ?? ''));
        $this->runtimeGlobalDesignTokens = $this->sanitizeGlobalDesignTokens($draft['global_design_tokens'] ?? []);
        $this->enablePreviewContext($postId, sanitize_key((string) get_post_type($postId)));
        nocache_headers();
        header('X-H4BS-Draft-Preview: 1');
        header('X-Robots-Tag: noindex, nofollow, noarchive', true);
    }

    public function renderTemplateDraftPreviewDocument(): void
    {
        if (
            ! $this->previewContextActive
            || $this->previewContextPostId <= 0
            || $this->previewContextPostType !== 'h4_template'
        ) {
            return;
        }

        $templateId = $this->previewContextPostId;
        $template = get_post($templateId);
        if (! $template instanceof WP_Post || $template->post_type !== 'h4_template') {
            return;
        }

        $this->activateTemplatePreviewQueryContext();
        $layout = $this->sanitizeLayout($this->layoutForPost($templateId));
        $pageLayout = $this->pageLayoutForPost($templateId);
        $html = $this->renderPreviewCanvasHtml(
            $layout,
            $pageLayout,
            'h4_template',
            [
                'header_template_id' => 0,
                'footer_template_id' => 0,
                'header_html' => '',
                'footer_html' => '',
            ]
        );

        status_header(200);
        nocache_headers();
        ?><!doctype html>
        <html <?php language_attributes(); ?>>
        <head>
            <meta charset="<?php bloginfo('charset'); ?>">
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <title><?php echo esc_html(get_the_title($templateId) ?: __('Builder Template Preview', 'help4-builder-suite')); ?></title>
            <?php wp_head(); ?>
        </head>
        <body <?php body_class('h4bs-builder-template-draft-preview'); ?>>
            <?php
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Builder preview markup is produced by sanitized renderers.
            echo $html;
            wp_footer();
            ?>
        </body>
        </html>
        <?php
        exit;
    }

    private function draftPreviewNonceAction(int $postId): string
    {
        return 'h4bs_builder_draft_preview_' . max(0, $postId);
    }

    private function draftPreviewTransientKey(int $postId): string
    {
        return 'h4bs_draft_' . substr(
            hash('sha256', (string) get_current_user_id() . '|' . (string) max(0, $postId)),
            0,
            32
        );
    }

    /**
     * @param array<int, array<string, mixed>> $layout
     * @param array<string, mixed> $globalDesignTokens
     * @return array<string, mixed>
     */
    private function draftPreviewState(
        int $postId,
        array $layout,
        string $pageLayout,
        string $pageTitleMode,
        string $pageCss,
        array $globalDesignTokens
    ): array {
        $draft = [
            'post_id' => $postId,
            'user_id' => get_current_user_id(),
            'layout' => $this->sanitizeLayout($layout),
            'page_layout' => $this->normalizePageLayout($pageLayout),
            'page_title_mode' => $this->normalizePageTitleMode($pageTitleMode),
            'page_css' => $this->sanitizePageCustomCss($pageCss),
            'global_design_tokens' => $this->sanitizeGlobalDesignTokens($globalDesignTokens),
            'created_at' => time(),
        ];
        $fingerprintSource = wp_json_encode(
            [$draft['layout'], $draft['page_layout'], $draft['page_title_mode'], $draft['page_css'], $draft['global_design_tokens']],
            JSON_UNESCAPED_SLASHES
        );
        $draft['fingerprint'] = hash('sha256', is_string($fingerprintSource) ? $fingerprintSource : '');
        return $draft;
    }

    private function draftPreviewUrl(int $postId, string $fingerprint): string
    {
        $permalink = get_post_type($postId) === 'h4_template'
            ? home_url('/')
            : get_permalink($postId);
        if (! is_string($permalink) || $permalink === '') {
            return '';
        }
        return add_query_arg(
            [
                'h4bs_builder_draft_preview' => '1',
                'h4bs_post_id' => $postId,
                'h4bs_draft_fingerprint' => sanitize_text_field($fingerprint),
                '_wpnonce' => wp_create_nonce($this->draftPreviewNonceAction($postId)),
            ],
            $permalink
        );
    }

    /**
     * @param array<int, array<string, mixed>> $layout
     * @param array{
     *   header_template_id: int,
     *   footer_template_id: int,
     *   header_html: string,
     *   footer_html: string
     * } $previewTemplateSlots
     */
    private function renderPreviewCanvasHtml(
        array $layout,
        string $pageLayout,
        string $postType,
        array $previewTemplateSlots
    ): string {
        $postType = sanitize_key($postType);
        $pageLayout = $this->normalizePageLayout($pageLayout);
        $layout = $this->sanitizeLayout($layout);
        $canShowTemplateSlots = $postType !== 'h4_template' && $pageLayout !== 'canvas';
        $headerHtml = (string) ($previewTemplateSlots['header_html'] ?? '');
        $footerHtml = (string) ($previewTemplateSlots['footer_html'] ?? '');
        $previousPost = $GLOBALS['post'] ?? null;
        $previousWpQuery = $GLOBALS['wp_query'] ?? null;
        $previousWpTheQuery = $GLOBALS['wp_the_query'] ?? null;
        $this->activateTemplatePreviewQueryContext();
        $previewPost = $this->previewRenderPost();
        if ($previewPost instanceof WP_Post) {
            $GLOBALS['post'] = $previewPost;
            setup_postdata($previewPost);
        }

        ob_start();
        if ($canShowTemplateSlots && $headerHtml !== '') {
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Template slot HTML is generated by Builder Suite renderers.
            echo '<div class="h4bs-preview-site-slot h4bs-preview-site-slot-header" data-h4bs-preview-site-slot="header">' . $headerHtml . '</div>';
        }
        $usePanelWrap = $canShowTemplateSlots && $pageLayout === 'default';
        $showTitle = $canShowTemplateSlots && $this->shouldShowPageTitle(
            $this->previewContextPostId,
            $layout,
            $pageLayout,
            $this->runtimePreviewPageTitleMode
        );
        $articleClassAttr = 'post';
        if ($previewPost instanceof WP_Post) {
            $previewArticleClasses = get_post_class('', $previewPost->ID);
            if (is_array($previewArticleClasses) && $previewArticleClasses !== []) {
                $articleClassAttr = implode(
                    ' ',
                    array_filter(
                        array_map(
                            static function ($className): string {
                                return sanitize_html_class((string) $className);
                            },
                            $previewArticleClasses
                        )
                    )
                );
            }
        }

        if ($canShowTemplateSlots) {
            echo '<main id="content" class="site-main h4bs-preview-site-main">';
            if ($usePanelWrap) {
                echo '<div class="content-panel">';
            }
            echo '<article class="' . esc_attr($articleClassAttr !== '' ? $articleClassAttr : 'post') . '" data-h4bs-preview-article="1">';
        }
        if ($showTitle && $previewPost instanceof WP_Post) {
            $previewTitle = trim((string) get_the_title($previewPost));
            if ($previewTitle !== '') {
                echo '<h1 data-h4bs-preview-page-title="1">' . esc_html($previewTitle) . '</h1>';
            }
        }
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Scoped declarations are assembled from sanitized Builder settings.
        echo $this->buildLayoutDesignVariantStyles($layout);
        $globalStyle = $this->globalDesignStyle();
        echo '<div class="h4bs-builder-page h4bs-builder-page-layout-' . esc_attr($pageLayout) . '" data-h4bs-layout-id="' . esc_attr((string) $this->previewContextPostId) . '" data-h4bs-preview-page="1" data-h4bs-version="' . esc_attr(H4BS_VERSION) . '" data-h4bs-build="' . esc_attr(H4BS_BUILD_HASH) . '"';
        if ($globalStyle !== '') {
            echo ' style="' . esc_attr($globalStyle) . '"';
        }
        echo '>';
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- renderLayoutBlocks scopes context and renderBlock sanitizes controlled Builder markup.
        echo $this->renderLayoutBlocks($layout, $this->previewContextPostId);
        echo '</div>';
        if ($canShowTemplateSlots) {
            echo '</article>';
            if ($usePanelWrap) {
                echo '</div>';
            }
            echo '</main>';
        }
        if ($canShowTemplateSlots && $footerHtml !== '') {
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Template slot HTML is generated by Builder Suite renderers.
            echo '<div class="h4bs-preview-site-slot h4bs-preview-site-slot-footer" data-h4bs-preview-site-slot="footer">' . $footerHtml . '</div>';
        }

        $html = (string) ob_get_clean();
        if ($previewPost instanceof WP_Post) {
            wp_reset_postdata();
        }
        if ($previousPost instanceof WP_Post) {
            $GLOBALS['post'] = $previousPost;
        } elseif (isset($GLOBALS['post'])) {
            unset($GLOBALS['post']);
        }
        if ($previousWpQuery instanceof \WP_Query) {
            $GLOBALS['wp_query'] = $previousWpQuery;
        } elseif (isset($GLOBALS['wp_query'])) {
            unset($GLOBALS['wp_query']);
        }
        if ($previousWpTheQuery instanceof \WP_Query) {
            $GLOBALS['wp_the_query'] = $previousWpTheQuery;
        } elseif (isset($GLOBALS['wp_the_query'])) {
            unset($GLOBALS['wp_the_query']);
        }

        return $html;
    }

    public function renderBuilderPagesScreen(): void
    {
        if (! current_user_can('edit_pages')) {
            return;
        }

        $pages = get_posts(
            [
                'post_type'      => 'page',
                'post_status'    => ['publish', 'draft', 'private', 'future', 'pending'],
                'posts_per_page' => 100,
                'orderby'        => 'modified',
                'order'          => 'DESC',
            ]
        );
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Help4 Builder Pages', 'help4-builder-suite'); ?></h1>
            <p><?php esc_html_e('Use this screen to quickly open any page directly in Help4 Builder mode.', 'help4-builder-suite'); ?></p>
            <table class="widefat striped">
                <thead>
                    <tr>
                        <th><?php esc_html_e('Page', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Status', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Builder Mode', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Actions', 'help4-builder-suite'); ?></th>
                    </tr>
                </thead>
                <tbody>
                    <?php if (! $pages) : ?>
                        <tr>
                            <td colspan="4"><?php esc_html_e('No pages found.', 'help4-builder-suite'); ?></td>
                        </tr>
                    <?php endif; ?>
                    <?php foreach ($pages as $page) : ?>
                        <?php
                        $pageId = (int) $page->ID;
                        $enabled = $this->isBuilderEnabled($pageId);
                        ?>
                        <tr>
                            <td>
                                <strong><?php echo esc_html(get_the_title($pageId) ?: __('(no title)', 'help4-builder-suite')); ?></strong><br>
                                <small><?php echo esc_html(get_permalink($pageId) ?: ''); ?></small>
                            </td>
                            <td><?php echo esc_html((string) $page->post_status); ?></td>
                            <td>
                                <?php if ($enabled) : ?>
                                    <span style="color:#0a9b7a;font-weight:600;"><?php esc_html_e('Active', 'help4-builder-suite'); ?></span>
                                <?php else : ?>
                                    <span style="color:#8a5252;"><?php esc_html_e('WordPress Editor', 'help4-builder-suite'); ?></span>
                                <?php endif; ?>
                            </td>
                            <td>
                                <a class="button button-primary" href="<?php echo esc_url($this->builderActionUrl($pageId)); ?>">
                                    <?php esc_html_e('Edit with Help4 Builder', 'help4-builder-suite'); ?>
                                </a>
                                <a class="button" href="<?php echo esc_url(get_edit_post_link($pageId, '')); ?>">
                                    <?php esc_html_e('Open WordPress Editor', 'help4-builder-suite'); ?>
                                </a>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </div>
        <?php
    }

    public function renderBuilderNotice(): void
    {
        if (! current_user_can('edit_pages')) {
            return;
        }

        $notice = get_transient('h4bs_builder_open_notice');
        if (! is_string($notice) || $notice === '') {
            return;
        }

        delete_transient('h4bs_builder_open_notice');
        printf('<div class="notice notice-success is-dismissible"><p>%s</p></div>', esc_html($notice));
    }

    public function registerAdminBarEditLink(WP_Admin_Bar $adminBar): void
    {
        if (! is_admin_bar_showing() || ! is_singular()) {
            return;
        }

        $postId = get_queried_object_id();
        if (! $postId || ! current_user_can('edit_post', $postId)) {
            return;
        }

        $adminBar->add_node(
            [
                'id'    => 'h4bs-edit-builder',
                'title' => __('Help4 Edit', 'help4-builder-suite'),
                'href'  => $this->builderActionUrl($postId),
            ]
        );

        $adminBar->add_node(
            [
                'id'     => 'h4bs-edit-current',
                'parent' => 'h4bs-edit-builder',
                'title'  => __('Edit Current Page (Help4)', 'help4-builder-suite'),
                'href'   => $this->builderActionUrl($postId),
            ]
        );

        $defaultEdit = get_edit_post_link($postId, '');
        if (is_string($defaultEdit) && $defaultEdit !== '') {
            $adminBar->add_node(
                [
                    'id'     => 'h4bs-edit-current-default',
                    'parent' => 'h4bs-edit-builder',
                    'title'  => __('Edit Current Page (WordPress)', 'help4-builder-suite'),
                    'href'   => $defaultEdit,
                ]
            );
        }

        $resources = $this->detectRelatedResources($postId);
        if ($resources['template_library']) {
            $adminBar->add_node(
                [
                    'id'     => 'h4bs-edit-template-library',
                    'parent' => 'h4bs-edit-builder',
                    'title'  => __('Open Template Library', 'help4-builder-suite'),
                    'href'   => admin_url('edit.php?post_type=h4_template'),
                ]
            );
        }

        foreach ($resources['template_ids'] as $templateId) {
            if (! current_user_can('edit_post', $templateId)) {
                continue;
            }

            $link = get_edit_post_link($templateId, '');
            if (! is_string($link) || $link === '') {
                continue;
            }

            $adminBar->add_node(
                [
                    'id'     => 'h4bs-edit-template-' . $templateId,
                    'parent' => 'h4bs-edit-builder',
                    'title'  => sprintf(
                        /* translators: %d = template id */
                        __('Edit Template #%d', 'help4-builder-suite'),
                        $templateId
                    ),
                    'href'   => $link,
                ]
            );
        }

        foreach ($resources['slider_ids'] as $sliderId) {
            if (! current_user_can('edit_post', $sliderId)) {
                continue;
            }

            $link = get_edit_post_link($sliderId, '');
            if (! is_string($link) || $link === '') {
                continue;
            }

            $adminBar->add_node(
                [
                    'id'     => 'h4bs-edit-slider-' . $sliderId,
                    'parent' => 'h4bs-edit-builder',
                    'title'  => sprintf(
                        /* translators: %d = slider id */
                        __('Edit Slider #%d', 'help4-builder-suite'),
                        $sliderId
                    ),
                    'href'   => $link,
                ]
            );
        }
    }

    /**
     * @param array<string, mixed> $columns
     * @return array<string, mixed>
     */
    public function addBuilderStatusColumn(array $columns): array
    {
        $result = [];
        foreach ($columns as $key => $label) {
            $result[$key] = $label;
            if ($key === 'title') {
                $result['h4bs_builder'] = __('Help4 Builder', 'help4-builder-suite');
            }
        }
        if (! isset($result['h4bs_builder'])) {
            $result['h4bs_builder'] = __('Help4 Builder', 'help4-builder-suite');
        }
        return $result;
    }

    public function renderBuilderStatusColumn(string $column, int $postId): void
    {
        if ($column !== 'h4bs_builder') {
            return;
        }

        $enabled = $this->isBuilderEnabled($postId);
        if ($enabled) {
            echo '<span style="color:#0a9b7a;font-weight:600;">' . esc_html__('Active', 'help4-builder-suite') . '</span><br>';
        } else {
            echo '<span style="color:#8a5252;">' . esc_html__('WordPress Editor', 'help4-builder-suite') . '</span><br>';
        }

        if (current_user_can('edit_post', $postId)) {
            echo '<a href="' . esc_url($this->builderActionUrl($postId)) . '">' . esc_html__('Edit with Help4', 'help4-builder-suite') . '</a>';
        }
    }

    public function handleOpenBuilder(): void
    {
        $postId = AdminRequest::queryAbsInt('post');
        if ($postId <= 0) {
            wp_die('Missing post id.');
        }
        check_admin_referer('h4bs_open_builder_' . $postId);

        if (! current_user_can('edit_post', $postId)) {
            wp_die('Not allowed.');
        }

        update_post_meta($postId, '_h4_builder_mode', 'help4');
        update_post_meta($postId, '_h4_builder_enabled', '1');
        set_transient('h4bs_builder_open_notice', __('Help4 Builder mode was enabled for this page.', 'help4-builder-suite'), 30);

        wp_safe_redirect($this->builderStudioUrl($postId));
        exit;
    }

    public function focusBuilderPanelScript(): void
    {
    }

    /**
     * @param array<string, mixed> $actions
     * @return array<string, mixed>
     */
    public function addDuplicateLink(array $actions, WP_Post $post): array
    {
        if (! current_user_can('edit_posts') || ! current_user_can('edit_post', $post->ID)) {
            return $actions;
        }

        $actions['h4bs_edit_builder'] = '<a href="' . esc_url($this->builderActionUrl((int) $post->ID)) . '">' . esc_html__('Edit with Help4 Builder', 'help4-builder-suite') . '</a>';

        $url = wp_nonce_url(
            admin_url('admin.php?action=h4bs_duplicate_post&post=' . $post->ID),
            'h4bs_duplicate_post_' . $post->ID
        );

        $actions['h4bs_duplicate'] = '<a href="' . esc_url($url) . '">' . esc_html__('Duplicate (Help4)', 'help4-builder-suite') . '</a>';
        return $actions;
    }

    public function handleDuplicate(): void
    {
        $postId = AdminRequest::queryAbsInt('post');
        if ($postId <= 0) {
            wp_die('Missing post id.');
        }
        check_admin_referer('h4bs_duplicate_post_' . $postId);

        $post = get_post($postId);
        if (! $post instanceof WP_Post) {
            wp_die('Invalid source post.');
        }

        if (! current_user_can('edit_post', $postId)) {
            wp_die('Not allowed.');
        }

        $newPostId = wp_insert_post(
            [
                'post_type'    => $post->post_type,
                'post_status'  => 'draft',
                'post_title'   => $post->post_title . ' (Copy)',
                'post_content' => $post->post_content,
                'post_excerpt' => $post->post_excerpt,
                'post_parent'  => $post->post_parent,
                'menu_order'   => $post->menu_order,
            ],
            true
        );

        if (is_wp_error($newPostId)) {
            wp_die(esc_html($newPostId->get_error_message()));
        }

        $taxonomies = get_object_taxonomies($post->post_type);
        foreach ($taxonomies as $taxonomy) {
            $terms = wp_get_object_terms($postId, $taxonomy, ['fields' => 'ids']);
            if (! is_wp_error($terms)) {
                wp_set_object_terms((int) $newPostId, $terms, $taxonomy, false);
            }
        }

        $meta = get_post_meta($postId);
        $skip = ['_edit_lock', '_edit_last'];
        foreach ($meta as $metaKey => $values) {
            if (in_array($metaKey, $skip, true)) {
                continue;
            }
            foreach ((array) $values as $value) {
                add_post_meta((int) $newPostId, $metaKey, maybe_unserialize($value));
            }
        }

        wp_safe_redirect(admin_url('post.php?action=edit&post=' . (int) $newPostId));
        exit;
    }

    private function builderActionUrl(int $postId): string
    {
        return wp_nonce_url(
            admin_url('admin.php?action=h4bs_open_builder&post=' . $postId),
            'h4bs_open_builder_' . $postId
        );
    }

    private function builderStudioUrl(int $postId): string
    {
        return add_query_arg(
            [
                'page' => 'h4bs-builder-studio',
                'post' => $postId,
            ],
            admin_url('admin.php')
        );
    }

    /**
     * @return array<string, mixed>
     */
    private function loadTemplateConditions(int $postId): array
    {
        if (get_post_type($postId) !== 'h4_template') {
            return [];
        }

        $schemas = [
            'role' => sanitize_key((string) get_post_meta($postId, '_h4_template_role', true)) ?: 'general',
            'scope' => sanitize_key((string) get_post_meta($postId, '_h4_template_scope', true)) ?: 'entire_site',
            'include_ids' => implode(',', $this->parseIdList((string) get_post_meta($postId, '_h4_template_include_ids', true))),
            'exclude_ids' => implode(',', $this->parseIdList((string) get_post_meta($postId, '_h4_template_exclude_ids', true))),
            'include_types' => implode(',', $this->parseSlugList((string) get_post_meta($postId, '_h4_template_include_types', true))),
            'exclude_types' => implode(',', $this->parseSlugList((string) get_post_meta($postId, '_h4_template_exclude_types', true))),
            'user_roles' => implode(',', $this->parseSlugList((string) get_post_meta($postId, '_h4_template_user_roles', true))),
            'priority' => (int) get_post_meta($postId, '_h4_template_priority', true) ?: 10,
        ];

        return $schemas;
    }

    /**
     * @return array<string, string>
     */
    private function templateRoleCatalog(): array
    {
        return [
            'general' => __('General Content', 'help4-builder-suite'),
            'header' => __('Header', 'help4-builder-suite'),
            'footer' => __('Footer', 'help4-builder-suite'),
            'popup' => __('Popup', 'help4-builder-suite'),
            'front_page' => __('Front Page', 'help4-builder-suite'),
            'home' => __('Blog Home', 'help4-builder-suite'),
            'page' => __('Page Template', 'help4-builder-suite'),
            'post' => __('Post Template', 'help4-builder-suite'),
            'canvas_page' => __('Canvas Page (No Header/Footer)', 'help4-builder-suite'),
            'singular' => __('Singular (Post/Page)', 'help4-builder-suite'),
            'archive' => __('Archive', 'help4-builder-suite'),
            'search' => __('Search Results', 'help4-builder-suite'),
            '404' => __('404 Page', 'help4-builder-suite'),
            'product_archive' => __('Product Archive', 'help4-builder-suite'),
            'product_single' => __('Single Product', 'help4-builder-suite'),
        ];
    }

    /**
     * @return array<string, string>
     */
    private function templateScopeCatalog(): array
    {
        return [
            'entire_site' => __('Entire Site', 'help4-builder-suite'),
            'front_page' => __('Front Page Only', 'help4-builder-suite'),
            'home' => __('Blog Home Only', 'help4-builder-suite'),
            'page' => __('Pages Only', 'help4-builder-suite'),
            'post' => __('Posts Only', 'help4-builder-suite'),
            'canvas_page' => __('Canvas Pages Only', 'help4-builder-suite'),
            'singular' => __('Any Singular View', 'help4-builder-suite'),
            'archive' => __('Any Archive View', 'help4-builder-suite'),
            'search' => __('Search Results Only', 'help4-builder-suite'),
            '404' => __('404 Only', 'help4-builder-suite'),
            'product_archive' => __('Product Archive Only', 'help4-builder-suite'),
            'product_single' => __('Single Product Only', 'help4-builder-suite'),
            'specific_ids' => __('Specific Post/Page IDs', 'help4-builder-suite'),
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function sanitizeTemplateConditions(string $raw): array
    {
        $decoded = json_decode($raw, true);
        $source = is_array($decoded) ? $decoded : [];

        $role = sanitize_key((string) ($source['role'] ?? 'general'));
        if (! array_key_exists($role, $this->templateRoleCatalog())) {
            $role = 'general';
        }

        $scope = sanitize_key((string) ($source['scope'] ?? 'entire_site'));
        if (! array_key_exists($scope, $this->templateScopeCatalog())) {
            $scope = 'entire_site';
        }

        $includeIds = implode(',', $this->parseIdList((string) ($source['include_ids'] ?? '')));
        $excludeIds = implode(',', $this->parseIdList((string) ($source['exclude_ids'] ?? '')));
        $includeTypes = implode(',', $this->parseSlugList((string) ($source['include_types'] ?? '')));
        $excludeTypes = implode(',', $this->parseSlugList((string) ($source['exclude_types'] ?? '')));
        $userRoles = implode(',', $this->parseSlugList((string) ($source['user_roles'] ?? '')));
        $priority = max(-100, min(999, (int) ($source['priority'] ?? 10)));

        return [
            'role' => $role,
            'scope' => $scope,
            'include_ids' => $includeIds,
            'exclude_ids' => $excludeIds,
            'include_types' => $includeTypes,
            'exclude_types' => $excludeTypes,
            'user_roles' => $userRoles,
            'priority' => $priority,
        ];
    }

    /**
     * @param array<string, mixed> $conditions
     */
    private function persistTemplateConditions(int $postId, array $conditions): void
    {
        update_post_meta($postId, '_h4_template_role', (string) ($conditions['role'] ?? 'general'));
        update_post_meta($postId, '_h4_template_scope', (string) ($conditions['scope'] ?? 'entire_site'));
        update_post_meta($postId, '_h4_template_include_ids', (string) ($conditions['include_ids'] ?? ''));
        update_post_meta($postId, '_h4_template_exclude_ids', (string) ($conditions['exclude_ids'] ?? ''));
        update_post_meta($postId, '_h4_template_include_types', (string) ($conditions['include_types'] ?? ''));
        update_post_meta($postId, '_h4_template_exclude_types', (string) ($conditions['exclude_types'] ?? ''));
        update_post_meta($postId, '_h4_template_user_roles', (string) ($conditions['user_roles'] ?? ''));
        update_post_meta($postId, '_h4_template_priority', (string) (int) ($conditions['priority'] ?? 10));
    }

    /**
     * @return array{
     *   slider_ids: array<int, int>,
     *   template_ids: array<int, int>,
     *   template_library: bool
     * }
     */
    private function detectRelatedResources(int $postId): array
    {
        $content = (string) get_post_field('post_content', $postId);
        $layout = $this->layoutForPost($postId);

        $sliderIds = $this->extractIdsFromShortcode('h4_slider', 'id', $content);
        $templateIds = $this->extractIdsFromShortcode('h4_template', 'id', $content);
        $usesTemplateLibrary = has_shortcode($content, 'h4_template_library');

        if (is_array($layout)) {
            foreach ($layout as $block) {
                if (! is_array($block)) {
                    continue;
                }
                $type = (string) ($block['type'] ?? '');
                $settings = is_array($block['settings'] ?? null) ? $block['settings'] : [];

                if ($type === 'slider') {
                    $sliderId = absint($settings['slider_id'] ?? 0);
                    if ($sliderId > 0) {
                        $sliderIds[] = $sliderId;
                    }
                }

                if ($type !== 'shortcode') {
                    continue;
                }

                $shortcode = (string) ($settings['shortcode'] ?? '');
                if ($shortcode === '') {
                    continue;
                }

                $sliderIds = array_merge($sliderIds, $this->extractIdsFromShortcode('h4_slider', 'id', $shortcode));
                $templateIds = array_merge($templateIds, $this->extractIdsFromShortcode('h4_template', 'id', $shortcode));
                $usesTemplateLibrary = $usesTemplateLibrary || has_shortcode($shortcode, 'h4_template_library');
            }
        }

        $sliderIds = array_values(array_unique(array_filter(array_map('absint', $sliderIds))));
        $templateIds = array_values(array_unique(array_filter(array_map('absint', $templateIds))));

        return [
            'slider_ids'       => array_slice($sliderIds, 0, 8),
            'template_ids'     => array_slice($templateIds, 0, 8),
            'template_library' => $usesTemplateLibrary,
        ];
    }

    /**
     * @return array<int, int>
     */
    private function extractIdsFromShortcode(string $shortcodeTag, string $attribute, string $content): array
    {
        if ($content === '' || strpos($content, '[' . $shortcodeTag) === false) {
            return [];
        }

        $ids = [];
        $quotedPattern = '/\[' . preg_quote($shortcodeTag, '/') . '[^\]]*' . preg_quote($attribute, '/') . '\s*=\s*"(\d+)"/i';
        $singleQuotedPattern = '/\[' . preg_quote($shortcodeTag, '/') . '[^\]]*' . preg_quote($attribute, '/') . '\s*=\s*\'(\d+)\'/i';
        $barePattern = '/\[' . preg_quote($shortcodeTag, '/') . '[^\]]*' . preg_quote($attribute, '/') . '\s*=\s*(\d+)/i';

        foreach ([$quotedPattern, $singleQuotedPattern, $barePattern] as $pattern) {
            if (! preg_match_all($pattern, $content, $matches)) {
                continue;
            }

            foreach ($matches[1] as $value) {
                $id = absint($value);
                if ($id > 0) {
                    $ids[] = $id;
                }
            }
        }

        return $ids;
    }

    /**
     * @param mixed $rawLayout
     * @return array<int, array<string, mixed>>
     */
    private function sanitizeLayout($rawLayout): array
    {
        if (is_string($rawLayout)) {
            $rawLayout = json_decode($rawLayout, true);
        }
        if (! is_array($rawLayout)) {
            return [];
        }

        $clean = [];
        foreach ($rawLayout as $index => $block) {
            if (! is_array($block)) {
                continue;
            }

            $type = $this->normalizeBlockType((string) ($block['type'] ?? 'text'));

            $settings = isset($block['settings']) && is_array($block['settings']) ? $block['settings'] : [];
            $cleanSettings = [];
            foreach ($settings as $key => $value) {
                $fieldKey = sanitize_key((string) $key);
                if (is_array($value)) {
                    $cleanSettings[$fieldKey] = $this->sanitizeExtensionValue($value);
                    continue;
                }
                if ($type === 'html' && $fieldKey === 'html') {
                    $cleanSettings[$fieldKey] = $this->sanitizeHtmlBlockContent((string) $value);
                } elseif ($type === 'shortcode' && $fieldKey === 'shortcode') {
                    $cleanSettings[$fieldKey] = sanitize_textarea_field((string) $value);
                } elseif ($type === 'map' && $fieldKey === 'embed_url') {
                    $cleanSettings[$fieldKey] = esc_url_raw((string) $value);
                } elseif (in_array($fieldKey, ['advanced_data_attributes', 'items', 'text', 'caption', 'empty_text'], true)) {
                    $textareaValue = (string) $value;
                    if (in_array($type, ['list', 'faq'], true) && $fieldKey === 'items') {
                        $textareaValue = $this->normalizeMultilineDelimiters($textareaValue);
                    }
                    $cleanSettings[$fieldKey] = sanitize_textarea_field($textareaValue);
                } else {
                    $cleanSettings[$fieldKey] = sanitize_text_field((string) $value);
                }
            }
            $extensions = is_array($block['extensions'] ?? null)
                ? $this->sanitizeExtensionValue($block['extensions'])
                : [];
            foreach ($block as $blockKey => $blockValue) {
                $blockKeyRaw = (string) $blockKey;
                if (
                    in_array($blockKeyRaw, ['schema_version', 'id', 'type', 'settings', 'visibility', 'parent_id', 'slot', 'extensions'], true)
                    || preg_match('/[:\/]/', $blockKeyRaw) !== 1
                ) {
                    continue;
                }
                $extensions['opaque'][$blockKeyRaw] = $this->sanitizeExtensionValue($blockValue);
            }

            $clean[] = [
                'schema_version' => sanitize_text_field((string) ($block['schema_version'] ?? '1.0')) ?: '1.0',
                'id'         => sanitize_key((string) ($block['id'] ?? ('block_' . $index))),
                'type'       => $type,
                'settings'   => $cleanSettings,
                'visibility' => $this->sanitizeVisibilityRules($block['visibility'] ?? []),
                'parent_id'  => sanitize_key((string) ($block['parent_id'] ?? '')),
                'slot'       => max(0, min(self::MAX_SLOT, (int) ($block['slot'] ?? 0))),
                'extensions' => $extensions,
            ];
        }

        return $clean;
    }

    /**
     * @param array<int, array<string, mixed>> $layout
     * @return array<int, string>
     */
    private function layoutStructureErrors(array $layout): array
    {
        $errors = [];
        $ids = [];
        foreach ($layout as $index => $block) {
            $id = sanitize_key((string) ($block['id'] ?? ''));
            if ($id === '') {
                $errors[] = 'missing_id:' . $index;
                continue;
            }
            if (isset($ids[$id])) {
                $errors[] = 'duplicate_id:' . $id;
            }
            $ids[$id] = true;
        }
        foreach ($layout as $block) {
            $id = sanitize_key((string) ($block['id'] ?? ''));
            $parentId = sanitize_key((string) ($block['parent_id'] ?? ''));
            if ($parentId !== '' && ! isset($ids[$parentId])) {
                $errors[] = 'dangling_parent:' . $id . ':' . $parentId;
            }
            if ($parentId !== '' && $parentId === $id) {
                $errors[] = 'self_parent:' . $id;
            }
        }
        return array_values(array_unique($errors));
    }

    /**
     * Keep only a small, page-local recovery window instead of creating server archives.
     *
     * @param array<int, array<string, mixed>> $layout
     */
    private function storeLayoutRecoveryPoint(int $postId, array $layout, string $layoutHash): void
    {
        if (! $layout) {
            return;
        }
        $history = get_post_meta($postId, '_h4_builder_layout_recovery', true);
        $history = is_array($history) ? $history : [];
        $latest = $history[0] ?? [];
        if (is_array($latest) && (string) ($latest['hash'] ?? '') === $layoutHash) {
            return;
        }
        array_unshift(
            $history,
            [
                'created_at' => current_time('mysql', true),
                'user_id' => get_current_user_id(),
                'hash' => $layoutHash,
                'layout' => $layout,
            ]
        );
        update_post_meta($postId, '_h4_builder_layout_recovery', array_slice($history, 0, 5));
    }

    private function normalizeBlockType(string $raw): string
    {
        $rawType = strtolower(trim($raw));
        if (preg_match('/^[a-z0-9][a-z0-9_-]{1,63}(?::|\/)[a-z0-9][a-z0-9_.-]{0,95}$/', $rawType) === 1) {
            return str_replace('/', ':', $rawType);
        }
        $type = $rawType;
        if (str_starts_with($type, 'drop')) {
            $type = preg_replace('/^drop[\s:_-]*/i', '', $type) ?? $type;
        }
        $type = str_replace([' ', '-', '.'], '_', $type);
        $type = sanitize_key($type);

        $aliases = [
            'paragraph' => 'text',
            'text_editor' => 'text',
            'row' => 'section',
            'innersection' => 'inner_section',
            'inner_row' => 'inner_section',
            'inner_column' => 'inner_section',
            'column' => 'inner_section',
            'columns' => 'inner_section',
            'code' => 'html',
            'cta_box' => 'cta',
        ];

        if (isset($aliases[$type])) {
            $type = $aliases[$type];
        }

        $allowedBlockTypes = apply_filters('h4bs_builder_allowed_block_types', $this->allowedBlockTypes);
        if (! is_array($allowedBlockTypes) || ! in_array($type, $allowedBlockTypes, true)) {
            return 'text';
        }

        return $type;
    }

    /**
     * @param mixed $value
     * @return mixed
     */
    private function sanitizeExtensionValue($value, int $depth = 0)
    {
        if ($depth > 12) {
            return null;
        }
        if (is_array($value)) {
            $clean = [];
            foreach ($value as $key => $item) {
                $cleanKey = is_int($key)
                    ? $key
                    : preg_replace('/[^a-zA-Z0-9_.:\/-]/', '', (string) $key);
                if ($cleanKey === '') {
                    continue;
                }
                $clean[$cleanKey] = $this->sanitizeExtensionValue($item, $depth + 1);
            }
            return $clean;
        }
        if (is_bool($value) || is_int($value) || is_float($value) || $value === null) {
            return $value;
        }
        return sanitize_textarea_field((string) $value);
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function layoutForPost(int $postId): array
    {
        if (
            is_array($this->runtimePreviewLayout)
            && $this->previewContextActive
            && $this->previewContextPostId === $postId
        ) {
            return $this->runtimePreviewLayout;
        }
        $raw = (string) get_post_meta($postId, '_h4_builder_layout', true);
        if ($raw === '') {
            return [];
        }
        $decoded = json_decode($raw, true);
        return is_array($decoded) ? $decoded : [];
    }

    private function pageLayoutForPost(int $postId): string
    {
        if (
            is_string($this->runtimePreviewPageLayout)
            && $this->previewContextActive
            && $this->previewContextPostId === $postId
        ) {
            return $this->normalizePageLayout($this->runtimePreviewPageLayout);
        }
        $raw = (string) get_post_meta($postId, '_h4_builder_page_layout', true);
        return $this->normalizePageLayout($raw);
    }

    private function pageTitleModeForPost(int $postId): string
    {
        if (
            is_string($this->runtimePreviewPageTitleMode)
            && $this->previewContextActive
            && $this->previewContextPostId === $postId
        ) {
            return $this->normalizePageTitleMode($this->runtimePreviewPageTitleMode);
        }
        return $this->normalizePageTitleMode((string) get_post_meta($postId, '_h4_builder_page_title', true));
    }

    /**
     * @return array<string, string>
     */
    private function pageTitleModeOptions(): array
    {
        return [
            'auto' => __('Automatic - avoid duplicate headings', 'help4-builder-suite'),
            'show' => __('Show theme page title', 'help4-builder-suite'),
            'hide' => __('Hide theme page title', 'help4-builder-suite'),
        ];
    }

    private function normalizePageTitleMode(string $raw): string
    {
        $mode = sanitize_key($raw);
        return in_array($mode, ['auto', 'show', 'hide'], true) ? $mode : 'auto';
    }

    /**
     * @param array<int, array<string, mixed>>|null $layout
     */
    private function shouldShowPageTitle(int $postId, ?array $layout = null, ?string $pageLayout = null, ?string $mode = null): bool
    {
        $pageLayout = $this->normalizePageLayout($pageLayout ?? $this->pageLayoutForPost($postId));
        if ($pageLayout === 'canvas') {
            return false;
        }
        $mode = $this->normalizePageTitleMode($mode ?? $this->pageTitleModeForPost($postId));
        if ($mode === 'show') {
            return true;
        }
        if ($mode === 'hide') {
            return false;
        }
        $layout = $this->sanitizeLayout($layout ?? $this->layoutForPost($postId));
        $encoded = wp_json_encode($layout, JSON_UNESCAPED_SLASHES);
        $hasDesignedH1 = is_string($encoded)
            && (
                preg_match('/<h1\b/i', $encoded) === 1
                || preg_match('/"tag"\s*:\s*"h1"/i', $encoded) === 1
            );
        $hasDesignedH1 = $hasDesignedH1 || $this->layoutReferencesPublishedSliderH1($layout, $postId);
        return ! ($pageLayout === 'full_width' && $hasDesignedH1);
    }

    public function filterLayoutHasSemanticH1(bool $hasHeading, int $postId): bool
    {
        if ($hasHeading || $postId <= 0) {
            return $hasHeading;
        }

        return $this->layoutReferencesPublishedSliderH1($this->layoutForPost($postId), $postId);
    }

    public function filterDraftHasAssignedTemplateRole(bool $hasTemplate, string $role): bool
    {
        if ($hasTemplate || ! $this->previewContextActive || ! in_array($role, ['header', 'footer'], true)) {
            return $hasTemplate;
        }
        $slots = $this->previewTemplateSlots(
            $this->previewContextPostId,
            $this->previewContextPostType,
            $this->runtimePreviewPageLayout ?? $this->pageLayoutForPost($this->previewContextPostId)
        );
        return absint($slots[$role . '_template_id'] ?? 0) > 0;
    }

    public function renderDraftAssignedHeader(): void
    {
        $this->renderDraftAssignedTemplateSlot('header');
    }

    public function renderDraftAssignedFooter(): void
    {
        $this->renderDraftAssignedTemplateSlot('footer');
    }

    private function renderDraftAssignedTemplateSlot(string $role): void
    {
        if (! $this->previewContextActive || ! in_array($role, ['header', 'footer'], true)) {
            return;
        }
        $slots = $this->previewTemplateSlots(
            $this->previewContextPostId,
            $this->previewContextPostType,
            $this->runtimePreviewPageLayout ?? $this->pageLayoutForPost($this->previewContextPostId)
        );
        $html = (string) ($slots[$role . '_html'] ?? '');
        if (absint($slots[$role . '_template_id'] ?? 0) <= 0 || $html === '') {
            return;
        }
        echo '<div class="h4bs-template-slot h4bs-template-slot-' . esc_attr($role) . '" data-h4bs-template-slot="' . esc_attr($role) . '">';
        echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Rendered by the assigned Builder template contract.
        echo '</div>';
    }

    /**
     * @param array<int, array<string, mixed>> $layout
     */
    private function layoutReferencesPublishedSliderH1(array $layout, int $postId = 0): bool
    {
        $sliderIds = [];
        foreach ($layout as $block) {
            if (! is_array($block)) {
                continue;
            }
            $type = sanitize_key((string) ($block['type'] ?? ''));
            $settings = is_array($block['settings'] ?? null) ? $block['settings'] : [];
            if ($type === 'slider') {
                $sliderIds[] = absint($settings['slider_id'] ?? 0);
            } elseif ($type === 'shortcode') {
                $sliderIds = array_merge(
                    $sliderIds,
                    $this->extractIdsFromShortcode('h4_slider', 'id', (string) ($settings['shortcode'] ?? ''))
                );
            }
        }

        if ($sliderIds === [] && $postId > 0) {
            $sliderIds = $this->detectRelatedResources($postId)['slider_ids'];
        }
        foreach (array_values(array_unique(array_filter(array_map('absint', $sliderIds)))) as $sliderId) {
            if (get_post_status($sliderId) !== 'publish') {
                continue;
            }
            $slides = json_decode((string) get_post_meta($sliderId, '_h4_slider_slides', true), true);
            if (! is_array($slides)) {
                continue;
            }
            foreach ($slides as $slide) {
                if (! is_array($slide) || ! is_array($slide['layers'] ?? null)) {
                    continue;
                }
                foreach ($slide['layers'] as $layer) {
                    if (
                        is_array($layer)
                        && sanitize_key((string) ($layer['type'] ?? '')) === 'heading'
                        && sanitize_key((string) ($layer['tag'] ?? '')) === 'h1'
                    ) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    /**
     * @return array<string, string>
     */
    private function pageLayoutOptions(): array
    {
        return [
            'default' => __('Theme Default', 'help4-builder-suite'),
            'full_width' => __('Help4 Full Width', 'help4-builder-suite'),
            'canvas' => __('Help4 Canvas (No Header/Footer)', 'help4-builder-suite'),
        ];
    }

    private function normalizePageLayout(string $raw): string
    {
        $layout = sanitize_key($raw);
        $allowed = ['default', 'full_width', 'canvas'];
        if (! in_array($layout, $allowed, true)) {
            return 'default';
        }

        return $layout;
    }

    /**
     * @return array{
     *   header_template_id: int,
     *   footer_template_id: int,
     *   header_html: string,
     *   footer_html: string
     * }
     */
    private function previewTemplateSlots(int $postId, string $postType, string $pageLayout): array
    {
        $postType = sanitize_key($postType);
        $pageLayout = $this->normalizePageLayout($pageLayout);
        $slots = apply_filters(
            'h4bs_builder_preview_template_slots',
            [
                'header_template_id' => 0,
                'footer_template_id' => 0,
                'header_html' => '',
                'footer_html' => '',
            ],
            $postId,
            $postType,
            $pageLayout
        );

        $resolved = [
            'header_template_id' => absint(is_array($slots) ? ($slots['header_template_id'] ?? 0) : 0),
            'footer_template_id' => absint(is_array($slots) ? ($slots['footer_template_id'] ?? 0) : 0),
            'header_html' => is_array($slots) ? (string) ($slots['header_html'] ?? '') : '',
            'footer_html' => is_array($slots) ? (string) ($slots['footer_html'] ?? '') : '',
        ];

        if ($postType === 'h4_template' || $pageLayout === 'canvas') {
            return [
                'header_template_id' => 0,
                'footer_template_id' => 0,
                'header_html' => '',
                'footer_html' => '',
            ];
        }

        // Mirror frontend default theme chrome when no assigned template is active.
        if ($resolved['header_html'] === '') {
            $resolved['header_html'] = $this->defaultPreviewHeaderHtml();
        }
        if ($resolved['footer_html'] === '') {
            $resolved['footer_html'] = $this->defaultPreviewFooterHtml();
        }

        return $resolved;
    }

    /**
     * @param array{
     *   header_template_id:int,
     *   footer_template_id:int,
     *   header_html:string,
     *   footer_html:string
     * } $previewTemplateSlots
     * @return array<string, array<string, string|int>>
     */
    private function assignedTemplateEditors(array $previewTemplateSlots): array
    {
        $headerId = absint($previewTemplateSlots['header_template_id'] ?? 0);
        $footerId = absint($previewTemplateSlots['footer_template_id'] ?? 0);

        return [
            'header' => [
                'template_id'  => $headerId,
                'builder_url'  => $headerId > 0 ? $this->builderActionUrl($headerId) : '',
                'wp_editor_url'=> $headerId > 0 ? (get_edit_post_link($headerId, '') ?: admin_url('post.php?post=' . $headerId . '&action=edit')) : '',
                'label'        => __('Header', 'help4-builder-suite'),
            ],
            'footer' => [
                'template_id'  => $footerId,
                'builder_url'  => $footerId > 0 ? $this->builderActionUrl($footerId) : '',
                'wp_editor_url'=> $footerId > 0 ? (get_edit_post_link($footerId, '') ?: admin_url('post.php?post=' . $footerId . '&action=edit')) : '',
                'label'        => __('Footer', 'help4-builder-suite'),
            ],
        ];
    }

    private function defaultPreviewHeaderHtml(): string
    {
        $logo = get_custom_logo();
        $siteName = get_bloginfo('name');
        $tagline = get_bloginfo('description');
        $menu = wp_nav_menu(
            [
                'theme_location' => 'primary',
                'container'      => false,
                'fallback_cb'    => false,
                'echo'           => false,
            ]
        );

        ob_start();
        ?>
        <header class="site-header">
            <div class="site-shell brand-lockup">
                <div>
                    <?php if (is_string($logo) && $logo !== '') : ?>
                        <?php echo $logo; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
                    <?php else : ?>
                        <h1 class="site-title"><a href="<?php echo esc_url(home_url('/')); ?>"><?php echo esc_html($siteName); ?></a></h1>
                    <?php endif; ?>
                    <p><?php echo esc_html((string) $tagline); ?></p>
                </div>
                <nav class="site-nav" aria-label="<?php esc_attr_e('Primary', 'help4-builder-suite'); ?>">
                    <?php if (is_string($menu) && $menu !== '') : ?>
                        <?php echo $menu; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
                    <?php endif; ?>
                </nav>
            </div>
        </header>
        <?php
        return (string) ob_get_clean();
    }

    private function defaultPreviewFooterHtml(): string
    {
        $credit = $this->defaultFooterCreditText();
        $menu = wp_nav_menu(
            [
                'theme_location' => 'footer',
                'container'      => false,
                'fallback_cb'    => false,
                'echo'           => false,
            ]
        );

        ob_start();
        ?>
        <footer class="site-footer">
            <div class="site-shell">
                <?php if ($credit !== '') : ?>
                    <p><?php echo esc_html($credit); ?></p>
                <?php endif; ?>
                <?php if (is_string($menu) && $menu !== '') : ?>
                    <?php echo $menu; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
                <?php endif; ?>
            </div>
        </footer>
        <?php
        return (string) ob_get_clean();
    }

    private function defaultFooterCreditText(): string
    {
        $settings = Settings::all();
        if (! rest_sanitize_boolean($settings['footer_credit_enabled'] ?? '1')) {
            return '';
        }

        $template = trim(sanitize_text_field((string) ($settings['footer_credit_text'] ?? '')));
        if ($template === '') {
            return '';
        }

        $siteName = trim(wp_strip_all_tags((string) get_bloginfo('name')));
        if ($siteName === '') {
            $siteName = __('Website', 'help4-builder-suite');
        }

        $brandName = trim(sanitize_text_field((string) ($settings['brand_name'] ?? '')));
        if ($brandName === '') {
            $brandName = $siteName;
        }

        $year = $this->footerCreditYearToken($settings);
        $credit = strtr(
            $template,
            [
                '{site}'  => $siteName,
                '{brand}' => $brandName,
                '{year}'  => $year,
            ]
        );

        return trim((string) preg_replace('/\s+/', ' ', $credit));
    }

    private function replaceBuilderTextTokens(string $text): string
    {
        if (! str_contains($text, '{')) {
            return $text;
        }

        $settings = Settings::all();
        $siteName = trim(wp_strip_all_tags((string) get_bloginfo('name')));
        if ($siteName === '') {
            $siteName = __('Website', 'help4-builder-suite');
        }

        $brandName = trim(sanitize_text_field((string) ($settings['brand_name'] ?? '')));
        if ($brandName === '') {
            $brandName = $siteName;
        }

        return strtr(
            $text,
            [
                '{site}'  => $siteName,
                '{brand}' => $brandName,
                '{year}'  => $this->footerCreditYearToken($settings),
            ]
        );
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function footerCreditYearToken(array $settings): string
    {
        $mode = sanitize_key((string) ($settings['footer_credit_year_mode'] ?? 'current'));
        if ($mode === 'none') {
            return '';
        }

        $currentYear = (int) gmdate('Y');
        $startYear = max(1900, min($currentYear + 25, (int) ($settings['footer_credit_start_year'] ?? $currentYear)));
        if ($mode === 'range' && $startYear < $currentYear) {
            return (string) $startYear . '-' . (string) $currentYear;
        }

        return (string) $currentYear;
    }

    /**
     * @return array{
     *   colors: array<string, string>,
     *   fonts: array<string, string>,
     *   typography: array<string, string>,
     *   spacing: array<string, string>,
     *   shape: array<string, string>,
     *   components: array<string, string>,
     *   layout: array<string, string>
     * }
     */
    private function globalDesignTokens(): array
    {
        if (is_array($this->runtimeGlobalDesignTokens)) {
            $tokens = $this->sanitizeGlobalDesignTokens($this->runtimeGlobalDesignTokens);
        } else {
            $raw = get_option('h4bs_global_design_tokens', []);
            $tokens = $this->sanitizeGlobalDesignTokens($raw);
        }
        $filtered = apply_filters('h4bs_builder_global_design_tokens', $tokens);
        $normalized = $this->sanitizeGlobalDesignTokens(is_array($filtered) ? $filtered : $tokens);
        $normalized = apply_filters('h4bs_builder_design_tokens', $normalized, '1.0');
        return $this->sanitizeGlobalDesignTokens(is_array($normalized) ? $normalized : $tokens);
    }

    private function globalDesignRevision(): int
    {
        return max(1, (int) get_option('h4bs_global_design_revision', 1));
    }

    private function globalDesignHash(): string
    {
        return $this->builderValueHash($this->globalDesignTokens());
    }

    /**
     * @param mixed $value
     */
    private function builderValueHash($value): string
    {
        $encoded = wp_json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        return hash('sha256', is_string($encoded) ? $encoded : '');
    }

    private function pageCustomCss(int $postId): string
    {
        if ($this->previewContextActive && $this->previewContextPostId === $postId && $this->runtimePreviewPageCss !== null) {
            return $this->runtimePreviewPageCss;
        }
        return $this->sanitizePageCustomCss((string) get_post_meta($postId, '_h4_builder_custom_css', true));
    }

    private function persistPageCustomCss(int $postId, string $rawCss): void
    {
        $css = $this->sanitizePageCustomCss($rawCss);
        update_post_meta($postId, '_h4_builder_custom_css', $css);
        update_post_meta($postId, '_h4_builder_custom_css_hash', hash('sha256', $css));
        update_post_meta($postId, '_h4_builder_custom_css_compiled', $this->compilePageCustomCss($postId, $css));
    }

    private function sanitizePageCustomCss(string $rawCss): string
    {
        $css = trim(str_replace(["\0", "\r\n", "\r"], ['', "\n", "\n"], $rawCss));
        if ($css === '') {
            return '';
        }
        $css = substr($css, 0, 100000);
        $css = preg_replace('#</?style\b[^>]*>#i', '', $css) ?? '';
        $css = preg_replace('/<[^>]*>/', '', $css) ?? '';
        $css = preg_replace('/@import\s+[^;]+;?/i', '', $css) ?? '';
        $css = preg_replace('/(?:expression\s*\(|javascript\s*:|vbscript\s*:|(?<![-a-z])behavior\s*:|-moz-binding\s*:)/i', '', $css) ?? '';
        $css = preg_replace_callback(
            '/url\(\s*(["\']?)(.*?)\1\s*\)/i',
            static function (array $matches): string {
                $url = trim((string) ($matches[2] ?? ''));
                if ($url === '' || str_starts_with($url, '#') || str_starts_with($url, '/')) {
                    return 'url("' . esc_url_raw($url) . '")';
                }
                if (preg_match('#^https?://#i', $url) === 1) {
                    return 'url("' . esc_url_raw($url, ['http', 'https']) . '")';
                }
                if (preg_match('#^data:image/(?:png|gif|jpeg|webp);base64,[a-z0-9+/=\s]+$#i', $url) === 1) {
                    return 'url("' . preg_replace('/\s+/', '', $url) . '")';
                }
                return 'url("")';
            },
            $css
        ) ?? '';
        if (substr_count($css, '{') !== substr_count($css, '}')) {
            return '';
        }
        return trim($css);
    }

    private function compilePageCustomCss(int $postId, string $css): string
    {
        if ($css === '') {
            return '';
        }
        $scope = '.h4bs-builder-page[data-h4bs-layout-id="' . absint($postId) . '"]';
        return str_replace([':builder', '{{WRAPPER}}'], $scope, $css);
    }

    /** @param array<int, int> $postIds */
    private function compiledCustomCssForPosts(array $postIds): string
    {
        $compiled = [];
        foreach (array_values(array_unique(array_map('absint', $postIds))) as $postId) {
            if ($postId <= 0) {
                continue;
            }
            $css = $this->pageCustomCss($postId);
            if ($css === '') {
                continue;
            }
            $hash = hash('sha256', $css);
            $cachedHash = (string) get_post_meta($postId, '_h4_builder_custom_css_hash', true);
            $cachedCss = (string) get_post_meta($postId, '_h4_builder_custom_css_compiled', true);
            if (! $this->previewContextActive && hash_equals($hash, $cachedHash) && $cachedCss !== '') {
                $compiled[] = $cachedCss;
                continue;
            }
            $next = $this->compilePageCustomCss($postId, $css);
            if (! $this->previewContextActive) {
                update_post_meta($postId, '_h4_builder_custom_css_hash', $hash);
                update_post_meta($postId, '_h4_builder_custom_css_compiled', $next);
            }
            $compiled[] = $next;
        }
        return implode("\n", array_values(array_unique(array_filter($compiled))));
    }

    /**
     * @param array<string, mixed> $changeSet
     * @return array<string, mixed>
     */
    private function purgeBuilderCaches(int $postId, array $changeSet): array
    {
        $hasChanges = ! empty($changeSet['layout_changed'])
            || ! empty($changeSet['page_layout_changed'])
            || ! empty($changeSet['page_css_changed'])
            || ! empty($changeSet['template_conditions_changed'])
            || ! empty($changeSet['builder_state_changed'])
            || ! empty($changeSet['globals_changed']);
        if (! $hasChanges) {
            return [
                'status' => 'skipped',
                'reason' => 'no_changes',
                'pages' => 0,
                'post_ids' => [],
                'globals' => false,
                'fingerprint' => (string) ($changeSet['new_global_hash'] ?? ''),
            ];
        }

        $postIds = [$postId];
        if (! empty($changeSet['globals_changed'])) {
            $globalPages = get_posts(
                [
                    'post_type' => ['page', 'h4_template'],
                    'post_status' => ['publish', 'private', 'draft', 'pending', 'future'],
                    'fields' => 'ids',
                    'posts_per_page' => -1,
                    'no_found_rows' => true,
                ]
            );
            if ($globalPages !== []) {
                update_meta_cache('post', $globalPages);
            }
            foreach ($globalPages as $globalPageId) {
                $globalPageId = absint($globalPageId);
                if ($globalPageId > 0 && (string) get_post_meta($globalPageId, '_h4_builder_enabled', true) === '1') {
                    $postIds[] = $globalPageId;
                }
            }
        }
        $postIds = array_values(array_unique(array_filter(array_map('absint', $postIds))));
        foreach ($postIds as $affectedPostId) {
            clean_post_cache($affectedPostId);
            wp_cache_delete($affectedPostId, 'posts');
            wp_cache_delete('post_meta_' . $affectedPostId, 'post_meta');
        }

        $result = [
            'status' => 'requested',
            'pages' => count($postIds),
            'post_ids' => $postIds,
            'globals' => ! empty($changeSet['globals_changed']),
            'fingerprint' => (string) ($changeSet['new_global_hash'] ?? ''),
        ];
        do_action('h4bs_builder_cache_purge', $postIds, $changeSet, $result);
        $filtered = apply_filters('h4bs_builder_cache_purge_result', $result, $postIds, $changeSet);
        return is_array($filtered) ? $filtered : $result;
    }

    /**
     * @param mixed $raw
     * @return array{
     *   colors: array<string, string>,
     *   fonts: array<string, string>,
     *   typography: array<string, string>,
     *   spacing: array<string, string>,
     *   shape: array<string, string>,
     *   components: array<string, string>,
     *   layout: array<string, string>
     * }
     */
    private function sanitizeGlobalDesignTokens($raw): array
    {
        if (is_string($raw)) {
            $decoded = json_decode($raw, true);
            $raw = is_array($decoded) ? $decoded : [];
        }
        $source = is_array($raw) ? $raw : [];
        $sourceColors = is_array($source['colors'] ?? null) ? $source['colors'] : [];
        $sourceFonts = is_array($source['fonts'] ?? null) ? $source['fonts'] : [];
        $sourceTypography = is_array($source['typography'] ?? null) ? $source['typography'] : [];
        $sourceSpacing = is_array($source['spacing'] ?? null) ? $source['spacing'] : [];
        $sourceShape = is_array($source['shape'] ?? null) ? $source['shape'] : [];
        $sourceComponents = is_array($source['components'] ?? null) ? $source['components'] : [];
        $sourceLayout = is_array($source['layout'] ?? null) ? $source['layout'] : [];

        $defaultColors = [
            'primary' => '#0f6cbf',
            'primary_hover' => '#0b579c',
            'on_primary' => '#ffffff',
            'secondary' => '#0a9b7a',
            'secondary_hover' => '#087f64',
            'on_secondary' => '#ffffff',
            'accent' => '#ff7a33',
            'heading' => '#152238',
            'text' => '#1f2937',
            'muted' => '#5f6f82',
            'link' => '#0f6cbf',
            'link_hover' => '#0b579c',
            'surface' => '#ffffff',
            'surface_alt' => '#f4f7fb',
            'border' => '#d8e2ec',
            'success' => '#16855b',
            'warning' => '#a65f00',
            'danger' => '#c0362c',
            'info' => '#2268a7',
        ];
        $defaultFonts = [
            'display' => 'Poppins',
            'heading' => 'Poppins',
            'body' => 'Inter',
            'ui' => 'Inter',
            'base_size' => '16',
        ];
        $defaultTypography = [
            'h1_size' => '56',
            'h2_size' => '42',
            'h3_size' => '32',
            'h4_size' => '25',
            'h5_size' => '20',
            'h6_size' => '17',
            'heading_weight' => '700',
            'body_weight' => '400',
            'heading_line_height' => '1.12',
            'body_line_height' => '1.65',
        ];
        $defaultSpacing = [
            'page_gap' => '16',
            'section_x' => '24',
            'section_y' => '64',
            'card_gap' => '20',
        ];
        $defaultShape = [
            'radius_sm' => '4',
            'radius_md' => '8',
            'radius_lg' => '16',
            'shadow_sm' => '0 2px 8px rgba(21,34,56,.08)',
            'shadow_md' => '0 16px 40px rgba(21,34,56,.14)',
        ];
        $defaultComponents = [
            'button_padding_x' => '20',
            'button_padding_y' => '12',
            'button_radius' => '8',
            'button_weight' => '700',
            'card_padding' => '28',
            'field_height' => '46',
            'field_padding_x' => '14',
            'field_radius' => '6',
        ];
        $defaultLayout = [
            'container_max_width' => '1200',
            'container_max_width_unit' => 'px',
            'structure_columns' => '2',
            'structure_gap' => '18',
            'structure_min_height' => '140',
            'structure_layout_mode' => 'grid',
            'structure_content_width_mode' => 'inherit',
            'structure_flex_direction' => 'row',
            'structure_flex_wrap' => 'wrap',
            'structure_justify_content' => 'flex-start',
            'structure_align_items' => 'stretch',
            'structure_align_content' => 'stretch',
        ];

        $colors = [];
        foreach ($defaultColors as $key => $fallback) {
            $candidate = sanitize_hex_color((string) ($sourceColors[$key] ?? ''));
            $colors[$key] = is_string($candidate) && $candidate !== '' ? $candidate : $fallback;
        }

        $fonts = [];
        foreach (['display', 'heading', 'body', 'ui'] as $key) {
            $candidate = sanitize_text_field((string) ($sourceFonts[$key] ?? ''));
            $fonts[$key] = $candidate !== '' ? $candidate : $defaultFonts[$key];
        }
        $fonts['base_size'] = (string) max(12, min(24, (int) ($sourceFonts['base_size'] ?? $defaultFonts['base_size'])));

        $boundedNumber = static function ($value, float $minimum, float $maximum, string $fallback, int $precision = 2): string {
            if (! is_numeric($value)) {
                return $fallback;
            }
            $number = (float) $value;
            if (! is_finite($number)) {
                return $fallback;
            }
            $number = max($minimum, min($maximum, $number));
            return rtrim(rtrim(number_format($number, $precision, '.', ''), '0'), '.');
        };

        $typography = [];
        foreach (['h1_size', 'h2_size', 'h3_size', 'h4_size', 'h5_size', 'h6_size'] as $key) {
            $typography[$key] = $boundedNumber($sourceTypography[$key] ?? $defaultTypography[$key], 10, 160, $defaultTypography[$key]);
        }
        foreach (['heading_weight', 'body_weight'] as $key) {
            $typography[$key] = (string) max(100, min(900, (int) ($sourceTypography[$key] ?? $defaultTypography[$key])));
        }
        foreach (['heading_line_height', 'body_line_height'] as $key) {
            $typography[$key] = $boundedNumber($sourceTypography[$key] ?? $defaultTypography[$key], 0.8, 3, $defaultTypography[$key]);
        }

        $spacing = [];
        foreach ($defaultSpacing as $key => $fallback) {
            $spacing[$key] = $boundedNumber($sourceSpacing[$key] ?? $fallback, 0, 320, $fallback);
        }

        $shape = [];
        foreach (['radius_sm', 'radius_md', 'radius_lg'] as $key) {
            $shape[$key] = $boundedNumber($sourceShape[$key] ?? $defaultShape[$key], 0, 100, $defaultShape[$key]);
        }
        foreach (['shadow_sm', 'shadow_md'] as $key) {
            $candidate = sanitize_text_field((string) ($sourceShape[$key] ?? ''));
            $shape[$key] = $candidate !== '' ? $candidate : $defaultShape[$key];
        }

        $components = [];
        foreach (['button_padding_x', 'button_padding_y', 'button_radius', 'card_padding', 'field_height', 'field_padding_x', 'field_radius'] as $key) {
            $components[$key] = $boundedNumber($sourceComponents[$key] ?? $defaultComponents[$key], 0, 160, $defaultComponents[$key]);
        }
        $components['button_weight'] = (string) max(100, min(900, (int) ($sourceComponents['button_weight'] ?? $defaultComponents['button_weight'])));

        $containerMaxWidth = (float) ($sourceLayout['container_max_width'] ?? $defaultLayout['container_max_width']);
        if (! is_finite($containerMaxWidth) || $containerMaxWidth <= 0) {
            $containerMaxWidth = (float) $defaultLayout['container_max_width'];
        }
        $containerMaxWidth = max(320.0, min(7680.0, $containerMaxWidth));
        $containerMaxWidthUnit = $this->cssUnit((string) ($sourceLayout['container_max_width_unit'] ?? $defaultLayout['container_max_width_unit']), 'px');
        if (! in_array($containerMaxWidthUnit, ['px', '%', 'vw', 'vh', 'rem', 'em'], true)) {
            $containerMaxWidthUnit = 'px';
        }

        $structureColumns = (int) ($sourceLayout['structure_columns'] ?? $defaultLayout['structure_columns']);
        $structureColumns = max(1, min(self::MAX_COLUMNS, $structureColumns));
        $structureGap = (int) ($sourceLayout['structure_gap'] ?? $defaultLayout['structure_gap']);
        $structureGap = max(0, min(160, $structureGap));
        $structureMinHeight = (int) ($sourceLayout['structure_min_height'] ?? $defaultLayout['structure_min_height']);
        $structureMinHeight = max(40, min(2400, $structureMinHeight));
        $structureLayoutMode = $this->enumToken(
            (string) ($sourceLayout['structure_layout_mode'] ?? $defaultLayout['structure_layout_mode']),
            ['grid', 'flex'],
            'grid'
        );
        $structureContentWidthMode = $this->enumToken(
            (string) ($sourceLayout['structure_content_width_mode'] ?? $defaultLayout['structure_content_width_mode']),
            ['inherit', 'custom', 'full'],
            'inherit'
        );
        $structureFlexDirection = $this->enumToken(
            (string) ($sourceLayout['structure_flex_direction'] ?? $defaultLayout['structure_flex_direction']),
            ['row', 'row-reverse', 'column', 'column-reverse'],
            'row'
        );
        $structureFlexWrap = $this->enumToken(
            (string) ($sourceLayout['structure_flex_wrap'] ?? $defaultLayout['structure_flex_wrap']),
            ['nowrap', 'wrap', 'wrap-reverse'],
            'wrap'
        );
        $structureJustifyContent = $this->enumToken(
            (string) ($sourceLayout['structure_justify_content'] ?? $defaultLayout['structure_justify_content']),
            ['flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'],
            'flex-start'
        );
        $structureAlignItems = $this->enumToken(
            (string) ($sourceLayout['structure_align_items'] ?? $defaultLayout['structure_align_items']),
            ['stretch', 'flex-start', 'center', 'flex-end', 'baseline'],
            'stretch'
        );
        $structureAlignContent = $this->enumToken(
            (string) ($sourceLayout['structure_align_content'] ?? $defaultLayout['structure_align_content']),
            ['stretch', 'flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'],
            'stretch'
        );

        return [
            'colors' => $colors,
            'fonts' => $fonts,
            'typography' => $typography,
            'spacing' => $spacing,
            'shape' => $shape,
            'components' => $components,
            'layout' => [
                'container_max_width' => rtrim(rtrim(number_format($containerMaxWidth, 2, '.', ''), '0'), '.'),
                'container_max_width_unit' => $containerMaxWidthUnit,
                'structure_columns' => (string) $structureColumns,
                'structure_gap' => (string) $structureGap,
                'structure_min_height' => (string) $structureMinHeight,
                'structure_layout_mode' => $structureLayoutMode,
                'structure_content_width_mode' => $structureContentWidthMode,
                'structure_flex_direction' => $structureFlexDirection,
                'structure_flex_wrap' => $structureFlexWrap,
                'structure_justify_content' => $structureJustifyContent,
                'structure_align_items' => $structureAlignItems,
                'structure_align_content' => $structureAlignContent,
            ],
        ];
    }

    private function isBuilderEnabled(int $postId): bool
    {
        if ($this->previewContextActive && $this->previewContextPostId === $postId) {
            return true;
        }
        $enabled = (string) get_post_meta($postId, '_h4_builder_enabled', true);
        $mode = (string) get_post_meta($postId, '_h4_builder_mode', true);
        if ($enabled !== '1' || $mode !== 'help4') {
            return false;
        }
        return ! $this->isLegacyAccidentalStarterLayout($postId);
    }

    private function isLegacyAccidentalStarterLayout(int $postId): bool
    {
        if ((string) get_post_meta($postId, self::DOCUMENT_ORIGIN_META, true) !== '') {
            return false;
        }

        if (
            (string) get_post_meta($postId, '_h4_builder_enabled', true) !== '1'
            || (string) get_post_meta($postId, '_h4_builder_mode', true) !== 'help4'
            || $this->pageLayoutForPost($postId) !== 'default'
            || $this->pageTitleModeForPost($postId) !== 'auto'
            || $this->pageCustomCss($postId) !== ''
        ) {
            return false;
        }

        $postType = (string) get_post_type($postId);
        $postTypeObject = $postType !== '' ? get_post_type_object($postType) : null;
        if (! $postTypeObject instanceof \WP_Post_Type || $postTypeObject->_builtin || $postType === 'h4_template') {
            return false;
        }

        $layout = $this->sanitizeLayout($this->layoutForPost($postId));
        $legacyStarter = $this->sanitizeLayout(
            [
                [
                    'id' => 'block_hero',
                    'type' => 'heading',
                    'settings' => [
                        'text' => 'Welcome to Help4 Builder',
                        'tag' => 'h1',
                    ],
                ],
            ]
        );
        $matches = $layout !== []
            && hash_equals($this->builderValueHash($legacyStarter), $this->builderValueHash($layout));

        return (bool) apply_filters(
            'h4bs_builder_quarantine_legacy_starter_layout',
            $matches,
            $postId,
            $postType,
            $layout
        );
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function defaultLayout(): array
    {
        return [
            [
                'id'       => 'hero_heading',
                'type'     => 'heading',
                'settings' => [
                    'text' => 'Build anything with Help4 WordPress Builder',
                    'tag'  => 'h1',
                ],
            ],
            [
                'id'       => 'hero_text',
                'type'     => 'text',
                'settings' => [
                    'text' => 'Drag blocks from the right panel, click any block to style it, and publish without touching code.',
                ],
            ],
            [
                'id'       => 'hero_button',
                'type'     => 'button',
                'settings' => [
                    'label' => 'Get Started',
                    'url'   => '#',
                ],
            ],
        ];
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function blockTypeCatalog(): array
    {
        $catalog = [
            ['slug' => 'section', 'label' => __('Section', 'help4-builder-suite')],
            ['slug' => 'heading', 'label' => __('Heading', 'help4-builder-suite')],
            ['slug' => 'text', 'label' => __('Text', 'help4-builder-suite')],
            ['slug' => 'image', 'label' => __('Image', 'help4-builder-suite')],
            ['slug' => 'button', 'label' => __('Button', 'help4-builder-suite')],
            ['slug' => 'shortcode', 'label' => __('Shortcode', 'help4-builder-suite')],
            ['slug' => 'product_reviews', 'label' => __('Product Reviews', 'help4-builder-suite')],
            ['slug' => 'commerce_indicator', 'label' => __('Commerce Indicator', 'help4-builder-suite')],
            ['slug' => 'google_business', 'label' => __('Google Business', 'help4-builder-suite')],
            ['slug' => 'slider', 'label' => __('Slider', 'help4-builder-suite')],
            ['slug' => 'html', 'label' => __('HTML', 'help4-builder-suite')],
            ['slug' => 'spacer', 'label' => __('Spacer', 'help4-builder-suite')],
            ['slug' => 'divider', 'label' => __('Divider', 'help4-builder-suite')],
            ['slug' => 'video', 'label' => __('Video', 'help4-builder-suite')],
            ['slug' => 'icon', 'label' => __('Icon', 'help4-builder-suite')],
            ['slug' => 'list', 'label' => __('List', 'help4-builder-suite')],
            ['slug' => 'quote', 'label' => __('Quote', 'help4-builder-suite')],
            ['slug' => 'cta', 'label' => __('CTA Box', 'help4-builder-suite')],
            ['slug' => 'gallery', 'label' => __('Gallery', 'help4-builder-suite')],
            ['slug' => 'map', 'label' => __('Map Embed', 'help4-builder-suite')],
            ['slug' => 'faq', 'label' => __('FAQ', 'help4-builder-suite')],
            ['slug' => 'collection', 'label' => __('Data View / Collection', 'help4-builder-suite')],
            ['slug' => 'calendar', 'label' => __('Calendar / Events', 'help4-builder-suite')],
            ['slug' => 'staff_directory', 'label' => __('Staff Directory', 'help4-builder-suite')],
            ['slug' => 'counter', 'label' => __('Counter', 'help4-builder-suite')],
            ['slug' => 'progress', 'label' => __('Progress Bar', 'help4-builder-suite')],
            ['slug' => 'menu', 'label' => __('Menu', 'help4-builder-suite')],
            ['slug' => 'container', 'label' => __('Container', 'help4-builder-suite')],
            ['slug' => 'inner_section', 'label' => __('Inner Section', 'help4-builder-suite')],
        ];

        $contract = [
            'schema_version' => '1.2',
            'controls' => ['content', 'design', 'advanced', 'responsive', 'states'],
            'preview_mode' => 'signed_frontend',
        ];
        $catalog = array_map(
            static function (array $item) use ($contract): array {
                return array_merge($contract, $item);
            },
            $catalog
        );
        $filtered = apply_filters('h4bs_builder_block_type_catalog', $catalog);

        return is_array($filtered) ? array_values($filtered) : $catalog;
    }

    /**
     * Canonical documentation used by Builder Studio and addon block catalogs.
     *
     * @return array<string, mixed>
     */
    private function builderDocumentationCatalog(): array
    {
        $baseUrl = 'https://help4wordpress.com/';
        $entry = static function (string $path, string $label) use ($baseUrl): array {
            return [
                'label' => $label,
                'url'   => $baseUrl . ltrim($path, '/'),
            ];
        };

        $catalog = [
            'schema_version' => '1.3',
            'hub' => $entry('help4-builder-tutorials/', __('Builder tutorial hub', 'help4-builder-suite')),
            'quick_links' => [
                'click-to-edit'    => $entry('help4-builder-editor-controls/#h4-video-click-to-edit-your-first-change', __('Click an image or text to edit it', 'help4-builder-suite')),
                'first-page'      => $entry('help4-builder-first-page/', __('Build your first page', 'help4-builder-suite')),
                'editing'         => $entry('help4-builder-editor-controls/', __('Edit text and content', 'help4-builder-suite')),
                'find-settings'   => $entry('help4-builder-find-settings/', __('Find any inspector setting', 'help4-builder-suite')),
                'responsive'      => $entry('help4-builder-responsive-design/', __('Responsive and mobile design', 'help4-builder-suite')),
                'structure'       => $entry('help4-builder-sections-containers-columns/', __('Sections, containers, and columns', 'help4-builder-suite')),
                'navigator'       => $entry('help4-builder-navigator-layers/', __('Navigator and page layers', 'help4-builder-suite')),
                'templates'       => $entry('help4-builder-template-library/', __('Templates and starter layouts', 'help4-builder-suite')),
                'beginner-templates' => $entry('help4-builder-beginner-templates/', __('Insert and customize a beginner template', 'help4-builder-suite')),
                'site-kits'       => $entry('help4-builder-site-kits-starter-sites/', __('Site kits and starter sites', 'help4-builder-suite')),
                'globals'         => $entry('help4-builder-global-styles-theme-packs/', __('Global styles and theme packs', 'help4-builder-suite')),
                'header-footer'   => $entry('help4-builder-menus-headers-footers/', __('Headers, menus, and footers', 'help4-builder-suite')),
                'images'          => $entry('help4-builder-images-media/', __('Images and galleries', 'help4-builder-suite')),
                'video'           => $entry('help4-builder-video-hls-backgrounds/', __('Video, HLS, and fallbacks', 'help4-builder-suite')),
                'forms'           => $entry('help4-builder-forms-lead-capture/', __('Forms and lead capture', 'help4-builder-suite')),
                'dynamic-data'    => $entry('help4-builder-dynamic-data/', __('Dynamic fields and shortcodes', 'help4-builder-suite')),
                'custom-css'      => $entry('help4-builder-custom-css-advanced-controls/', __('Custom CSS and advanced controls', 'help4-builder-suite')),
                'commerce'        => $entry('help4-commerce-getting-started/', __('Products and checkout', 'help4-builder-suite')),
                'seo'             => $entry('help4-builder-seo-security-performance/', __('SEO and readability', 'help4-builder-suite')),
                'accessibility'   => $entry('help4-builder-accessibility-audit/', __('Accessibility checks', 'help4-builder-suite')),
                'recovery'        => $entry('help4-builder-safe-editing-recovery/', __('Undo, history, and recovery', 'help4-builder-suite')),
                'publishing'      => $entry('help4-builder-save-preview-publish/', __('Save, preview, and publish', 'help4-builder-suite')),
                'email-campaign'  => $entry('help4-email-marketing-first-campaign/', __('Create and test an email campaign', 'help4-builder-suite')),
                'email-reports'   => $entry('help4-email-marketing-reports-automations/', __('Read reports and build drip automations', 'help4-builder-suite')),
                'quick-help'      => $entry('help4-builder-quick-help/', __('Use Quick Help inside Studio', 'help4-builder-suite')),
                'troubleshooting' => $entry('help4-builder-migration-troubleshooting/', __('Troubleshooting', 'help4-builder-suite')),
            ],
            'contexts' => [
                'first-page'   => $entry('help4-builder-first-page/', __('Build your first page', 'help4-builder-suite')),
                'elements'     => $entry('help4-builder-find-settings/', __('Find and edit any element setting', 'help4-builder-suite')),
                'responsive'   => $entry('help4-builder-responsive-design/', __('Use responsive controls', 'help4-builder-suite')),
                'forms'        => $entry('help4-builder-forms-lead-capture/', __('Build forms and lead capture', 'help4-builder-suite')),
                'dynamic-data' => $entry('help4-builder-dynamic-data/', __('Use dynamic fields and shortcodes', 'help4-builder-suite')),
                'accessibility' => $entry('help4-builder-accessibility-audit/', __('Run accessibility checks', 'help4-builder-suite')),
                'troubleshooting' => $entry('help4-builder-migration-troubleshooting/', __('Troubleshoot Builder issues', 'help4-builder-suite')),
                'templates'    => $entry('help4-builder-beginner-templates/#h4-video-templates-site-kits-and-safe-imports', __('Insert and customize a beginner template', 'help4-builder-suite')),
                'site-kits'    => $entry('help4-builder-site-kits-starter-sites/#h4-video-templates-site-kits-and-safe-imports', __('Watch a safe site-kit import', 'help4-builder-suite')),
                'themes'       => $entry('help4-builder-global-styles-theme-packs/#h4-video-global-styles-and-theme-packs', __('Watch global styles and theme packs', 'help4-builder-suite')),
                'widgets'      => $entry('help4-builder-widget-library/', __('Choose and configure widgets', 'help4-builder-suite')),
                'navigator'    => $entry('help4-builder-navigator-layers/#h4-video-navigator-layers-and-reusable-sections', __('Watch Navigator and page selection', 'help4-builder-suite')),
                'mini-preview' => $entry('help4-builder-live-preview-workflow/#h4-video-live-preview-and-responsive-viewports', __('Watch the live-preview workflow', 'help4-builder-suite')),
                'page-layout'  => $entry('help4-builder-sections-containers-columns/#h4-video-sections-containers-and-columns', __('Watch page structure controls', 'help4-builder-suite')),
                'page-globals' => $entry('help4-builder-global-styles-theme-packs/#h4-video-global-styles-and-theme-packs', __('Watch global style changes', 'help4-builder-suite')),
                'page-history' => $entry('help4-builder-safe-editing-recovery/#h4-video-page-settings-history-and-recovery', __('Watch page history and recovery', 'help4-builder-suite')),
                'page-template' => $entry('help4-theme-builder-template-conditions/#h4-video-theme-builder-conditions-and-reuse', __('Watch template conditions', 'help4-builder-suite')),
                'page-code'    => $entry('help4-builder-custom-css-advanced-controls/#h4-video-custom-css-classes-and-page-styles', __('Use custom CSS safely', 'help4-builder-suite')),
                'seo' => $entry('help4-builder-seo-security-performance/#h4-video-seo-scores-readability-and-schema', __('Watch SEO scoring and readability', 'help4-builder-suite')),
                'commerce' => $entry('help4-commerce-getting-started/#h4-video-commerce-products-quantity-and-checkout', __('Watch products, quantity, and checkout', 'help4-builder-suite')),
                'email-marketing' => $entry('help4-email-marketing-first-campaign/#h4-video-newsletter-subscribers-and-campaign-readiness', __('Create and test an email campaign', 'help4-builder-suite')),
                'email-reports' => $entry('help4-email-marketing-reports-automations/#h4-video-newsletter-subscribers-and-campaign-readiness', __('Read campaign reports and build automations', 'help4-builder-suite')),
                'quick-help' => $entry('help4-builder-quick-help/', __('Use Builder Quick Help', 'help4-builder-suite')),
                'image-optimization' => $entry('help4-image-optimization-image-studio/#h4-video-images-cache-assets-and-performance', __('Watch image and performance checks', 'help4-builder-suite')),
                'site-search' => $entry('help4-site-search-setup/#h4-video-site-search-setup-and-result-testing', __('Configure site search', 'help4-builder-suite')),
                'redirects' => $entry('help4-redirects-404-monitoring/#h4-video-redirects-and-404-monitoring', __('Manage redirects and 404s', 'help4-builder-suite')),
                'insights' => $entry('help4-insights-site-health-shortcodes/#h4-video-insights-logs-and-igor-diagnostics', __('Watch Insights and diagnostics', 'help4-builder-suite')),
                'security' => $entry('help4-security-hardening-guide/#h4-video-security-updates-and-recovery', __('Watch safe hardening and updates', 'help4-builder-suite')),
                'performance' => $entry('help4-performance-cache-assets-guide/#h4-video-images-cache-assets-and-performance', __('Watch cache and asset checks', 'help4-builder-suite')),
                'feedback' => $entry('help4-feedback-igor-diagnostics/#h4-video-insights-logs-and-igor-diagnostics', __('Watch an I.G.O.R. diagnostic workflow', 'help4-builder-suite')),
                'lms' => $entry('help4-lms-courses-progress-certificates/#h4-video-lms-courses-protected-video-and-certificates', __('Build learning-site surfaces', 'help4-builder-suite')),
                'google-business' => $entry('help4-google-business-reviews-guide/#h4-video-local-international-seo-and-indexnow', __('Watch local business discovery setup', 'help4-builder-suite')),
                'mega-menu' => $entry('help4-mega-menu-guide/#h4-video-mega-menus-and-complex-navigation', __('Build complex navigation', 'help4-builder-suite')),
                'updates' => $entry('help4-dependency-update-diagnostics/#h4-video-modules-dependencies-and-update-diagnostics', __('Watch update and recovery checks', 'help4-builder-suite')),
                'website-snapshot' => $entry('help4-builder-website-snapshot/#h4-video-website-snapshot-and-portfolio-proof', __('Build portfolio and launch proof', 'help4-builder-suite')),
                'theme-catalog' => $entry('help4-blank-theme/#h4-video-theme-catalog-and-companion-theme-setup', __('Set up the companion theme', 'help4-builder-suite')),
                'migration' => $entry('help4-builder-import-export-migrations/#h4-video-migration-import-export-and-cutover', __('Watch migration and cutover checks', 'help4-builder-suite')),
                'publishing' => $entry('help4-builder-save-preview-publish/#h4-video-publish-launch-qa-and-rollback', __('Watch publish and launch QA', 'help4-builder-suite')),
            ],
            'block_types' => [
                'section'            => $entry('help4-builder-sections-containers-columns/#h4-video-sections-containers-and-columns', __('Watch sections and columns', 'help4-builder-suite')),
                'container'          => $entry('help4-builder-sections-containers-columns/#h4-video-sections-containers-and-columns', __('Watch containers and columns', 'help4-builder-suite')),
                'inner_section'      => $entry('help4-builder-sections-containers-columns/#h4-video-sections-containers-and-columns', __('Watch nested page structure', 'help4-builder-suite')),
                'heading'            => $entry('help4-builder-buttons-typography-spacing/#h4-video-edit-text-and-responsive-style', __('Watch heading and typography edits', 'help4-builder-suite')),
                'text'               => $entry('help4-builder-editor-controls/#h4-video-edit-text-and-responsive-style', __('Watch safe text editing', 'help4-builder-suite')),
                'image'              => $entry('help4-builder-images-media/#h4-video-replace-an-image-safely', __('Watch safe image replacement', 'help4-builder-suite')),
                'button'             => $entry('help4-builder-buttons-typography-spacing/#h4-video-build-and-style-buttons', __('Watch button setup and styling', 'help4-builder-suite')),
                'shortcode'          => $entry('help4-builder-dynamic-data/#h4-video-custom-fields-and-dynamic-data', __('Watch dynamic and shortcode content', 'help4-builder-suite')),
                'product_reviews'    => $entry('help4-commerce-products-variations-customer-fields/#h4-video-commerce-variations-subscriptions-and-customer-fields', __('Watch advanced commerce setup', 'help4-builder-suite')),
                'commerce_indicator' => $entry('help4-commerce-launch-testing/#h4-video-checkout-payment-routing-and-orders', __('Watch commerce and checkout checks', 'help4-builder-suite')),
                'google_business'    => $entry('help4-google-business-reviews-guide/#h4-video-local-international-seo-and-indexnow', __('Watch local business setup', 'help4-builder-suite')),
                'slider'             => $entry('help4-builder-motion-sliders/#h4-video-sliders-motion-and-video-fallbacks', __('Watch sliders and motion', 'help4-builder-suite')),
                'html'               => $entry('help4-builder-editor-controls/#h4-video-click-to-edit-your-first-change', __('Edit visible items in a migrated HTML page', 'help4-builder-suite')),
                'spacer'             => $entry('help4-builder-buttons-typography-spacing/', __('Control spacing', 'help4-builder-suite')),
                'divider'            => $entry('help4-builder-buttons-typography-spacing/', __('Style dividers and spacing', 'help4-builder-suite')),
                'video'              => $entry('help4-builder-video-hls-backgrounds/#h4-video-sliders-motion-and-video-fallbacks', __('Watch video and HLS fallbacks', 'help4-builder-suite')),
                'icon'               => $entry('help4-builder-icons/', __('Choose and style icons', 'help4-builder-suite')),
                'list'               => $entry('help4-builder-editor-controls/', __('Build readable lists', 'help4-builder-suite')),
                'quote'              => $entry('help4-builder-editor-controls/', __('Edit quotes and content', 'help4-builder-suite')),
                'cta'                => $entry('help4-builder-sales-homepage/', __('Build a call to action', 'help4-builder-suite')),
                'gallery'            => $entry('help4-builder-images-media/', __('Build image galleries', 'help4-builder-suite')),
                'map'                => $entry('help4-seo-local-international-indexnow/', __('Add local map content', 'help4-builder-suite')),
                'faq'                => $entry('help4-builder-faq-accordion-schema/', __('Build an accessible FAQ', 'help4-builder-suite')),
                'counter'            => $entry('help4-builder-buttons-typography-spacing/', __('Style counters', 'help4-builder-suite')),
                'progress'           => $entry('help4-builder-buttons-typography-spacing/', __('Style progress indicators', 'help4-builder-suite')),
                'menu'               => $entry('help4-builder-menus-headers-footers/#h4-video-headers-menus-and-mobile-navigation', __('Watch menus and mobile navigation', 'help4-builder-suite')),
            ],
        ];

        $filtered = apply_filters('h4bs_builder_documentation_catalog', $catalog);
        if (! is_array($filtered)) {
            $filtered = $catalog;
        }

        $normalizeEntry = static function ($candidate, array $fallback): array {
            if (! is_array($candidate)) {
                $candidate = [];
            }
            $label = sanitize_text_field((string) ($candidate['label'] ?? $fallback['label']));
            $url = esc_url_raw((string) ($candidate['url'] ?? $fallback['url']), ['http', 'https']);
            if ($label === '') {
                $label = (string) $fallback['label'];
            }
            if ($url === '' || ! wp_http_validate_url($url)) {
                $url = (string) $fallback['url'];
            }
            return ['label' => $label, 'url' => $url];
        };

        $normalized = [
            'schema_version' => sanitize_text_field((string) ($filtered['schema_version'] ?? '1.3')),
            'hub' => $normalizeEntry($filtered['hub'] ?? [], $catalog['hub']),
            'quick_links' => [],
            'contexts' => [],
            'block_types' => [],
        ];

        foreach (['quick_links', 'contexts', 'block_types'] as $section) {
            $filteredEntries = isset($filtered[$section]) && is_array($filtered[$section]) ? $filtered[$section] : [];
            $mergedEntries = array_replace($catalog[$section], $filteredEntries);
            foreach ($mergedEntries as $key => $candidate) {
                $safeKey = sanitize_key((string) $key);
                if ($safeKey === '') {
                    continue;
                }
                $fallback = isset($catalog[$section][$key]) && is_array($catalog[$section][$key])
                    ? $catalog[$section][$key]
                    : $catalog['hub'];
                $normalized[$section][$safeKey] = $normalizeEntry($candidate, $fallback);
            }
        }

        return $normalized;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function templatePresets(): array
    {
        $presets = [
            [
                'slug' => 'local-business-home',
                'label' => __('Local Business Home', 'help4-builder-suite'),
                'category' => __('Local Business', 'help4-builder-suite'),
                'description' => __('Hero + CTA + trusted reviews + contact section.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'lb_hero_title',
                        'type' => 'heading',
                        'settings' => ['text' => 'Grow Your Local Business Faster', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 'lb_hero_text',
                        'type' => 'text',
                        'settings' => ['text' => 'Launch high-converting pages with Help4 templates built for service businesses.'],
                    ],
                    [
                        'id' => 'lb_cta',
                        'type' => 'button',
                        'settings' => ['label' => 'Book a Free Consultation', 'url' => '#contact'],
                    ],
                    [
                        'id' => 'lb_reviews',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_testimonials limit="6"]'],
                    ],
                ],
            ],
            [
                'slug' => 'store-front',
                'label' => __('Storefront Hero', 'help4-builder-suite'),
                'category' => __('Ecommerce', 'help4-builder-suite'),
                'description' => __('Hero + product grid + checkout starter.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'sf_heading',
                        'type' => 'heading',
                        'settings' => ['text' => 'Featured Products', 'tag' => 'h2'],
                    ],
                    [
                        'id' => 'sf_products',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_products limit="8"]'],
                    ],
                    [
                        'id' => 'sf_checkout',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_checkout]'],
                    ],
                ],
            ],
            [
                'slug' => 'native-commerce-catalog',
                'label' => __('Native Commerce Catalog', 'help4-builder-suite'),
                'category' => __('Ecommerce', 'help4-builder-suite'),
                'description' => __('Searchable catalog with quick view, compare, stock badges, and cursor paging.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'ncc_heading',
                        'type' => 'heading',
                        'settings' => ['text' => 'Find the Right Product Fast', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 'ncc_intro',
                        'type' => 'text',
                        'settings' => ['text' => 'Use filters, quick view, comparison, and clear checkout routing so customers do not need to understand the backend.'],
                    ],
                    [
                        'id' => 'ncc_products',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_products limit="24" card_style="clean" hover="quick_actions" show_filters="1" filter_style="drawer" show_quick_view="1" show_compare="1" show_price_watch="1" show_waitlist="1" cursor="1"]'],
                    ],
                    [
                        'id' => 'ncc_checkout',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_checkout]'],
                    ],
                ],
            ],
            [
                'slug' => 'product-showcase-comparison',
                'label' => __('Product Showcase + Comparison', 'help4-builder-suite'),
                'category' => __('Ecommerce', 'help4-builder-suite'),
                'description' => __('Polished product cards with compare tray, quick facts, and price/restock prompts.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'psc_heading',
                        'type' => 'heading',
                        'settings' => ['text' => 'Compare Before You Buy', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 'psc_text',
                        'type' => 'text',
                        'settings' => ['text' => 'Let buyers compare product type, price, availability, and options without leaving the page.'],
                    ],
                    [
                        'id' => 'psc_products',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_products limit="8" card_style="showcase" hover="image_zoom" columns="4" columns_tablet="2" columns_mobile="1" show_filters="0" show_quick_view="1" show_compare="1" show_stock_badges="1" show_type_badges="1"]'],
                    ],
                    [
                        'id' => 'psc_note',
                        'type' => 'cta',
                        'settings' => [
                            'title' => 'Need help choosing?',
                            'text' => 'Use the connected checkout or ask support to confirm the right package before purchase.',
                            'button_label' => 'Ask for Help',
                            'button_url' => '#h4bs-contact',
                        ],
                    ],
                ],
            ],
            [
                'slug' => 'subscription-offer-catalog',
                'label' => __('Subscription Offer Catalog', 'help4-builder-suite'),
                'category' => __('Ecommerce', 'help4-builder-suite'),
                'description' => __('Recurring-plan page with plain-language value, product options, and checkout handoff.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'soc_heading',
                        'type' => 'heading',
                        'settings' => ['text' => 'Pick the Plan by What You Want Handled', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 'soc_text',
                        'type' => 'text',
                        'settings' => ['text' => 'Explain what renews, what support includes, and how the customer gets help after checkout.'],
                    ],
                    [
                        'id' => 'soc_products',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_products limit="12" search="subscription" search_mode="contains" card_style="showcase" show_filters="1" filter_style="compact" show_type_badges="1" show_quick_view="1" show_compare="1"]'],
                    ],
                    [
                        'id' => 'soc_faq',
                        'type' => 'faq',
                        'settings' => ['items' => 'What renews?::The product details should explain the renewal term before checkout.\nCan I change plans?::Use the account/support route to request a plan change.\nWhere do I get help?::Use the connected support path after purchase.', 'open_first' => '1'],
                    ],
                ],
            ],
            [
                'slug' => 'checkout-recovery-support',
                'label' => __('Checkout Recovery Support', 'help4-builder-suite'),
                'category' => __('Ecommerce', 'help4-builder-suite'),
                'description' => __('Support page for failed checkout, restock questions, price questions, and account routing.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'crs_heading',
                        'type' => 'heading',
                        'settings' => ['text' => 'Finish Checkout or Get the Right Help', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 'crs_products',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_products limit="12" card_style="compact" show_filters="1" show_quick_view="1" show_price_watch="1" show_waitlist="1" show_compare="0" request_anchor="h4bs-contact"]'],
                    ],
                    [
                        'id' => 'crs_checkout',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_checkout]'],
                    ],
                    [
                        'id' => 'crs_support',
                        'type' => 'text',
                        'settings' => ['text' => 'If payment, account access, or product availability is the blocker, route the customer to support instead of letting the sale disappear.'],
                    ],
                ],
            ],
            [
                'slug' => 'realtor-landing',
                'label' => __('Realtor Landing', 'help4-builder-suite'),
                'category' => __('Real Estate', 'help4-builder-suite'),
                'description' => __('Hero + map/embed placeholder + lead capture CTA.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 're_header',
                        'type' => 'heading',
                        'settings' => ['text' => 'Find Your Next Home', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 're_search',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_search type="all" placeholder="Search properties..."]'],
                    ],
                    [
                        'id' => 're_cta',
                        'type' => 'button',
                        'settings' => ['label' => 'Request a Showing', 'url' => '#contact'],
                    ],
                ],
            ],
            [
                'slug' => 'contractor-estimate',
                'label' => __('Contractor Estimate Funnel', 'help4-builder-suite'),
                'category' => __('Contractor + Trades', 'help4-builder-suite'),
                'description' => __('Service hero + trust copy + quote request + service area map.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'co_hero_heading',
                        'type' => 'heading',
                        'settings' => ['text' => 'Need Help Fast? Get a Same-Day Estimate', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 'co_hero_text',
                        'type' => 'text',
                        'settings' => ['text' => 'Route leads by ZIP, capture service details, and dispatch with confidence from one workflow.'],
                    ],
                    [
                        'id' => 'co_quote',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_quote_request id="1"]'],
                    ],
                    [
                        'id' => 'co_service_map',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_service_area_map]'],
                    ],
                    [
                        'id' => 'co_cta',
                        'type' => 'button',
                        'settings' => ['label' => 'Book Emergency Service', 'url' => '#contact'],
                    ],
                ],
            ],
            [
                'slug' => 'law-firm-consult',
                'label' => __('Law Firm Consult Page', 'help4-builder-suite'),
                'category' => __('Legal', 'help4-builder-suite'),
                'description' => __('Authority headline + intake checklist + consultation call-to-action.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'law_heading',
                        'type' => 'heading',
                        'settings' => ['text' => 'Practical Legal Strategy, Not Guesswork', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 'law_text',
                        'type' => 'text',
                        'settings' => ['text' => 'Show practice focus, outcomes, and clear next steps so visitors become qualified consultations quickly.'],
                    ],
                    [
                        'id' => 'law_list',
                        'type' => 'list',
                        'settings' => ['items' => 'Business disputes|Contracts & compliance|Civil litigation|Urgent legal response', 'ordered' => '0'],
                    ],
                    [
                        'id' => 'law_form',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_contact_form]'],
                    ],
                ],
            ],
            [
                'slug' => 'clinic-booking',
                'label' => __('Clinic Booking Landing', 'help4-builder-suite'),
                'category' => __('Healthcare', 'help4-builder-suite'),
                'description' => __('Patient-ready hero + scheduling block + review proof section.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'med_heading',
                        'type' => 'heading',
                        'settings' => ['text' => 'Care You Can Book in Minutes', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 'med_text',
                        'type' => 'text',
                        'settings' => ['text' => 'Let patients view services, choose a time, and receive reminders without phone bottlenecks.'],
                    ],
                    [
                        'id' => 'med_booking',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_booking_calendar]'],
                    ],
                    [
                        'id' => 'med_reviews',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_testimonials limit="6"]'],
                    ],
                ],
            ],
            [
                'slug' => 'membership-portal-launch',
                'label' => __('Membership Portal Launch', 'help4-builder-suite'),
                'category' => __('Membership + Portal', 'help4-builder-suite'),
                'description' => __('Member onboarding hero + dashboard + gated resource stack.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'mp_heading',
                        'type' => 'heading',
                        'settings' => ['text' => 'Launch a Member Hub That Scales', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 'mp_text',
                        'type' => 'text',
                        'settings' => ['text' => 'Bundle secure content, member dashboards, and account actions into one clean experience.'],
                    ],
                    [
                        'id' => 'mp_dash',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_member_dashboard]'],
                    ],
                    [
                        'id' => 'mp_library',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_resource_library]'],
                    ],
                ],
            ],
            [
                'slug' => 'one-page-offer-funnel',
                'label' => __('One Page Offer Funnel', 'help4-builder-suite'),
                'category' => __('Marketing Funnel', 'help4-builder-suite'),
                'description' => __('Headline + value stack + pricing + FAQ + direct action.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'op_heading',
                        'type' => 'heading',
                        'settings' => ['text' => 'One Offer. One Page. Clear Conversion Path.', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 'op_cta',
                        'type' => 'cta',
                        'settings' => [
                            'title' => 'Everything Needed to Launch Fast',
                            'text' => 'Stack services, automation, and support into one plan visitors can understand instantly.',
                            'button_label' => 'Start Now',
                            'button_url' => '#checkout',
                        ],
                    ],
                    [
                        'id' => 'op_pricing',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_pricing_table plan="starter"]'],
                    ],
                    [
                        'id' => 'op_faq',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_faq title="Offer FAQs"]'],
                    ],
                ],
            ],
            [
                'slug' => 'hosting-plan-comparison',
                'label' => __('Hosting Plan Comparison', 'help4-builder-suite'),
                'category' => __('Hosting Sales', 'help4-builder-suite'),
                'description' => __('Plan ladder + feature table + CTA route to cart or ticket.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'host_heading',
                        'type' => 'heading',
                        'settings' => ['text' => 'Choose the Right Support Path', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 'host_intro',
                        'type' => 'text',
                        'settings' => ['text' => 'Start DIY at $4.95/month, then move into managed tiers as traffic and risk increase.'],
                    ],
                    [
                        'id' => 'host_compare',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_pricing_table plan="starter"]'],
                    ],
                    [
                        'id' => 'host_cta',
                        'type' => 'button',
                        'settings' => ['label' => 'Open Cart', 'url' => home_url('/cart/')],
                    ],
                ],
            ],
            [
                'slug' => 'plugin-showcase',
                'label' => __('Plugin Showcase Page', 'help4-builder-suite'),
                'category' => __('Plugin Marketing', 'help4-builder-suite'),
                'description' => __('Feature blocks + screenshot gallery + FAQ + final CTA.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'plugin_heading',
                        'type' => 'heading',
                        'settings' => ['text' => 'Everything You Need in One Stack', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 'plugin_text',
                        'type' => 'text',
                        'settings' => ['text' => 'Show builder, theme, and router capabilities with practical screenshots and clear outcomes.'],
                    ],
                    [
                        'id' => 'plugin_gallery',
                        'type' => 'gallery',
                        'settings' => ['images' => '', 'columns' => 3],
                    ],
                    [
                        'id' => 'plugin_faq',
                        'type' => 'faq',
                        'settings' => ['items' => 'Does it replace multiple plugins?::Yes, with module toggles to avoid bloat.\nCan we import templates?::Yes, with Insert/Replace and spec-string portability.', 'open_first' => '1'],
                    ],
                ],
            ],
            [
                'slug' => 'lms-certification-launch',
                'label' => __('LMS + Certification Launch', 'help4-builder-suite'),
                'category' => __('LMS + Education', 'help4-builder-suite'),
                'description' => __('Course intro + enrollment CTA + progress + testimonials.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'lms_heading',
                        'type' => 'heading',
                        'settings' => ['text' => 'Launch Certification Programs Fast', 'tag' => 'h1'],
                    ],
                    [
                        'id' => 'lms_text',
                        'type' => 'text',
                        'settings' => ['text' => 'Bundle enrollment, lesson flow, and completion proof into one learning experience.'],
                    ],
                    [
                        'id' => 'lms_progress',
                        'type' => 'progress',
                        'settings' => ['value' => 72, 'label' => 'Average completion rate'],
                    ],
                    [
                        'id' => 'lms_reviews',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_testimonials limit="5"]'],
                    ],
                ],
            ],
        ];

        return array_merge($presets, $this->basicTemplatePresets(), $this->designTemplatePresets());
    }

    /**
     * Beginner-focused, native Builder layouts that make the first useful
     * page possible without understanding an industry kit or custom code.
     *
     * @return array<int, array<string, mixed>>
     */
    private function basicTemplatePresets(): array
    {
        static $catalog = null;
        if (is_array($catalog)) {
            return $catalog;
        }

        $catalogPath = H4BS_PATH . 'includes/Modules/Builder/basic-template-presets.php';
        if (! is_readable($catalogPath)) {
            $catalog = [];
            return $catalog;
        }

        $loaded = require $catalogPath;
        if (! is_array($loaded)) {
            $catalog = [];
            return $catalog;
        }

        $catalog = array_values(
            array_filter(
                $loaded,
                static fn ($preset): bool => is_array($preset)
            )
        );

        return $catalog;
    }

    /**
     * Original, editable site foundations. Kits intentionally reference native
     * Builder presets instead of customer layouts or third-party template files.
     *
     * @return array<int, array<string, mixed>>
     */
    private function siteKitCatalog(): array
    {
        $kits = [
            [
                'slug' => 'local-service-launch',
                'label' => __('Local Service Launch', 'help4-builder-suite'),
                'industry' => __('Local Services', 'help4-builder-suite'),
                'description' => __('Quote-focused foundation for contractors, home services, repair teams, and appointment businesses.', 'help4-builder-suite'),
                'theme_pack' => 'help4-contractor-pro',
                'features' => ['7 draft pages', 'utility header', 'business footer', '404 recovery', 'kit navigation'],
                'pages' => [
                    ['key' => 'home', 'title' => 'Home', 'slug' => 'home', 'template_slug' => 'local-business-home'],
                    ['key' => 'services', 'title' => 'Services', 'slug' => 'services', 'template_slug' => 'local-service-command'],
                    ['key' => 'about', 'title' => 'About the Team', 'slug' => 'about', 'template_slug' => 'trust-authority-team'],
                    ['key' => 'work', 'title' => 'Recent Work', 'slug' => 'recent-work', 'template_slug' => 'proof-case-study-results'],
                    ['key' => 'pricing', 'title' => 'Service Plans', 'slug' => 'service-plans', 'template_slug' => 'pricing-five-plan-responsive'],
                    ['key' => 'faq', 'title' => 'Frequently Asked Questions', 'slug' => 'frequently-asked-questions', 'template_slug' => 'faq-visible-schema-ready'],
                    ['key' => 'contact', 'title' => 'Request an Estimate', 'slug' => 'contact', 'template_slug' => 'contact-intake-split'],
                ],
                'templates' => [
                    ['key' => 'header', 'title' => 'Local Service Header', 'slug' => 'local-service-header', 'template_slug' => 'header-utility-service', 'role' => 'header', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => 'footer', 'title' => 'Local Service Footer', 'slug' => 'local-service-footer', 'template_slug' => 'footer-business-columns', 'role' => 'footer', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => '404', 'title' => 'Local Service 404', 'slug' => 'local-service-404', 'template_slug' => '404-service-rescue', 'role' => '404', 'scope' => '404', 'priority' => 100],
                ],
            ],
            [
                'slug' => 'professional-firm',
                'label' => __('Professional Firm', 'help4-builder-suite'),
                'industry' => __('Professional Services', 'help4-builder-suite'),
                'description' => __('Authority-led foundation for legal, consulting, accounting, finance, and advisory teams.', 'help4-builder-suite'),
                'theme_pack' => 'help4-legal-shield',
                'features' => ['8 draft pages', 'logo-ready header', 'authority sections', 'pricing', 'guided intake'],
                'pages' => [
                    ['key' => 'home', 'title' => 'Home', 'slug' => 'home', 'template_slug' => 'sales-homepage-open-bands'],
                    ['key' => 'services', 'title' => 'Services', 'slug' => 'services', 'template_slug' => 'handled-wordpress-service'],
                    ['key' => 'expertise', 'title' => 'Expertise', 'slug' => 'expertise', 'template_slug' => 'law-firm-consult'],
                    ['key' => 'about', 'title' => 'About the Firm', 'slug' => 'about', 'template_slug' => 'trust-authority-team'],
                    ['key' => 'results', 'title' => 'Client Results', 'slug' => 'results', 'template_slug' => 'proof-case-study-results'],
                    ['key' => 'pricing', 'title' => 'Engagement Options', 'slug' => 'engagement-options', 'template_slug' => 'pricing-five-plan-responsive'],
                    ['key' => 'faq', 'title' => 'Frequently Asked Questions', 'slug' => 'frequently-asked-questions', 'template_slug' => 'faq-visible-schema-ready'],
                    ['key' => 'contact', 'title' => 'Request a Consultation', 'slug' => 'contact', 'template_slug' => 'contact-intake-split'],
                ],
                'templates' => [
                    ['key' => 'header', 'title' => 'Professional Firm Header', 'slug' => 'professional-firm-header', 'template_slug' => 'header-exact-logo-nav-cta', 'role' => 'header', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => 'footer', 'title' => 'Professional Firm Footer', 'slug' => 'professional-firm-footer', 'template_slug' => 'footer-business-columns', 'role' => 'footer', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => '404', 'title' => 'Professional Firm 404', 'slug' => 'professional-firm-404', 'template_slug' => '404-search-rescue', 'role' => '404', 'scope' => '404', 'priority' => 100],
                ],
            ],
            [
                'slug' => 'creative-studio',
                'label' => __('Creative Studio', 'help4-builder-suite'),
                'industry' => __('Portfolio + Creative', 'help4-builder-suite'),
                'description' => __('Proof-first foundation for designers, photographers, production teams, creators, and agencies.', 'help4-builder-suite'),
                'theme_pack' => 'help4-proof-studio',
                'features' => ['6 draft pages', 'centered header', 'portfolio home', 'case-study proof', 'minimal footer'],
                'pages' => [
                    ['key' => 'home', 'title' => 'Home', 'slug' => 'home', 'template_slug' => 'proof-studio-portfolio'],
                    ['key' => 'work', 'title' => 'Selected Work', 'slug' => 'work', 'template_slug' => 'proof-case-study-results'],
                    ['key' => 'services', 'title' => 'Creative Services', 'slug' => 'services', 'template_slug' => 'sales-homepage-open-bands'],
                    ['key' => 'about', 'title' => 'About the Studio', 'slug' => 'about', 'template_slug' => 'trust-authority-team'],
                    ['key' => 'faq', 'title' => 'Project Questions', 'slug' => 'frequently-asked-questions', 'template_slug' => 'faq-visible-schema-ready'],
                    ['key' => 'contact', 'title' => 'Start a Project', 'slug' => 'contact', 'template_slug' => 'contact-intake-split'],
                ],
                'templates' => [
                    ['key' => 'header', 'title' => 'Creative Studio Header', 'slug' => 'creative-studio-header', 'template_slug' => 'header-centered-minimal', 'role' => 'header', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => 'footer', 'title' => 'Creative Studio Footer', 'slug' => 'creative-studio-footer', 'template_slug' => 'footer-minimal-legal', 'role' => 'footer', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => '404', 'title' => 'Creative Studio 404', 'slug' => 'creative-studio-404', 'template_slug' => '404-search-rescue', 'role' => '404', 'scope' => '404', 'priority' => 100],
                ],
            ],
            [
                'slug' => 'nonprofit-mission',
                'label' => __('Nonprofit Mission', 'help4-builder-suite'),
                'industry' => __('Nonprofit + Community', 'help4-builder-suite'),
                'description' => __('Mission, program, impact, volunteer, and contact foundation for community organizations.', 'help4-builder-suite'),
                'theme_pack' => 'help4-foundation',
                'features' => ['7 draft pages', 'mission narrative', 'program paths', 'impact proof', 'volunteer intake'],
                'pages' => [
                    ['key' => 'home', 'title' => 'Home', 'slug' => 'home', 'template_slug' => 'sales-homepage-open-bands'],
                    ['key' => 'mission', 'title' => 'Our Mission', 'slug' => 'mission', 'template_slug' => 'trust-authority-team'],
                    ['key' => 'programs', 'title' => 'Programs', 'slug' => 'programs', 'template_slug' => 'local-service-command'],
                    ['key' => 'impact', 'title' => 'Our Impact', 'slug' => 'impact', 'template_slug' => 'proof-case-study-results'],
                    ['key' => 'resources', 'title' => 'Resources', 'slug' => 'resources', 'template_slug' => 'visual-documentation-hub'],
                    ['key' => 'faq', 'title' => 'Frequently Asked Questions', 'slug' => 'frequently-asked-questions', 'template_slug' => 'faq-visible-schema-ready'],
                    ['key' => 'contact', 'title' => 'Volunteer or Contact Us', 'slug' => 'contact', 'template_slug' => 'contact-intake-split'],
                ],
                'templates' => [
                    ['key' => 'header', 'title' => 'Nonprofit Header', 'slug' => 'nonprofit-header', 'template_slug' => 'header-exact-logo-nav-cta', 'role' => 'header', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => 'footer', 'title' => 'Nonprofit Footer', 'slug' => 'nonprofit-footer', 'template_slug' => 'footer-business-columns', 'role' => 'footer', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => '404', 'title' => 'Nonprofit 404', 'slug' => 'nonprofit-404', 'template_slug' => '404-search-rescue', 'role' => '404', 'scope' => '404', 'priority' => 100],
                ],
            ],
            [
                'slug' => 'learning-academy',
                'label' => __('Learning Academy', 'help4-builder-suite'),
                'industry' => __('Courses + Education', 'help4-builder-suite'),
                'description' => __('Course, resource, membership, certification, FAQ, and enrollment foundation for educators.', 'help4-builder-suite'),
                'theme_pack' => 'help4-holistic-studio',
                'features' => ['7 draft pages', 'course launch', 'resource library', 'membership path', 'certification proof'],
                'pages' => [
                    ['key' => 'home', 'title' => 'Academy Home', 'slug' => 'home', 'template_slug' => 'lms-certification-launch'],
                    ['key' => 'courses', 'title' => 'Courses', 'slug' => 'courses', 'template_slug' => 'visual-documentation-hub'],
                    ['key' => 'membership', 'title' => 'Membership', 'slug' => 'membership', 'template_slug' => 'membership-portal-launch'],
                    ['key' => 'certification', 'title' => 'Certification', 'slug' => 'certification', 'template_slug' => 'lms-certification-launch'],
                    ['key' => 'plans', 'title' => 'Learning Plans', 'slug' => 'plans', 'template_slug' => 'pricing-five-plan-responsive'],
                    ['key' => 'faq', 'title' => 'Learner Questions', 'slug' => 'frequently-asked-questions', 'template_slug' => 'faq-visible-schema-ready'],
                    ['key' => 'contact', 'title' => 'Enrollment Help', 'slug' => 'contact', 'template_slug' => 'contact-intake-split'],
                ],
                'templates' => [
                    ['key' => 'header', 'title' => 'Learning Academy Header', 'slug' => 'learning-academy-header', 'template_slug' => 'header-brand-nav-cta', 'role' => 'header', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => 'footer', 'title' => 'Learning Academy Footer', 'slug' => 'learning-academy-footer', 'template_slug' => 'footer-business-columns', 'role' => 'footer', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => '404', 'title' => 'Learning Academy 404', 'slug' => 'learning-academy-404', 'template_slug' => '404-search-rescue', 'role' => '404', 'scope' => '404', 'priority' => 100],
                ],
            ],
            [
                'slug' => 'membership-community',
                'label' => __('Membership Community', 'help4-builder-suite'),
                'industry' => __('Membership + Portal', 'help4-builder-suite'),
                'description' => __('Member benefits, plans, resources, dashboard, FAQ, and support foundation.', 'help4-builder-suite'),
                'theme_pack' => 'help4-membership-core',
                'features' => ['7 draft pages', 'member dashboard', 'plan ladder', 'resource hub', 'support intake'],
                'pages' => [
                    ['key' => 'home', 'title' => 'Community Home', 'slug' => 'home', 'template_slug' => 'membership-portal-launch'],
                    ['key' => 'benefits', 'title' => 'Member Benefits', 'slug' => 'benefits', 'template_slug' => 'sales-homepage-open-bands'],
                    ['key' => 'plans', 'title' => 'Membership Plans', 'slug' => 'plans', 'template_slug' => 'pricing-five-plan-responsive'],
                    ['key' => 'resources', 'title' => 'Member Resources', 'slug' => 'resources', 'template_slug' => 'visual-documentation-hub'],
                    ['key' => 'dashboard', 'title' => 'Member Dashboard', 'slug' => 'dashboard', 'template_slug' => 'membership-portal-launch'],
                    ['key' => 'faq', 'title' => 'Membership Questions', 'slug' => 'frequently-asked-questions', 'template_slug' => 'faq-visible-schema-ready'],
                    ['key' => 'contact', 'title' => 'Member Support', 'slug' => 'contact', 'template_slug' => 'contact-intake-split'],
                ],
                'templates' => [
                    ['key' => 'header', 'title' => 'Membership Header', 'slug' => 'membership-header', 'template_slug' => 'header-brand-nav-cta', 'role' => 'header', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => 'footer', 'title' => 'Membership Footer', 'slug' => 'membership-footer', 'template_slug' => 'footer-business-columns', 'role' => 'footer', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => '404', 'title' => 'Membership 404', 'slug' => 'membership-404', 'template_slug' => '404-search-rescue', 'role' => '404', 'scope' => '404', 'priority' => 100],
                ],
            ],
            [
                'slug' => 'native-commerce-shop',
                'label' => __('Native Commerce Shop', 'help4-builder-suite'),
                'industry' => __('Commerce + Catalog', 'help4-builder-suite'),
                'description' => __('Store, catalog, product comparison, subscriptions, checkout help, and contact foundation.', 'help4-builder-suite'),
                'theme_pack' => 'help4-commerce-neo',
                'features' => ['8 draft pages', 'native catalog', 'subscriptions', 'comparison', 'commerce footer'],
                'pages' => [
                    ['key' => 'home', 'title' => 'Store Home', 'slug' => 'home', 'template_slug' => 'store-front'],
                    ['key' => 'shop', 'title' => 'Shop', 'slug' => 'shop', 'template_slug' => 'native-commerce-catalog'],
                    ['key' => 'collections', 'title' => 'Collections', 'slug' => 'collections', 'template_slug' => 'catalog-command-store'],
                    ['key' => 'compare', 'title' => 'Compare Products', 'slug' => 'compare', 'template_slug' => 'product-showcase-comparison'],
                    ['key' => 'subscriptions', 'title' => 'Subscriptions', 'slug' => 'subscriptions', 'template_slug' => 'subscription-offer-catalog'],
                    ['key' => 'checkout-help', 'title' => 'Checkout Help', 'slug' => 'checkout-help', 'template_slug' => 'checkout-recovery-support'],
                    ['key' => 'faq', 'title' => 'Ordering Questions', 'slug' => 'frequently-asked-questions', 'template_slug' => 'faq-visible-schema-ready'],
                    ['key' => 'contact', 'title' => 'Customer Support', 'slug' => 'contact', 'template_slug' => 'contact-intake-split'],
                ],
                'templates' => [
                    ['key' => 'header', 'title' => 'Commerce Header', 'slug' => 'commerce-header', 'template_slug' => 'header-exact-logo-nav-cta', 'role' => 'header', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => 'footer', 'title' => 'Commerce Footer', 'slug' => 'commerce-footer', 'template_slug' => 'footer-commerce-support', 'role' => 'footer', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => '404', 'title' => 'Commerce 404', 'slug' => 'commerce-404', 'template_slug' => '404-search-rescue', 'role' => '404', 'scope' => '404', 'priority' => 100],
                ],
            ],
            [
                'slug' => 'product-saas-launch',
                'label' => __('Product + SaaS Launch', 'help4-builder-suite'),
                'industry' => __('Software + Product', 'help4-builder-suite'),
                'description' => __('Product story, features, pricing, documentation, proof, FAQ, and contact foundation.', 'help4-builder-suite'),
                'theme_pack' => 'help4-enterprise-dark',
                'features' => ['7 draft pages', 'product launch', 'feature showcase', 'documentation', 'pricing and proof'],
                'pages' => [
                    ['key' => 'home', 'title' => 'Product Home', 'slug' => 'home', 'template_slug' => 'plugin-product-launch'],
                    ['key' => 'features', 'title' => 'Features', 'slug' => 'features', 'template_slug' => 'plugin-showcase'],
                    ['key' => 'pricing', 'title' => 'Pricing', 'slug' => 'pricing', 'template_slug' => 'pricing-five-plan-responsive'],
                    ['key' => 'documentation', 'title' => 'Documentation', 'slug' => 'documentation', 'template_slug' => 'visual-documentation-hub'],
                    ['key' => 'results', 'title' => 'Customer Results', 'slug' => 'results', 'template_slug' => 'proof-case-study-results'],
                    ['key' => 'faq', 'title' => 'Product Questions', 'slug' => 'frequently-asked-questions', 'template_slug' => 'faq-visible-schema-ready'],
                    ['key' => 'contact', 'title' => 'Talk to the Team', 'slug' => 'contact', 'template_slug' => 'contact-intake-split'],
                ],
                'templates' => [
                    ['key' => 'header', 'title' => 'Product Header', 'slug' => 'product-header', 'template_slug' => 'header-exact-logo-nav-cta', 'role' => 'header', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => 'footer', 'title' => 'Product Footer', 'slug' => 'product-footer', 'template_slug' => 'footer-business-columns', 'role' => 'footer', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => '404', 'title' => 'Product 404', 'slug' => 'product-404', 'template_slug' => '404-search-rescue', 'role' => '404', 'scope' => '404', 'priority' => 100],
                ],
            ],
            [
                'slug' => 'real-estate-team',
                'label' => __('Real Estate Team', 'help4-builder-suite'),
                'industry' => __('Real Estate', 'help4-builder-suite'),
                'description' => __('Listing, seller, neighborhood, proof, team, FAQ, and lead-intake foundation for agents and brokerages.', 'help4-builder-suite'),
                'theme_pack' => 'help4-realtor-luxe',
                'features' => ['8 draft pages', 'listing search', 'seller path', 'neighborhood guides', 'lead intake'],
                'pages' => [
                    ['key' => 'home', 'title' => 'Real Estate Home', 'slug' => 'home', 'template_slug' => 'realtor-landing'],
                    ['key' => 'listings', 'title' => 'Property Listings', 'slug' => 'listings', 'template_slug' => 'native-commerce-catalog'],
                    ['key' => 'buy', 'title' => 'Buy a Home', 'slug' => 'buy', 'template_slug' => 'sales-homepage-open-bands'],
                    ['key' => 'sell', 'title' => 'Sell a Home', 'slug' => 'sell', 'template_slug' => 'local-service-command'],
                    ['key' => 'areas', 'title' => 'Neighborhood Guides', 'slug' => 'neighborhoods', 'template_slug' => 'visual-documentation-hub'],
                    ['key' => 'about', 'title' => 'Meet the Team', 'slug' => 'about', 'template_slug' => 'trust-authority-team'],
                    ['key' => 'faq', 'title' => 'Real Estate Questions', 'slug' => 'frequently-asked-questions', 'template_slug' => 'faq-visible-schema-ready'],
                    ['key' => 'contact', 'title' => 'Talk to an Agent', 'slug' => 'contact', 'template_slug' => 'contact-intake-split'],
                ],
                'templates' => [
                    ['key' => 'header', 'title' => 'Real Estate Header', 'slug' => 'real-estate-header', 'template_slug' => 'header-exact-logo-nav-cta', 'role' => 'header', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => 'footer', 'title' => 'Real Estate Footer', 'slug' => 'real-estate-footer', 'template_slug' => 'footer-business-columns', 'role' => 'footer', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => '404', 'title' => 'Real Estate 404', 'slug' => 'real-estate-404', 'template_slug' => '404-search-rescue', 'role' => '404', 'scope' => '404', 'priority' => 100],
                ],
            ],
            [
                'slug' => 'clinic-wellness',
                'label' => __('Clinic + Wellness', 'help4-builder-suite'),
                'industry' => __('Health + Wellness', 'help4-builder-suite'),
                'description' => __('Service, practitioner, booking, plan, resource, FAQ, and patient-contact foundation.', 'help4-builder-suite'),
                'theme_pack' => 'help4-clinic-care',
                'features' => ['7 draft pages', 'booking path', 'practitioner trust', 'service plans', 'patient resources'],
                'pages' => [
                    ['key' => 'home', 'title' => 'Clinic Home', 'slug' => 'home', 'template_slug' => 'clinic-booking'],
                    ['key' => 'services', 'title' => 'Services', 'slug' => 'services', 'template_slug' => 'local-service-command'],
                    ['key' => 'team', 'title' => 'Practitioners', 'slug' => 'practitioners', 'template_slug' => 'trust-authority-team'],
                    ['key' => 'plans', 'title' => 'Care Plans', 'slug' => 'care-plans', 'template_slug' => 'pricing-five-plan-responsive'],
                    ['key' => 'resources', 'title' => 'Patient Resources', 'slug' => 'resources', 'template_slug' => 'visual-documentation-hub'],
                    ['key' => 'faq', 'title' => 'Patient Questions', 'slug' => 'frequently-asked-questions', 'template_slug' => 'faq-visible-schema-ready'],
                    ['key' => 'contact', 'title' => 'Book or Contact Us', 'slug' => 'contact', 'template_slug' => 'contact-intake-split'],
                ],
                'templates' => [
                    ['key' => 'header', 'title' => 'Clinic Header', 'slug' => 'clinic-header', 'template_slug' => 'header-brand-nav-cta', 'role' => 'header', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => 'footer', 'title' => 'Clinic Footer', 'slug' => 'clinic-footer', 'template_slug' => 'footer-business-columns', 'role' => 'footer', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => '404', 'title' => 'Clinic 404', 'slug' => 'clinic-404', 'template_slug' => '404-search-rescue', 'role' => '404', 'scope' => '404', 'priority' => 100],
                ],
            ],
            [
                'slug' => 'managed-wordpress-provider',
                'label' => __('Managed WordPress Provider', 'help4-builder-suite'),
                'industry' => __('Hosting + WordPress', 'help4-builder-suite'),
                'description' => __('Hosting, care, plugin, proof, documentation, FAQ, and support foundation for WordPress providers.', 'help4-builder-suite'),
                'theme_pack' => 'help4-handled-wordpress',
                'features' => ['7 draft pages', 'hosting comparison', 'care offer', 'plugin showcase', 'support documentation'],
                'pages' => [
                    ['key' => 'home', 'title' => 'Managed WordPress Home', 'slug' => 'home', 'template_slug' => 'handled-wordpress-service'],
                    ['key' => 'hosting', 'title' => 'Hosting Plans', 'slug' => 'hosting', 'template_slug' => 'hosting-plan-comparison'],
                    ['key' => 'care', 'title' => 'WordPress Care', 'slug' => 'wordpress-care', 'template_slug' => 'sales-homepage-open-bands'],
                    ['key' => 'plugins', 'title' => 'WordPress Tools', 'slug' => 'wordpress-tools', 'template_slug' => 'plugin-showcase'],
                    ['key' => 'documentation', 'title' => 'Documentation', 'slug' => 'documentation', 'template_slug' => 'visual-documentation-hub'],
                    ['key' => 'faq', 'title' => 'Hosting Questions', 'slug' => 'frequently-asked-questions', 'template_slug' => 'faq-visible-schema-ready'],
                    ['key' => 'contact', 'title' => 'Get WordPress Help', 'slug' => 'contact', 'template_slug' => 'contact-intake-split'],
                ],
                'templates' => [
                    ['key' => 'header', 'title' => 'Managed WordPress Header', 'slug' => 'managed-wordpress-header', 'template_slug' => 'header-exact-logo-nav-cta', 'role' => 'header', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => 'footer', 'title' => 'Managed WordPress Footer', 'slug' => 'managed-wordpress-footer', 'template_slug' => 'footer-business-columns', 'role' => 'footer', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => '404', 'title' => 'Managed WordPress 404', 'slug' => 'managed-wordpress-404', 'template_slug' => '404-service-rescue', 'role' => '404', 'scope' => '404', 'priority' => 100],
                ],
            ],
            [
                'slug' => 'editorial-knowledge-hub',
                'label' => __('Editorial Knowledge Hub', 'help4-builder-suite'),
                'industry' => __('Editorial + Resources', 'help4-builder-suite'),
                'description' => __('Guide, resource, authority, membership, FAQ, and contact foundation for publishers and experts.', 'help4-builder-suite'),
                'theme_pack' => 'help4-foundation',
                'features' => ['7 draft pages', 'guide hub', 'resource library', 'membership path', 'editorial authority'],
                'pages' => [
                    ['key' => 'home', 'title' => 'Knowledge Hub Home', 'slug' => 'home', 'template_slug' => 'visual-documentation-hub'],
                    ['key' => 'guides', 'title' => 'Guides', 'slug' => 'guides', 'template_slug' => 'visual-documentation-hub'],
                    ['key' => 'topics', 'title' => 'Topics', 'slug' => 'topics', 'template_slug' => 'sales-homepage-open-bands'],
                    ['key' => 'membership', 'title' => 'Membership', 'slug' => 'membership', 'template_slug' => 'membership-portal-launch'],
                    ['key' => 'about', 'title' => 'About the Publisher', 'slug' => 'about', 'template_slug' => 'trust-authority-team'],
                    ['key' => 'faq', 'title' => 'Reader Questions', 'slug' => 'frequently-asked-questions', 'template_slug' => 'faq-visible-schema-ready'],
                    ['key' => 'contact', 'title' => 'Contact the Editorial Team', 'slug' => 'contact', 'template_slug' => 'contact-intake-split'],
                ],
                'templates' => [
                    ['key' => 'header', 'title' => 'Editorial Header', 'slug' => 'editorial-header', 'template_slug' => 'header-centered-minimal', 'role' => 'header', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => 'footer', 'title' => 'Editorial Footer', 'slug' => 'editorial-footer', 'template_slug' => 'footer-minimal-legal', 'role' => 'footer', 'scope' => 'entire_site', 'priority' => 100],
                    ['key' => '404', 'title' => 'Editorial 404', 'slug' => 'editorial-404', 'template_slug' => '404-search-rescue', 'role' => '404', 'scope' => '404', 'priority' => 100],
                ],
            ],
        ];

        $filtered = apply_filters('h4bs_builder_site_kits', $kits, '1.0');
        $source = is_array($filtered) ? $filtered : $kits;
        $presetMap = $this->templatePresetMap();
        $catalog = [];
        $seen = [];
        foreach ($source as $kit) {
            if (! is_array($kit)) {
                continue;
            }
            $slug = sanitize_key((string) ($kit['slug'] ?? ''));
            if ($slug === '' || isset($seen[$slug])) {
                continue;
            }
            $seen[$slug] = true;
            $pages = $this->sanitizeSiteKitItems((array) ($kit['pages'] ?? []), $presetMap, false);
            $templates = $this->sanitizeSiteKitItems((array) ($kit['templates'] ?? []), $presetMap, true);
            if ($pages === [] || $templates === []) {
                continue;
            }
            $themePackSlug = sanitize_key((string) ($kit['theme_pack'] ?? ''));
            if ($this->findThemePack($themePackSlug) === null) {
                $themePackSlug = '';
            }
            $catalog[] = [
                'schema_version' => '1.0',
                'slug' => $slug,
                'label' => sanitize_text_field((string) ($kit['label'] ?? $slug)),
                'industry' => sanitize_text_field((string) ($kit['industry'] ?? __('General', 'help4-builder-suite'))),
                'description' => sanitize_text_field((string) ($kit['description'] ?? '')),
                'theme_pack' => $themePackSlug,
                'features' => array_values(array_filter(array_map('sanitize_text_field', (array) ($kit['features'] ?? [])))),
                'pages' => $pages,
                'templates' => $templates,
            ];
        }
        return array_slice($catalog, 0, 50);
    }

    /**
     * @param array<int, mixed> $items
     * @param array<string, array<string, mixed>> $presetMap
     * @return array<int, array<string, mixed>>
     */
    private function sanitizeSiteKitItems(array $items, array $presetMap, bool $isTemplate): array
    {
        $clean = [];
        $seen = [];
        foreach ($items as $item) {
            if (! is_array($item)) {
                continue;
            }
            $key = sanitize_key((string) ($item['key'] ?? ''));
            $templateSlug = sanitize_key((string) ($item['template_slug'] ?? ''));
            if ($key === '' || isset($seen[$key]) || ! isset($presetMap[$templateSlug])) {
                continue;
            }
            $seen[$key] = true;
            $next = [
                'key' => $key,
                'title' => sanitize_text_field((string) ($item['title'] ?? $key)),
                'slug' => sanitize_title((string) ($item['slug'] ?? $key)),
                'template_slug' => $templateSlug,
                'page_layout' => $this->normalizePageLayout(
                    (string) ($item['page_layout'] ?? ($presetMap[$templateSlug]['page_layout'] ?? 'full_width'))
                ),
            ];
            if ($isTemplate) {
                $role = sanitize_key((string) ($item['role'] ?? 'general'));
                $scope = sanitize_key((string) ($item['scope'] ?? 'entire_site'));
                $next['role'] = array_key_exists($role, $this->templateRoleCatalog()) ? $role : 'general';
                $next['scope'] = array_key_exists($scope, $this->templateScopeCatalog()) ? $scope : 'entire_site';
                $next['priority'] = max(-100, min(999, (int) ($item['priority'] ?? 10)));
            }
            $clean[] = $next;
        }
        return $clean;
    }

    /**
     * @return array<string, array<string, mixed>>
     */
    private function templatePresetMap(): array
    {
        $map = [];
        foreach ($this->templatePresets() as $preset) {
            if (! is_array($preset)) {
                continue;
            }
            $slug = sanitize_key((string) ($preset['slug'] ?? ''));
            if ($slug !== '' && ! isset($map[$slug])) {
                $map[$slug] = $preset;
            }
        }
        return $map;
    }

    /**
     * @return array<string, mixed>|null
     */
    private function findSiteKit(string $slug): ?array
    {
        $needle = sanitize_key($slug);
        if ($needle === '') {
            return null;
        }
        foreach ($this->siteKitCatalog() as $kit) {
            if ((string) ($kit['slug'] ?? '') === $needle) {
                return $kit;
            }
        }
        return null;
    }

    /**
     * Art-directed layouts built from native Builder blocks so every section
     * remains editable after the preset is applied.
     *
     * @return array<int, array<string, mixed>>
     */
    private function designTemplatePresets(): array
    {
        return [
            [
                'slug' => 'handled-wordpress-service',
                'label' => __('Handled WordPress Service', 'help4-builder-suite'),
                'category' => __('Managed WordPress', 'help4-builder-suite'),
                'description' => __('Editorial service page with a two-column hero, outcome band, plan path, FAQ, and closing action.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'hws_hero',
                        'type' => 'section',
                        'settings' => [
                            'columns' => 2,
                            'column_widths' => '46|54',
                            'gap' => 48,
                            'min_height' => 520,
                            'content_width_mode' => 'custom',
                            'content_width' => 1240,
                            'content_width_unit' => 'px',
                            'align_items' => 'center',
                            'design_background_color' => '#ffffff',
                            'design_padding_top' => 72,
                            'design_padding_right' => 32,
                            'design_padding_bottom' => 72,
                            'design_padding_left' => 32,
                        ],
                    ],
                    [
                        'id' => 'hws_heading',
                        'type' => 'heading',
                        'parent_id' => 'hws_hero',
                        'slot' => 0,
                        'settings' => [
                            'text' => 'WordPress handled. Business moving.',
                            'tag' => 'h1',
                            'design_text_color' => '#081a38',
                            'design_font_size' => 62,
                            'design_font_size_unit' => 'px',
                            'design_font_weight' => '800',
                            'design_line_height' => '1.04',
                            'design_line_height_unit' => '',
                            'design_margin_bottom' => 20,
                        ],
                    ],
                    [
                        'id' => 'hws_intro',
                        'type' => 'text',
                        'parent_id' => 'hws_hero',
                        'slot' => 0,
                        'settings' => [
                            'text' => 'Hosting, updates, security, speed, backups, and real support without making you become the WordPress department.',
                            'design_text_color' => '#334155',
                            'design_font_size' => 19,
                            'design_font_size_unit' => 'px',
                            'design_line_height' => '1.65',
                            'design_line_height_unit' => '',
                            'design_max_width' => 560,
                            'design_max_width_unit' => 'px',
                            'design_margin_bottom' => 24,
                        ],
                    ],
                    [
                        'id' => 'hws_action',
                        'type' => 'button',
                        'parent_id' => 'hws_hero',
                        'slot' => 0,
                        'settings' => [
                            'label' => 'Compare plans',
                            'url' => '#plans',
                            'design_background_color' => '#155eef',
                            'design_text_color' => '#ffffff',
                            'design_padding_top' => 14,
                            'design_padding_right' => 22,
                            'design_padding_bottom' => 14,
                            'design_padding_left' => 22,
                            'design_border_radius' => 4,
                            'design_border_radius_unit' => 'px',
                        ],
                    ],
                    [
                        'id' => 'hws_health',
                        'type' => 'cta',
                        'parent_id' => 'hws_hero',
                        'slot' => 1,
                        'settings' => [
                            'title' => 'Your site health, handled',
                            'text' => 'Core current. Backups verified. Security monitored. Performance watched. A real support path when something changes.',
                            'button_label' => 'See what is included',
                            'button_url' => '#outcomes',
                            'design_background_color' => '#081a38',
                            'design_text_color' => '#ffffff',
                            'design_padding_top' => 44,
                            'design_padding_right' => 40,
                            'design_padding_bottom' => 44,
                            'design_padding_left' => 40,
                            'design_border_radius' => 6,
                            'design_border_radius_unit' => 'px',
                            'design_box_shadow' => '0 24px 60px rgba(8,26,56,.18)',
                        ],
                    ],
                    [
                        'id' => 'hws_outcomes',
                        'type' => 'section',
                        'settings' => [
                            'columns' => 3,
                            'gap' => 0,
                            'min_height' => 260,
                            'content_width_mode' => 'custom',
                            'content_width' => 1180,
                            'content_width_unit' => 'px',
                            'design_background_color' => '#f7f9fc',
                            'design_padding_top' => 56,
                            'design_padding_right' => 32,
                            'design_padding_bottom' => 56,
                            'design_padding_left' => 32,
                        ],
                    ],
                    [
                        'id' => 'hws_secure',
                        'type' => 'cta',
                        'parent_id' => 'hws_outcomes',
                        'slot' => 0,
                        'settings' => ['title' => 'Stay secure', 'text' => 'Updates, hardening, monitoring, and recovery planning in one clear path.', 'button_label' => 'Security details', 'button_url' => '#security', 'design_background_color' => '#f7f9fc', 'design_text_color' => '#081a38', 'design_padding_top' => 24, 'design_padding_right' => 28, 'design_padding_bottom' => 24, 'design_padding_left' => 28],
                    ],
                    [
                        'id' => 'hws_fast',
                        'type' => 'cta',
                        'parent_id' => 'hws_outcomes',
                        'slot' => 1,
                        'settings' => ['title' => 'Keep it fast', 'text' => 'Performance checks focus on the pages people actually use and buy from.', 'button_label' => 'Performance details', 'button_url' => '#performance', 'design_background_color' => '#ffffff', 'design_text_color' => '#081a38', 'design_padding_top' => 24, 'design_padding_right' => 28, 'design_padding_bottom' => 24, 'design_padding_left' => 28],
                    ],
                    [
                        'id' => 'hws_support',
                        'type' => 'cta',
                        'parent_id' => 'hws_outcomes',
                        'slot' => 2,
                        'settings' => ['title' => 'Get real support', 'text' => 'Send the problem, recent change, and screenshot. We take it from there.', 'button_label' => 'Support path', 'button_url' => '#support', 'design_background_color' => '#f7f9fc', 'design_text_color' => '#081a38', 'design_padding_top' => 24, 'design_padding_right' => 28, 'design_padding_bottom' => 24, 'design_padding_left' => 28],
                    ],
                    [
                        'id' => 'hws_plans',
                        'type' => 'section',
                        'settings' => ['columns' => 2, 'column_widths' => '34|66', 'gap' => 48, 'min_height' => 390, 'content_width_mode' => 'custom', 'content_width' => 1180, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color' => '#ffffff', 'design_padding_top' => 72, 'design_padding_right' => 32, 'design_padding_bottom' => 72, 'design_padding_left' => 32],
                    ],
                    [
                        'id' => 'hws_plan_copy',
                        'type' => 'cta',
                        'parent_id' => 'hws_plans',
                        'slot' => 0,
                        'settings' => ['title' => 'Simple plans. Clear value.', 'text' => 'Choose by how much you want handled today. Move up when traffic, revenue, or risk grows.', 'button_label' => 'Talk through the options', 'button_url' => '#contact', 'design_background_color' => '#081a38', 'design_text_color' => '#ffffff', 'design_padding_top' => 38, 'design_padding_right' => 34, 'design_padding_bottom' => 38, 'design_padding_left' => 34, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px'],
                    ],
                    [
                        'id' => 'hws_plan_table',
                        'type' => 'shortcode',
                        'parent_id' => 'hws_plans',
                        'slot' => 1,
                        'settings' => ['shortcode' => '[h4_pricing_table plan="starter"]'],
                    ],
                    [
                        'id' => 'hws_faq',
                        'type' => 'faq',
                        'settings' => [
                            'items' => 'What is managed WordPress?::It means the hosting, routine maintenance, backups, security checks, and support path are handled together.\nWill you update plugins and themes?::Yes. Updates should be tested, applied, and checked instead of clicked blindly.\nWhat happens if something breaks?::Use the support route with the site URL, impact, screenshot, and latest change.\nCan I move my existing site?::Yes. Migration planning should cover files, database, DNS, SSL, forms, email, and checkout paths.',
                            'open_first' => '1',
                            'design_background_color' => '#f7f9fc',
                            'design_text_color' => '#081a38',
                            'design_padding_top' => 64,
                            'design_padding_right' => 32,
                            'design_padding_bottom' => 64,
                            'design_padding_left' => 32,
                        ],
                    ],
                    [
                        'id' => 'hws_close',
                        'type' => 'cta',
                        'settings' => ['title' => 'Let us handle WordPress. You handle your business.', 'text' => 'Start with the plan that fits now, then keep one support path as the site grows.', 'button_label' => 'Compare plans', 'button_url' => '#plans', 'design_background_color' => '#081a38', 'design_text_color' => '#ffffff', 'design_padding_top' => 64, 'design_padding_right' => 48, 'design_padding_bottom' => 64, 'design_padding_left' => 48],
                    ],
                ],
            ],
            [
                'slug' => 'catalog-command-store',
                'label' => __('Catalog Command Store', 'help4-builder-suite'),
                'category' => __('Native Commerce', 'help4-builder-suite'),
                'description' => __('Operational commerce page with buyer guidance, dense catalog controls, checkout handoff, and support.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'cc_intro',
                        'type' => 'section',
                        'settings' => ['columns' => 2, 'column_widths' => '58|42', 'gap' => 44, 'min_height' => 330, 'content_width_mode' => 'custom', 'content_width' => 1440, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color' => '#ffffff', 'design_padding_top' => 64, 'design_padding_right' => 28, 'design_padding_bottom' => 48, 'design_padding_left' => 28],
                    ],
                    [
                        'id' => 'cc_heading',
                        'type' => 'heading',
                        'parent_id' => 'cc_intro',
                        'slot' => 0,
                        'settings' => ['text' => 'Find the right product without the hunt.', 'tag' => 'h1', 'design_text_color' => '#111827', 'design_font_size' => 54, 'design_font_size_unit' => 'px', 'design_font_weight' => '800', 'design_line_height' => '1.05', 'design_line_height_unit' => '', 'design_margin_bottom' => 18],
                    ],
                    [
                        'id' => 'cc_text',
                        'type' => 'text',
                        'parent_id' => 'cc_intro',
                        'slot' => 0,
                        'settings' => ['text' => 'Filter large catalogs, select variations, compare products, choose one-time or recurring delivery, and move into checkout without backend jargon.', 'design_text_color' => '#475569', 'design_font_size' => 18, 'design_font_size_unit' => 'px', 'design_line_height' => '1.6', 'design_line_height_unit' => '', 'design_margin_bottom' => 22],
                    ],
                    [
                        'id' => 'cc_button',
                        'type' => 'button',
                        'parent_id' => 'cc_intro',
                        'slot' => 0,
                        'settings' => ['label' => 'Browse catalog', 'url' => '#catalog', 'design_background_color' => '#155eef', 'design_text_color' => '#ffffff', 'design_padding_top' => 14, 'design_padding_right' => 22, 'design_padding_bottom' => 14, 'design_padding_left' => 22, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px'],
                    ],
                    [
                        'id' => 'cc_guide',
                        'type' => 'list',
                        'parent_id' => 'cc_intro',
                        'slot' => 1,
                        'settings' => ['items' => "Built for high-volume catalogs\nVariation choices stay visible\nRecurring and one-time orders\nRule-based processor routing", 'ordered' => '0', 'design_background_color' => '#f6f8fb', 'design_text_color' => '#111827', 'design_padding_top' => 32, 'design_padding_right' => 34, 'design_padding_bottom' => 32, 'design_padding_left' => 34, 'design_border_width' => 1, 'design_border_width_unit' => 'px', 'design_border_style' => 'solid', 'design_border_color' => '#dbe2ea', 'design_border_radius' => 4, 'design_border_radius_unit' => 'px'],
                    ],
                    [
                        'id' => 'cc_catalog',
                        'type' => 'shortcode',
                        'settings' => ['shortcode' => '[h4_products limit="24" card_style="clean" hover="quick_actions" show_filters="1" filter_style="drawer" show_quick_view="1" show_compare="1" show_price_watch="1" show_waitlist="1" cursor="1"]', 'design_background_color' => '#ffffff', 'design_padding_top' => 28, 'design_padding_right' => 20, 'design_padding_bottom' => 48, 'design_padding_left' => 20],
                    ],
                    [
                        'id' => 'cc_features',
                        'type' => 'section',
                        'settings' => ['columns' => 4, 'gap' => 1, 'min_height' => 220, 'content_width_mode' => 'full', 'design_background_color' => '#dbe2ea', 'design_padding_top' => 1, 'design_padding_right' => 0, 'design_padding_bottom' => 1, 'design_padding_left' => 0],
                    ],
                    [
                        'id' => 'cc_variants',
                        'type' => 'cta',
                        'parent_id' => 'cc_features',
                        'slot' => 0,
                        'settings' => ['title' => 'Smart variations', 'text' => 'Show size, color, configuration, and availability where the customer decides.', 'button_label' => 'Variation guide', 'button_url' => '#variations', 'design_background_color' => '#ffffff', 'design_text_color' => '#111827', 'design_padding_top' => 30, 'design_padding_right' => 26, 'design_padding_bottom' => 30, 'design_padding_left' => 26],
                    ],
                    [
                        'id' => 'cc_recurring',
                        'type' => 'cta',
                        'parent_id' => 'cc_features',
                        'slot' => 1,
                        'settings' => ['title' => 'Recurring orders', 'text' => 'Explain frequency, savings, renewal behavior, and account changes before checkout.', 'button_label' => 'Subscription guide', 'button_url' => '#subscriptions', 'design_background_color' => '#ffffff', 'design_text_color' => '#111827', 'design_padding_top' => 30, 'design_padding_right' => 26, 'design_padding_bottom' => 30, 'design_padding_left' => 26],
                    ],
                    [
                        'id' => 'cc_routing',
                        'type' => 'cta',
                        'parent_id' => 'cc_features',
                        'slot' => 2,
                        'settings' => ['title' => 'Processor routing', 'text' => 'Send transactions to the configured processor using clear rules and fallback paths.', 'button_label' => 'Routing guide', 'button_url' => '#routing', 'design_background_color' => '#ffffff', 'design_text_color' => '#111827', 'design_padding_top' => 30, 'design_padding_right' => 26, 'design_padding_bottom' => 30, 'design_padding_left' => 26],
                    ],
                    [
                        'id' => 'cc_scale',
                        'type' => 'cta',
                        'parent_id' => 'cc_features',
                        'slot' => 3,
                        'settings' => ['title' => 'Built for scale', 'text' => 'Keep catalog queries, product cards, and paging disciplined as inventory grows.', 'button_label' => 'Performance guide', 'button_url' => '#performance', 'design_background_color' => '#ffffff', 'design_text_color' => '#111827', 'design_padding_top' => 30, 'design_padding_right' => 26, 'design_padding_bottom' => 30, 'design_padding_left' => 26],
                    ],
                    [
                        'id' => 'cc_checkout',
                        'type' => 'section',
                        'settings' => ['columns' => 2, 'column_widths' => '64|36', 'gap' => 36, 'min_height' => 360, 'content_width_mode' => 'custom', 'content_width' => 1280, 'content_width_unit' => 'px', 'align_items' => 'start', 'design_background_color' => '#f6f8fb', 'design_padding_top' => 64, 'design_padding_right' => 28, 'design_padding_bottom' => 64, 'design_padding_left' => 28],
                    ],
                    [
                        'id' => 'cc_cart',
                        'type' => 'shortcode',
                        'parent_id' => 'cc_checkout',
                        'slot' => 0,
                        'settings' => ['shortcode' => '[h4_checkout]'],
                    ],
                    [
                        'id' => 'cc_help',
                        'type' => 'cta',
                        'parent_id' => 'cc_checkout',
                        'slot' => 1,
                        'settings' => ['title' => 'Need help finding something?', 'text' => 'Route product questions, stock problems, payment trouble, and account issues into a real support path.', 'button_label' => 'Contact support', 'button_url' => '#contact', 'design_background_color' => '#081a38', 'design_text_color' => '#ffffff', 'design_padding_top' => 38, 'design_padding_right' => 34, 'design_padding_bottom' => 38, 'design_padding_left' => 34, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px'],
                    ],
                ],
            ],
            [
                'slug' => 'proof-studio-portfolio',
                'label' => __('Proof Studio Portfolio', 'help4-builder-suite'),
                'category' => __('Agency + Portfolio', 'help4-builder-suite'),
                'description' => __('Screenshot-led portfolio with editorial project proof, process, testimonial, and project inquiry.', 'help4-builder-suite'),
                'layout' => [
                    [
                        'id' => 'ps_hero',
                        'type' => 'section',
                        'settings' => ['columns' => 2, 'column_widths' => '42|58', 'gap' => 44, 'min_height' => 620, 'content_width_mode' => 'custom', 'content_width' => 1320, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color' => '#ffffff', 'design_padding_top' => 72, 'design_padding_right' => 28, 'design_padding_bottom' => 72, 'design_padding_left' => 28],
                    ],
                    [
                        'id' => 'ps_heading',
                        'type' => 'heading',
                        'parent_id' => 'ps_hero',
                        'slot' => 0,
                        'settings' => ['text' => 'Websites built from the proof you bring.', 'tag' => 'h1', 'design_text_color' => '#0b0d12', 'design_font_size' => 68, 'design_font_size_unit' => 'px', 'design_font_weight' => '800', 'design_line_height' => '.98', 'design_line_height_unit' => '', 'design_margin_bottom' => 22],
                    ],
                    [
                        'id' => 'ps_intro',
                        'type' => 'text',
                        'parent_id' => 'ps_hero',
                        'slot' => 0,
                        'settings' => ['text' => 'Bring designs, screenshots, copy, or a working reference. Turn that proof into a fast, accessible, responsive WordPress build.', 'design_text_color' => '#525866', 'design_font_size' => 18, 'design_font_size_unit' => 'px', 'design_line_height' => '1.65', 'design_line_height_unit' => '', 'design_margin_bottom' => 24],
                    ],
                    [
                        'id' => 'ps_action',
                        'type' => 'button',
                        'parent_id' => 'ps_hero',
                        'slot' => 0,
                        'settings' => ['label' => 'Start a project', 'url' => '#project', 'design_background_color' => '#155eef', 'design_text_color' => '#ffffff', 'design_padding_top' => 14, 'design_padding_right' => 22, 'design_padding_bottom' => 14, 'design_padding_left' => 22, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px'],
                    ],
                    [
                        'id' => 'ps_snapshot',
                        'type' => 'shortcode',
                        'parent_id' => 'ps_hero',
                        'slot' => 1,
                        'settings' => ['shortcode' => '[h4_website_snapshot url="https://example.com" width="960" height="640" fit="cover"]', 'design_background_color' => '#0b0d12', 'design_padding_top' => 16, 'design_padding_right' => 16, 'design_padding_bottom' => 16, 'design_padding_left' => 16, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px', 'design_box_shadow' => '0 24px 70px rgba(11,13,18,.2)'],
                    ],
                    [
                        'id' => 'ps_work',
                        'type' => 'section',
                        'settings' => ['columns' => 2, 'column_widths' => '30|70', 'gap' => 54, 'min_height' => 480, 'content_width_mode' => 'custom', 'content_width' => 1320, 'content_width_unit' => 'px', 'align_items' => 'start', 'design_background_color' => '#0b0d12', 'design_padding_top' => 72, 'design_padding_right' => 28, 'design_padding_bottom' => 72, 'design_padding_left' => 28],
                    ],
                    [
                        'id' => 'ps_work_intro',
                        'type' => 'cta',
                        'parent_id' => 'ps_work',
                        'slot' => 0,
                        'settings' => ['title' => 'Recent work', 'text' => 'Real projects, useful context, and visible outcomes. Replace the sample rows with work people can inspect.', 'button_label' => 'View all projects', 'button_url' => '#work', 'design_background_color' => '#0b0d12', 'design_text_color' => '#ffffff', 'design_padding_top' => 12, 'design_padding_right' => 8, 'design_padding_bottom' => 12, 'design_padding_left' => 8],
                    ],
                    [
                        'id' => 'ps_work_list',
                        'type' => 'list',
                        'parent_id' => 'ps_work',
                        'slot' => 1,
                        'settings' => ['items' => "01  Design handoff to responsive service site\n02  Product redesign with native commerce\n03  Elementor migration to clean Builder templates\n04  Website Snapshot portfolio and recent-work proof", 'ordered' => '0', 'design_background_color' => '#0b0d12', 'design_text_color' => '#ffffff', 'design_font_size' => 20, 'design_font_size_unit' => 'px', 'design_line_height' => '2.25', 'design_line_height_unit' => '', 'design_border_width' => 1, 'design_border_width_unit' => 'px', 'design_border_style' => 'solid', 'design_border_color' => '#30343b', 'design_padding_top' => 24, 'design_padding_right' => 28, 'design_padding_bottom' => 24, 'design_padding_left' => 28],
                    ],
                    [
                        'id' => 'ps_process',
                        'type' => 'section',
                        'settings' => ['columns' => 3, 'gap' => 28, 'min_height' => 340, 'content_width_mode' => 'custom', 'content_width' => 1240, 'content_width_unit' => 'px', 'design_background_color' => '#ffffff', 'design_padding_top' => 80, 'design_padding_right' => 28, 'design_padding_bottom' => 80, 'design_padding_left' => 28],
                    ],
                    [
                        'id' => 'ps_step_one',
                        'type' => 'cta',
                        'parent_id' => 'ps_process',
                        'slot' => 0,
                        'settings' => ['title' => '01  Design handoff', 'text' => 'Send designs, screenshots, content, references, and the must-keep behavior.', 'button_label' => 'Prepare the handoff', 'button_url' => '#handoff', 'design_background_color' => '#f7f9fc', 'design_text_color' => '#0b0d12', 'design_padding_top' => 32, 'design_padding_right' => 28, 'design_padding_bottom' => 32, 'design_padding_left' => 28, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px'],
                    ],
                    [
                        'id' => 'ps_step_two',
                        'type' => 'cta',
                        'parent_id' => 'ps_process',
                        'slot' => 1,
                        'settings' => ['title' => '02  Responsive build', 'text' => 'Build the structure, interactions, templates, forms, and responsive behavior.', 'button_label' => 'Review the build', 'button_url' => '#build', 'design_background_color' => '#eefbf5', 'design_text_color' => '#0b0d12', 'design_padding_top' => 32, 'design_padding_right' => 28, 'design_padding_bottom' => 32, 'design_padding_left' => 28, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px'],
                    ],
                    [
                        'id' => 'ps_step_three',
                        'type' => 'cta',
                        'parent_id' => 'ps_process',
                        'slot' => 2,
                        'settings' => ['title' => '03  Launch and support', 'text' => 'Test public routes, mobile widths, forms, SEO output, and the update path.', 'button_label' => 'Plan the launch', 'button_url' => '#launch', 'design_background_color' => '#fff3ee', 'design_text_color' => '#0b0d12', 'design_padding_top' => 32, 'design_padding_right' => 28, 'design_padding_bottom' => 32, 'design_padding_left' => 28, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px'],
                    ],
                    [
                        'id' => 'ps_quote',
                        'type' => 'quote',
                        'settings' => ['text' => 'They took the designs and made them better in the browser. The build is clean, fast, and easy to manage.', 'cite' => 'Project owner', 'design_background_color' => '#f7f9fc', 'design_text_color' => '#0b0d12', 'design_font_size' => 30, 'design_font_size_unit' => 'px', 'design_font_weight' => '700', 'design_line_height' => '1.35', 'design_line_height_unit' => '', 'design_padding_top' => 72, 'design_padding_right' => 48, 'design_padding_bottom' => 72, 'design_padding_left' => 48],
                    ],
                    [
                        'id' => 'ps_close',
                        'type' => 'cta',
                        'settings' => ['title' => 'Have a design ready?', 'text' => 'Send the files, screenshots, working URL, and the pages that matter most. Start with the proof you already have.', 'button_label' => 'Start a project', 'button_url' => '#project', 'design_background_color' => '#155eef', 'design_text_color' => '#ffffff', 'design_padding_top' => 56, 'design_padding_right' => 48, 'design_padding_bottom' => 56, 'design_padding_left' => 48],
                    ],
                ],
            ],
            [
                'slug' => 'local-service-command',
                'label' => __('Local Service Command', 'help4-builder-suite'),
                'category' => __('Contractor + Trades', 'help4-builder-suite'),
                'description' => __('Quote-first local service layout with emergency routing, service proof, process, reviews, and area coverage.', 'help4-builder-suite'),
                'layout' => [
                    ['id' => 'lsc_hero', 'type' => 'section', 'settings' => ['columns' => 2, 'column_widths' => '56|44', 'gap' => 40, 'min_height' => 520, 'content_width_mode' => 'custom', 'content_width' => 1260, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color' => '#101820', 'design_padding_top' => 70, 'design_padding_right' => 30, 'design_padding_bottom' => 70, 'design_padding_left' => 30]],
                    ['id' => 'lsc_heading', 'type' => 'heading', 'parent_id' => 'lsc_hero', 'slot' => 0, 'settings' => ['text' => 'Local service without the runaround.', 'tag' => 'h1', 'design_text_color' => '#ffffff', 'design_font_size' => 60, 'design_font_size_unit' => 'px', 'design_font_weight' => '800', 'design_line_height' => '1.03', 'design_line_height_unit' => '', 'design_margin_bottom' => 18]],
                    ['id' => 'lsc_text', 'type' => 'text', 'parent_id' => 'lsc_hero', 'slot' => 0, 'settings' => ['text' => 'Show the work, explain the service area, collect the right project details, and give urgent customers a clear next step.', 'design_text_color' => '#d8e1ea', 'design_font_size' => 19, 'design_font_size_unit' => 'px', 'design_line_height' => '1.6', 'design_line_height_unit' => '', 'design_margin_bottom' => 24]],
                    ['id' => 'lsc_button', 'type' => 'button', 'parent_id' => 'lsc_hero', 'slot' => 0, 'settings' => ['label' => 'Request an estimate', 'url' => '#estimate', 'design_background_color' => '#ff6b35', 'design_text_color' => '#101820', 'design_padding_top' => 14, 'design_padding_right' => 22, 'design_padding_bottom' => 14, 'design_padding_left' => 22, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'lsc_form', 'type' => 'shortcode', 'parent_id' => 'lsc_hero', 'slot' => 1, 'settings' => ['shortcode' => '[h4_quote_request id="1"]', 'design_background_color' => '#ffffff', 'design_padding_top' => 30, 'design_padding_right' => 30, 'design_padding_bottom' => 30, 'design_padding_left' => 30, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'lsc_services', 'type' => 'section', 'settings' => ['columns' => 3, 'gap' => 18, 'min_height' => 320, 'content_width_mode' => 'custom', 'content_width' => 1200, 'content_width_unit' => 'px', 'design_background_color' => '#ffffff', 'design_padding_top' => 72, 'design_padding_right' => 30, 'design_padding_bottom' => 72, 'design_padding_left' => 30]],
                    ['id' => 'lsc_service_one', 'type' => 'cta', 'parent_id' => 'lsc_services', 'slot' => 0, 'settings' => ['title' => 'Repair', 'text' => 'Explain symptoms, response windows, and what the first visit covers.', 'button_label' => 'Repair service', 'button_url' => '#repair', 'design_background_color' => '#f4f7f9', 'design_text_color' => '#101820', 'design_padding_top' => 30, 'design_padding_right' => 28, 'design_padding_bottom' => 30, 'design_padding_left' => 28, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'lsc_service_two', 'type' => 'cta', 'parent_id' => 'lsc_services', 'slot' => 1, 'settings' => ['title' => 'Replace', 'text' => 'Set expectations for inspection, options, estimate, scheduling, and cleanup.', 'button_label' => 'Replacement service', 'button_url' => '#replace', 'design_background_color' => '#f4f7f9', 'design_text_color' => '#101820', 'design_padding_top' => 30, 'design_padding_right' => 28, 'design_padding_bottom' => 30, 'design_padding_left' => 28, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'lsc_service_three', 'type' => 'cta', 'parent_id' => 'lsc_services', 'slot' => 2, 'settings' => ['title' => 'Maintain', 'text' => 'Turn recurring visits into a simple plan customers understand before booking.', 'button_label' => 'Maintenance plan', 'button_url' => '#maintain', 'design_background_color' => '#f4f7f9', 'design_text_color' => '#101820', 'design_padding_top' => 30, 'design_padding_right' => 28, 'design_padding_bottom' => 30, 'design_padding_left' => 28, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'lsc_proof', 'type' => 'section', 'settings' => ['columns' => 2, 'column_widths' => '52|48', 'gap' => 42, 'min_height' => 420, 'content_width_mode' => 'custom', 'content_width' => 1200, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color' => '#f4f7f9', 'design_padding_top' => 64, 'design_padding_right' => 30, 'design_padding_bottom' => 64, 'design_padding_left' => 30]],
                    ['id' => 'lsc_reviews', 'type' => 'shortcode', 'parent_id' => 'lsc_proof', 'slot' => 0, 'settings' => ['shortcode' => '[h4_testimonials limit="5"]']],
                    ['id' => 'lsc_map', 'type' => 'shortcode', 'parent_id' => 'lsc_proof', 'slot' => 1, 'settings' => ['shortcode' => '[h4_service_area_map]']],
                    ['id' => 'lsc_close', 'type' => 'cta', 'settings' => ['title' => 'Tell us what needs attention.', 'text' => 'Share the address or service area, what happened, when it started, and a photo when possible.', 'button_label' => 'Request an estimate', 'button_url' => '#estimate', 'design_background_color' => '#ff6b35', 'design_text_color' => '#101820', 'design_padding_top' => 56, 'design_padding_right' => 44, 'design_padding_bottom' => 56, 'design_padding_left' => 44]],
                ],
            ],
            [
                'slug' => 'visual-documentation-hub',
                'label' => __('Visual Documentation Hub', 'help4-builder-suite'),
                'category' => __('Documentation', 'help4-builder-suite'),
                'description' => __('Screenshot-first help center with search, guided setup paths, common fixes, and escalation.', 'help4-builder-suite'),
                'layout' => [
                    ['id' => 'vdh_hero', 'type' => 'section', 'settings' => ['columns' => 2, 'column_widths' => '54|46', 'gap' => 42, 'min_height' => 420, 'content_width_mode' => 'custom', 'content_width' => 1220, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color' => '#eef5ff', 'design_padding_top' => 68, 'design_padding_right' => 30, 'design_padding_bottom' => 68, 'design_padding_left' => 30]],
                    ['id' => 'vdh_heading', 'type' => 'heading', 'parent_id' => 'vdh_hero', 'slot' => 0, 'settings' => ['text' => 'Show the screen. Explain the next click.', 'tag' => 'h1', 'design_text_color' => '#081a38', 'design_font_size' => 56, 'design_font_size_unit' => 'px', 'design_font_weight' => '800', 'design_line_height' => '1.04', 'design_line_height_unit' => '', 'design_margin_bottom' => 18]],
                    ['id' => 'vdh_text', 'type' => 'text', 'parent_id' => 'vdh_hero', 'slot' => 0, 'settings' => ['text' => 'Build tutorials for people who need the picture, the exact control name, and the result they should expect.', 'design_text_color' => '#3d4b63', 'design_font_size' => 18, 'design_font_size_unit' => 'px', 'design_line_height' => '1.65', 'design_line_height_unit' => '']],
                    ['id' => 'vdh_search', 'type' => 'shortcode', 'parent_id' => 'vdh_hero', 'slot' => 1, 'settings' => ['shortcode' => '[h4_search type="all" placeholder="What are you trying to set up?"]', 'design_background_color' => '#ffffff', 'design_padding_top' => 28, 'design_padding_right' => 28, 'design_padding_bottom' => 28, 'design_padding_left' => 28, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'vdh_paths', 'type' => 'section', 'settings' => ['columns' => 3, 'gap' => 20, 'min_height' => 320, 'content_width_mode' => 'custom', 'content_width' => 1180, 'content_width_unit' => 'px', 'design_background_color' => '#ffffff', 'design_padding_top' => 72, 'design_padding_right' => 30, 'design_padding_bottom' => 72, 'design_padding_left' => 30]],
                    ['id' => 'vdh_start', 'type' => 'cta', 'parent_id' => 'vdh_paths', 'slot' => 0, 'settings' => ['title' => 'Start here', 'text' => 'Installation, companion theme, first page, global styles, and save/publish.', 'button_label' => 'Open setup path', 'button_url' => '#setup', 'design_background_color' => '#f7f9fc', 'design_text_color' => '#081a38', 'design_padding_top' => 30, 'design_padding_right' => 28, 'design_padding_bottom' => 30, 'design_padding_left' => 28, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'vdh_build', 'type' => 'cta', 'parent_id' => 'vdh_paths', 'slot' => 1, 'settings' => ['title' => 'Build pages', 'text' => 'Templates, elements, nested sections, responsive controls, images, menus, and forms.', 'button_label' => 'Open builder path', 'button_url' => '#builder', 'design_background_color' => '#eefbf5', 'design_text_color' => '#081a38', 'design_padding_top' => 30, 'design_padding_right' => 28, 'design_padding_bottom' => 30, 'design_padding_left' => 28, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'vdh_launch', 'type' => 'cta', 'parent_id' => 'vdh_paths', 'slot' => 2, 'settings' => ['title' => 'Launch safely', 'text' => 'Mobile QA, SEO output, forms, commerce, backups, updates, and support.', 'button_label' => 'Open launch path', 'button_url' => '#launch', 'design_background_color' => '#fff3ee', 'design_text_color' => '#081a38', 'design_padding_top' => 30, 'design_padding_right' => 28, 'design_padding_bottom' => 30, 'design_padding_left' => 28, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'vdh_steps', 'type' => 'section', 'settings' => ['columns' => 2, 'column_widths' => '58|42', 'gap' => 40, 'min_height' => 420, 'content_width_mode' => 'custom', 'content_width' => 1180, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color' => '#081a38', 'design_padding_top' => 68, 'design_padding_right' => 30, 'design_padding_bottom' => 68, 'design_padding_left' => 30]],
                    ['id' => 'vdh_gallery', 'type' => 'gallery', 'parent_id' => 'vdh_steps', 'slot' => 0, 'settings' => ['images' => '', 'columns' => 2, 'design_background_color' => '#13284b', 'design_padding_top' => 18, 'design_padding_right' => 18, 'design_padding_bottom' => 18, 'design_padding_left' => 18, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'vdh_checklist', 'type' => 'list', 'parent_id' => 'vdh_steps', 'slot' => 1, 'settings' => ['items' => "Use a real blank-install screenshot\nName the exact menu and control\nMark the click or changed field\nShow the expected result\nAdd the next safe step", 'ordered' => '1', 'design_background_color' => '#081a38', 'design_text_color' => '#ffffff', 'design_font_size' => 18, 'design_font_size_unit' => 'px', 'design_line_height' => '1.9', 'design_line_height_unit' => '']],
                    ['id' => 'vdh_faq', 'type' => 'faq', 'settings' => ['items' => 'Where are the theme files?::The companion blank theme is a separate theme package. Install it under Appearance, then use Builder Suite for the design system.\nWhy do my screenshots look different?::Confirm WordPress, Builder Suite, active theme, enabled modules, and viewport before comparing screens.\nWhat should I send support?::Send the page URL, screen name, exact action, expected result, actual result, screenshot, and recent change.', 'open_first' => '1', 'design_background_color' => '#f7f9fc', 'design_text_color' => '#081a38', 'design_padding_top' => 64, 'design_padding_right' => 30, 'design_padding_bottom' => 64, 'design_padding_left' => 30]],
                ],
            ],
            [
                'slug' => 'plugin-product-launch',
                'label' => __('Plugin Product Launch', 'help4-builder-suite'),
                'category' => __('Plugin Marketing', 'help4-builder-suite'),
                'description' => __('Product-led plugin page with screenshots, plain-language outcomes, compatibility, FAQ, and download path.', 'help4-builder-suite'),
                'layout' => [
                    ['id' => 'ppl_hero', 'type' => 'section', 'settings' => ['columns' => 2, 'column_widths' => '46|54', 'gap' => 46, 'min_height' => 560, 'content_width_mode' => 'custom', 'content_width' => 1280, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color' => '#07182f', 'design_padding_top' => 72, 'design_padding_right' => 30, 'design_padding_bottom' => 72, 'design_padding_left' => 30]],
                    ['id' => 'ppl_heading', 'type' => 'heading', 'parent_id' => 'ppl_hero', 'slot' => 0, 'settings' => ['text' => 'Build the site. Keep the stack understandable.', 'tag' => 'h1', 'design_text_color' => '#ffffff', 'design_font_size' => 58, 'design_font_size_unit' => 'px', 'design_font_weight' => '800', 'design_line_height' => '1.04', 'design_line_height_unit' => '', 'design_margin_bottom' => 20]],
                    ['id' => 'ppl_text', 'type' => 'text', 'parent_id' => 'ppl_hero', 'slot' => 0, 'settings' => ['text' => 'Show the builder, templates, fields, commerce, SEO, performance, and security modules through the jobs they solve.', 'design_text_color' => '#c8d7ea', 'design_font_size' => 19, 'design_font_size_unit' => 'px', 'design_line_height' => '1.65', 'design_line_height_unit' => '', 'design_margin_bottom' => 24]],
                    ['id' => 'ppl_download', 'type' => 'button', 'parent_id' => 'ppl_hero', 'slot' => 0, 'settings' => ['label' => 'Download the plugin', 'url' => '/help4-builder-suite/latest.zip', 'design_background_color' => '#2dd4bf', 'design_text_color' => '#07182f', 'design_padding_top' => 14, 'design_padding_right' => 22, 'design_padding_bottom' => 14, 'design_padding_left' => 22, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'ppl_gallery', 'type' => 'gallery', 'parent_id' => 'ppl_hero', 'slot' => 1, 'settings' => ['images' => '', 'columns' => 2, 'design_background_color' => '#102a4f', 'design_padding_top' => 18, 'design_padding_right' => 18, 'design_padding_bottom' => 18, 'design_padding_left' => 18, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'ppl_outcomes', 'type' => 'section', 'settings' => ['columns' => 3, 'gap' => 20, 'min_height' => 330, 'content_width_mode' => 'custom', 'content_width' => 1200, 'content_width_unit' => 'px', 'design_background_color' => '#ffffff', 'design_padding_top' => 72, 'design_padding_right' => 30, 'design_padding_bottom' => 72, 'design_padding_left' => 30]],
                    ['id' => 'ppl_build', 'type' => 'cta', 'parent_id' => 'ppl_outcomes', 'slot' => 0, 'settings' => ['title' => 'Start with a real layout', 'text' => 'Use nested presets and style packs instead of staring at three unstyled blocks.', 'button_label' => 'Browse templates', 'button_url' => '#templates', 'design_background_color' => '#eef5ff', 'design_text_color' => '#07182f', 'design_padding_top' => 32, 'design_padding_right' => 28, 'design_padding_bottom' => 32, 'design_padding_left' => 28, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'ppl_manage', 'type' => 'cta', 'parent_id' => 'ppl_outcomes', 'slot' => 1, 'settings' => ['title' => 'Enable only what you need', 'text' => 'Keep modules compartmentalized so every page does not carry every feature.', 'button_label' => 'Review modules', 'button_url' => '#modules', 'design_background_color' => '#eefbf5', 'design_text_color' => '#07182f', 'design_padding_top' => 32, 'design_padding_right' => 28, 'design_padding_bottom' => 32, 'design_padding_left' => 28, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'ppl_launch', 'type' => 'cta', 'parent_id' => 'ppl_outcomes', 'slot' => 2, 'settings' => ['title' => 'Launch with a checklist', 'text' => 'Review mobile, forms, checkout, SEO output, security, backups, and updates.', 'button_label' => 'Open launch guide', 'button_url' => '#launch', 'design_background_color' => '#fff3ee', 'design_text_color' => '#07182f', 'design_padding_top' => 32, 'design_padding_right' => 28, 'design_padding_bottom' => 32, 'design_padding_left' => 28, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'ppl_compat', 'type' => 'section', 'settings' => ['columns' => 2, 'column_widths' => '40|60', 'gap' => 42, 'min_height' => 360, 'content_width_mode' => 'custom', 'content_width' => 1160, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color' => '#f7f9fc', 'design_padding_top' => 64, 'design_padding_right' => 30, 'design_padding_bottom' => 64, 'design_padding_left' => 30]],
                    ['id' => 'ppl_compat_copy', 'type' => 'cta', 'parent_id' => 'ppl_compat', 'slot' => 0, 'settings' => ['title' => 'Built for current WordPress', 'text' => 'State the tested WordPress and PHP requirements clearly, then keep the update path visible.', 'button_label' => 'Read compatibility', 'button_url' => '#compatibility', 'design_background_color' => '#07182f', 'design_text_color' => '#ffffff', 'design_padding_top' => 34, 'design_padding_right' => 30, 'design_padding_bottom' => 34, 'design_padding_left' => 30, 'design_border_radius' => 4, 'design_border_radius_unit' => 'px']],
                    ['id' => 'ppl_faq', 'type' => 'faq', 'parent_id' => 'ppl_compat', 'slot' => 1, 'settings' => ['items' => 'Does it require the companion theme?::No. The plugin works with other themes, but the separate blank theme provides the cleanest Builder-first starting point.\nDoes every module load on every page?::No. Modules and assets should load only where their functionality is enabled and used.\nCan I use the templates on an existing site?::Yes. Apply a style pack alone, append a starter, or replace a draft layout after reviewing it.', 'open_first' => '1']],
                    ['id' => 'ppl_close', 'type' => 'cta', 'settings' => ['title' => 'Install it. Pick a design. Replace the sample content.', 'text' => 'The first useful page should take minutes, not an afternoon of blank-canvas decisions.', 'button_label' => 'Download latest', 'button_url' => '/help4-builder-suite/latest.zip', 'design_background_color' => '#2dd4bf', 'design_text_color' => '#07182f', 'design_padding_top' => 56, 'design_padding_right' => 44, 'design_padding_bottom' => 56, 'design_padding_left' => 44]],
                ],
            ],
            [
                'slug' => 'header-brand-nav-cta',
                'label' => __('Header: Brand, Menu, CTA', 'help4-builder-suite'),
                'category' => __('Headers', 'help4-builder-suite'),
                'description' => __('Three-part header with editable brand, native dropdown menu, mobile hamburger, and primary action.', 'help4-builder-suite'),
                'layout' => [
                    ['id' => 'hbnc_shell', 'type' => 'section', 'settings' => ['columns' => 3, 'column_widths' => '24|56|20', 'gap' => 18, 'min_height' => 84, 'content_width_mode' => 'custom', 'content_width' => 1280, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color_global' => 'surface', 'design_padding_top' => 14, 'design_padding_right' => 24, 'design_padding_bottom' => 14, 'design_padding_left' => 24]],
                    ['id' => 'hbnc_brand', 'type' => 'heading', 'parent_id' => 'hbnc_shell', 'slot' => 0, 'settings' => ['text' => 'Your Brand', 'tag' => 'h3', 'design_font_family_global' => 'heading', 'design_font_weight' => '800', 'design_margin_bottom' => 0]],
                    ['id' => 'hbnc_menu', 'type' => 'menu', 'parent_id' => 'hbnc_shell', 'slot' => 1, 'settings' => ['menu_slug' => '', 'orientation' => 'horizontal', 'dropdown_mode' => 'hover', 'depth' => '3', 'mobile_hamburger' => '1', 'menu_label' => 'Main Menu']],
                    ['id' => 'hbnc_cta', 'type' => 'button', 'parent_id' => 'hbnc_shell', 'slot' => 2, 'settings' => ['label' => 'Get Started', 'url' => '#contact', 'design_style_preset' => 'button_primary', 'design_text_align' => 'right']],
                ],
            ],
            [
                'slug' => 'header-centered-minimal',
                'label' => __('Header: Centered Minimal', 'help4-builder-suite'),
                'category' => __('Headers', 'help4-builder-suite'),
                'description' => __('Quiet centered brand and navigation for editorial, portfolio, and service sites.', 'help4-builder-suite'),
                'layout' => [
                    ['id' => 'hcm_shell', 'type' => 'section', 'settings' => ['columns' => 1, 'gap' => 10, 'min_height' => 112, 'content_width_mode' => 'custom', 'content_width' => 1120, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color_global' => 'surface', 'design_padding_top' => 18, 'design_padding_right' => 24, 'design_padding_bottom' => 18, 'design_padding_left' => 24]],
                    ['id' => 'hcm_brand', 'type' => 'heading', 'parent_id' => 'hcm_shell', 'slot' => 0, 'settings' => ['text' => 'Your Brand', 'tag' => 'h3', 'design_text_align' => 'center', 'design_font_weight' => '800', 'design_margin_bottom' => 4]],
                    ['id' => 'hcm_menu', 'type' => 'menu', 'parent_id' => 'hcm_shell', 'slot' => 0, 'settings' => ['menu_slug' => '', 'orientation' => 'horizontal', 'dropdown_mode' => 'hover', 'depth' => '3', 'mobile_hamburger' => '1', 'menu_label' => 'Main Menu', 'design_text_align' => 'center']],
                ],
            ],
            [
                'slug' => 'header-utility-service',
                'label' => __('Header: Utility + Service Nav', 'help4-builder-suite'),
                'category' => __('Headers', 'help4-builder-suite'),
                'description' => __('Service-business header with contact utility row, native navigation, and mobile action.', 'help4-builder-suite'),
                'layout' => [
                    ['id' => 'hus_utility', 'type' => 'section', 'settings' => ['columns' => 2, 'column_widths' => '60|40', 'gap' => 16, 'min_height' => 38, 'content_width_mode' => 'full', 'design_background_color_global' => 'heading', 'design_text_color_global' => 'on_primary', 'design_padding_top' => 8, 'design_padding_right' => 24, 'design_padding_bottom' => 8, 'design_padding_left' => 24]],
                    ['id' => 'hus_hours', 'type' => 'text', 'parent_id' => 'hus_utility', 'slot' => 0, 'settings' => ['text' => 'Mon-Fri 8am-6pm | Serving your area', 'design_text_color_global' => 'on_primary']],
                    ['id' => 'hus_phone', 'type' => 'button', 'parent_id' => 'hus_utility', 'slot' => 1, 'settings' => ['label' => 'Call now', 'url' => 'tel:+10000000000', 'design_style_preset' => 'button_ghost', 'design_text_align' => 'right']],
                    ['id' => 'hus_nav_shell', 'type' => 'section', 'settings' => ['columns' => 2, 'column_widths' => '28|72', 'gap' => 18, 'min_height' => 78, 'content_width_mode' => 'custom', 'content_width' => 1240, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color_global' => 'surface', 'design_padding_top' => 12, 'design_padding_right' => 24, 'design_padding_bottom' => 12, 'design_padding_left' => 24]],
                    ['id' => 'hus_brand', 'type' => 'heading', 'parent_id' => 'hus_nav_shell', 'slot' => 0, 'settings' => ['text' => 'Service Company', 'tag' => 'h3', 'design_font_weight' => '800']],
                    ['id' => 'hus_menu', 'type' => 'menu', 'parent_id' => 'hus_nav_shell', 'slot' => 1, 'settings' => ['menu_slug' => '', 'orientation' => 'horizontal', 'dropdown_mode' => 'hover', 'depth' => '3', 'mobile_hamburger' => '1', 'menu_label' => 'Services Menu']],
                ],
            ],
            [
                'slug' => 'footer-business-columns',
                'label' => __('Footer: Business Columns', 'help4-builder-suite'),
                'category' => __('Footers', 'help4-builder-suite'),
                'description' => __('Four-column business footer for brand summary, services, resources, and contact.', 'help4-builder-suite'),
                'layout' => [
                    ['id' => 'fbc_shell', 'type' => 'section', 'settings' => ['columns' => 4, 'columns_tablet' => 2, 'columns_mobile' => 1, 'gap' => 28, 'min_height' => 300, 'content_width_mode' => 'custom', 'content_width' => 1240, 'content_width_unit' => 'px', 'design_background_color_global' => 'heading', 'design_text_color_global' => 'on_primary', 'design_padding_top' => 60, 'design_padding_right' => 28, 'design_padding_bottom' => 44, 'design_padding_left' => 28]],
                    ['id' => 'fbc_brand', 'type' => 'cta', 'parent_id' => 'fbc_shell', 'slot' => 0, 'settings' => ['title' => 'Your Brand', 'text' => 'A short promise, service area, and reason customers trust the team.', 'button_label' => 'Contact us', 'button_url' => '#contact', 'design_text_color_global' => 'on_primary']],
                    ['id' => 'fbc_services', 'type' => 'list', 'parent_id' => 'fbc_shell', 'slot' => 1, 'settings' => ['items' => "Primary service\nSecondary service\nMaintenance plan\nEmergency help", 'ordered' => '0', 'design_text_color_global' => 'on_primary']],
                    ['id' => 'fbc_resources', 'type' => 'list', 'parent_id' => 'fbc_shell', 'slot' => 2, 'settings' => ['items' => "About\nRecent work\nGuides\nFrequently asked questions", 'ordered' => '0', 'design_text_color_global' => 'on_primary']],
                    ['id' => 'fbc_contact', 'type' => 'text', 'parent_id' => 'fbc_shell', 'slot' => 3, 'settings' => ['text' => "Phone: (000) 000-0000\nEmail: hello@example.com\nCity, State", 'design_text_color_global' => 'on_primary']],
                ],
            ],
            [
                'slug' => 'footer-minimal-legal',
                'label' => __('Footer: Minimal Legal', 'help4-builder-suite'),
                'category' => __('Footers', 'help4-builder-suite'),
                'description' => __('Compact copyright, legal links, and native footer menu.', 'help4-builder-suite'),
                'layout' => [
                    ['id' => 'fml_shell', 'type' => 'section', 'settings' => ['columns' => 2, 'columns_mobile' => 1, 'column_widths' => '55|45', 'gap' => 18, 'min_height' => 96, 'content_width_mode' => 'custom', 'content_width' => 1200, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color_global' => 'surface_alt', 'design_padding_top' => 24, 'design_padding_right' => 28, 'design_padding_bottom' => 24, 'design_padding_left' => 28]],
                    ['id' => 'fml_copy', 'type' => 'text', 'parent_id' => 'fml_shell', 'slot' => 0, 'settings' => ['text' => 'Copyright {year} Your Brand. All rights reserved.']],
                    ['id' => 'fml_menu', 'type' => 'menu', 'parent_id' => 'fml_shell', 'slot' => 1, 'settings' => ['menu_slug' => '', 'orientation' => 'horizontal', 'dropdown_mode' => 'none', 'depth' => '1', 'mobile_hamburger' => '0', 'menu_label' => 'Legal Menu']],
                ],
            ],
            [
                'slug' => 'footer-commerce-support',
                'label' => __('Footer: Commerce + Support', 'help4-builder-suite'),
                'category' => __('Footers', 'help4-builder-suite'),
                'description' => __('Commerce footer with account, ordering, policies, and support paths.', 'help4-builder-suite'),
                'layout' => [
                    ['id' => 'fcs_shell', 'type' => 'section', 'settings' => ['columns' => 3, 'columns_tablet' => 2, 'columns_mobile' => 1, 'gap' => 24, 'min_height' => 260, 'content_width_mode' => 'custom', 'content_width' => 1240, 'content_width_unit' => 'px', 'design_background_color_global' => 'heading', 'design_text_color_global' => 'on_primary', 'design_padding_top' => 54, 'design_padding_right' => 28, 'design_padding_bottom' => 46, 'design_padding_left' => 28]],
                    ['id' => 'fcs_shop', 'type' => 'cta', 'parent_id' => 'fcs_shell', 'slot' => 0, 'settings' => ['title' => 'Shop', 'text' => 'Products, subscriptions, bundles, and account-ready checkout.', 'button_label' => 'Browse products', 'button_url' => '/products/', 'design_text_color_global' => 'on_primary']],
                    ['id' => 'fcs_help', 'type' => 'cta', 'parent_id' => 'fcs_shell', 'slot' => 1, 'settings' => ['title' => 'Need help?', 'text' => 'Questions about an order, payment, access, variation, or renewal.', 'button_label' => 'Contact support', 'button_url' => '/contact/', 'design_text_color_global' => 'on_primary']],
                    ['id' => 'fcs_policy', 'type' => 'list', 'parent_id' => 'fcs_shell', 'slot' => 2, 'settings' => ['items' => "Shipping and delivery\nReturns and cancellations\nPrivacy policy\nTerms of use", 'ordered' => '0', 'design_text_color_global' => 'on_primary']],
                ],
            ],
            [
                'slug' => '404-search-rescue',
                'label' => __('404: Search Rescue', 'help4-builder-suite'),
                'category' => __('404 Pages', 'help4-builder-suite'),
                'description' => __('Helpful missing-page response with search, popular routes, and a clear home action.', 'help4-builder-suite'),
                'page_layout' => 'canvas',
                'layout' => [
                    ['id' => 'fsr_shell', 'type' => 'section', 'settings' => ['columns' => 1, 'gap' => 18, 'min_height' => 680, 'content_width_mode' => 'custom', 'content_width' => 820, 'content_width_unit' => 'px', 'align_items' => 'center', 'justify_content' => 'center', 'design_background_color_global' => 'surface_alt', 'design_padding_top' => 80, 'design_padding_right' => 28, 'design_padding_bottom' => 80, 'design_padding_left' => 28]],
                    ['id' => 'fsr_code', 'type' => 'heading', 'parent_id' => 'fsr_shell', 'slot' => 0, 'settings' => ['text' => '404', 'tag' => 'h1', 'design_text_align' => 'center', 'design_text_color_global' => 'primary', 'design_font_size' => 96, 'design_font_size_unit' => 'px', 'design_font_weight' => '800']],
                    ['id' => 'fsr_title', 'type' => 'heading', 'parent_id' => 'fsr_shell', 'slot' => 0, 'settings' => ['text' => 'That page moved or never existed.', 'tag' => 'h2', 'design_text_align' => 'center']],
                    ['id' => 'fsr_text', 'type' => 'text', 'parent_id' => 'fsr_shell', 'slot' => 0, 'settings' => ['text' => 'Search the site, return home, or use the main menu to get back on track.', 'design_text_align' => 'center']],
                    ['id' => 'fsr_search', 'type' => 'shortcode', 'parent_id' => 'fsr_shell', 'slot' => 0, 'settings' => ['shortcode' => '[h4_search type="all" placeholder="Search this site..."]']],
                    ['id' => 'fsr_home', 'type' => 'button', 'parent_id' => 'fsr_shell', 'slot' => 0, 'settings' => ['label' => 'Return Home', 'url' => '/', 'design_style_preset' => 'button_primary', 'design_text_align' => 'center']],
                ],
            ],
            [
                'slug' => '404-service-rescue',
                'label' => __('404: Service Rescue', 'help4-builder-suite'),
                'category' => __('404 Pages', 'help4-builder-suite'),
                'description' => __('Conversion-aware 404 with service, support, and contact recovery paths.', 'help4-builder-suite'),
                'page_layout' => 'full_width',
                'layout' => [
                    ['id' => 'fsv_intro', 'type' => 'cta', 'settings' => ['title' => 'The link is broken. The help is not.', 'text' => 'Choose the job you came to solve or contact the team and we will point you to the right place.', 'button_label' => 'Contact support', 'button_url' => '/contact/', 'design_background_color_global' => 'heading', 'design_text_color_global' => 'on_primary', 'design_padding_top' => 74, 'design_padding_right' => 36, 'design_padding_bottom' => 74, 'design_padding_left' => 36]],
                    ['id' => 'fsv_paths', 'type' => 'section', 'settings' => ['columns' => 3, 'columns_mobile' => 1, 'gap' => 20, 'min_height' => 280, 'content_width_mode' => 'custom', 'content_width' => 1120, 'content_width_unit' => 'px', 'design_padding_top' => 58, 'design_padding_right' => 28, 'design_padding_bottom' => 58, 'design_padding_left' => 28]],
                    ['id' => 'fsv_service', 'type' => 'cta', 'parent_id' => 'fsv_paths', 'slot' => 0, 'settings' => ['title' => 'Services', 'text' => 'See what the team handles and how the process works.', 'button_label' => 'View services', 'button_url' => '/services/', 'design_style_preset' => 'card']],
                    ['id' => 'fsv_work', 'type' => 'cta', 'parent_id' => 'fsv_paths', 'slot' => 1, 'settings' => ['title' => 'Recent work', 'text' => 'Review real projects, outcomes, and the kind of work we accept.', 'button_label' => 'View work', 'button_url' => '/portfolio/', 'design_style_preset' => 'card']],
                    ['id' => 'fsv_home', 'type' => 'cta', 'parent_id' => 'fsv_paths', 'slot' => 2, 'settings' => ['title' => 'Start over', 'text' => 'Return to the homepage and choose the route that fits.', 'button_label' => 'Go home', 'button_url' => '/', 'design_style_preset' => 'card']],
                ],
            ],
            [
                'slug' => 'sales-homepage-open-bands',
                'label' => __('Sales Homepage: Open Bands', 'help4-builder-suite'),
                'category' => __('Sales + Services', 'help4-builder-suite'),
                'description' => __('Complete service homepage with offer, proof, outcomes, process, FAQ, and contact paths.', 'help4-builder-suite'),
                'tags' => ['homepage', 'service', 'agency', 'offer', 'proof', 'process', 'contact', 'conversion'],
                'page_layout' => 'full_width',
                'layout' => [
                    ['id' => 'shob_hero', 'type' => 'section', 'settings' => ['columns' => 2, 'columns_tablet' => 2, 'columns_mobile' => 1, 'column_widths' => '58|42', 'column_widths_tablet' => '55|45', 'gap' => 44, 'gap_tablet' => 30, 'gap_mobile' => 22, 'min_height' => 600, 'min_height_tablet' => 520, 'min_height_mobile' => 420, 'content_width_mode' => 'custom', 'content_width' => 1240, 'content_width_unit' => 'px', 'content_width_mode_mobile' => 'full', 'align_items' => 'center', 'design_background_color_global' => 'heading', 'design_text_color_global' => 'on_primary', 'design_padding_top' => 84, 'design_padding_right' => 30, 'design_padding_bottom' => 84, 'design_padding_left' => 30, 'design_padding_top_mobile' => 54, 'design_padding_right_mobile' => 20, 'design_padding_bottom_mobile' => 54, 'design_padding_left_mobile' => 20]],
                    ['id' => 'shob_offer', 'type' => 'cta', 'parent_id' => 'shob_hero', 'slot' => 0, 'settings' => ['title' => 'A clear result for the customer you serve.', 'text' => 'State the problem, the useful outcome, what is included, and what happens after the customer responds.', 'button_label' => 'Start the conversation', 'button_url' => '#contact', 'design_text_color_global' => 'on_primary', 'design_font_size' => 19, 'design_font_size_unit' => 'px']],
                    ['id' => 'shob_proof', 'type' => 'list', 'parent_id' => 'shob_hero', 'slot' => 1, 'settings' => ['items' => "Specific customer outcome\nPlain-language scope\nVisible proof or examples\nLow-friction next step", 'ordered' => '0', 'design_background_color_global' => 'surface', 'design_text_color_global' => 'text', 'design_padding_top' => 32, 'design_padding_right' => 30, 'design_padding_bottom' => 32, 'design_padding_left' => 30, 'design_border_radius' => 6, 'design_border_radius_unit' => 'px']],
                    ['id' => 'shob_outcomes', 'type' => 'section', 'settings' => ['columns' => 3, 'columns_tablet' => 2, 'columns_mobile' => 1, 'gap' => 22, 'gap_mobile' => 16, 'min_height' => 330, 'content_width_mode' => 'custom', 'content_width' => 1180, 'content_width_unit' => 'px', 'design_padding_top' => 72, 'design_padding_right' => 28, 'design_padding_bottom' => 72, 'design_padding_left' => 28, 'design_padding_top_mobile' => 48, 'design_padding_right_mobile' => 20, 'design_padding_bottom_mobile' => 48, 'design_padding_left_mobile' => 20]],
                    ['id' => 'shob_outcome_one', 'type' => 'cta', 'parent_id' => 'shob_outcomes', 'slot' => 0, 'settings' => ['title' => 'Outcome one', 'text' => "Explain the first useful result in the customer's language.", 'button_label' => 'Learn more', 'button_url' => '#outcome-one', 'design_style_preset' => 'card']],
                    ['id' => 'shob_outcome_two', 'type' => 'cta', 'parent_id' => 'shob_outcomes', 'slot' => 1, 'settings' => ['title' => 'Outcome two', 'text' => 'Show what becomes easier, faster, safer, or more profitable.', 'button_label' => 'See the process', 'button_url' => '#process', 'design_style_preset' => 'card_soft']],
                    ['id' => 'shob_outcome_three', 'type' => 'cta', 'parent_id' => 'shob_outcomes', 'slot' => 2, 'settings' => ['title' => 'Outcome three', 'text' => 'Connect the service to a concrete next step or deliverable.', 'button_label' => 'View examples', 'button_url' => '#proof', 'design_style_preset' => 'card']],
                    ['id' => 'shob_process', 'type' => 'section', 'settings' => ['columns' => 3, 'columns_tablet' => 3, 'columns_mobile' => 1, 'gap' => 18, 'min_height' => 280, 'content_width_mode' => 'custom', 'content_width' => 1120, 'content_width_unit' => 'px', 'design_background_color_global' => 'surface_alt', 'design_padding_top' => 64, 'design_padding_right' => 28, 'design_padding_bottom' => 64, 'design_padding_left' => 28, 'advanced_anchor_id' => 'process']],
                    ['id' => 'shob_step_one', 'type' => 'cta', 'parent_id' => 'shob_process', 'slot' => 0, 'settings' => ['title' => '1. Understand', 'text' => 'Collect the goal, constraints, current state, and deadline.', 'button_label' => 'What to prepare', 'button_url' => '#prepare']],
                    ['id' => 'shob_step_two', 'type' => 'cta', 'parent_id' => 'shob_process', 'slot' => 1, 'settings' => ['title' => '2. Build', 'text' => 'Complete the agreed work and keep decisions visible.', 'button_label' => 'How work happens', 'button_url' => '#work']],
                    ['id' => 'shob_step_three', 'type' => 'cta', 'parent_id' => 'shob_process', 'slot' => 2, 'settings' => ['title' => '3. Verify', 'text' => 'Test the result, explain the handoff, and define support.', 'button_label' => 'Launch checklist', 'button_url' => '#launch']],
                    ['id' => 'shob_faq', 'type' => 'faq', 'settings' => ['items' => "What is included?::Replace this answer with the exact deliverables, limits, and ownership for the service.\nHow long does it take?::Give a realistic range and explain what can change the schedule.\nWhat happens after I contact you?::Describe the response, qualification, estimate, and kickoff path.", 'open_first' => '1', 'design_background_color_global' => 'surface', 'design_text_color_global' => 'text', 'design_padding_top' => 64, 'design_padding_right' => 30, 'design_padding_bottom' => 64, 'design_padding_left' => 30, 'advanced_anchor_id' => 'faq']],
                    ['id' => 'shob_close', 'type' => 'cta', 'settings' => ['title' => 'Make the next step obvious.', 'text' => 'Tell the customer what to send and when they should expect a response.', 'button_label' => 'Contact the team', 'button_url' => '#contact', 'design_background_color_global' => 'primary', 'design_text_color_global' => 'on_primary', 'design_padding_top' => 58, 'design_padding_right' => 42, 'design_padding_bottom' => 58, 'design_padding_left' => 42]],
                ],
            ],
            [
                'slug' => 'pricing-five-plan-responsive',
                'label' => __('Pricing: Five Plans Responsive', 'help4-builder-suite'),
                'category' => __('Pricing + Offers', 'help4-builder-suite'),
                'description' => __('Five editable plan cards that become two columns on tablet and one labeled plan per row on mobile.', 'help4-builder-suite'),
                'tags' => ['pricing', 'plans', 'packages', 'subscriptions', 'comparison', 'sales'],
                'layout' => [
                    ['id' => 'pfpr_intro', 'type' => 'cta', 'settings' => ['title' => 'Choose by how much help you need.', 'text' => 'Name billing terms, limits, support, and the next step without hiding important differences.', 'button_label' => 'Compare plan details', 'button_url' => '#plans', 'design_background_color_global' => 'surface_alt', 'design_text_color_global' => 'text', 'design_padding_top' => 54, 'design_padding_right' => 32, 'design_padding_bottom' => 40, 'design_padding_left' => 32]],
                    ['id' => 'pfpr_plans', 'type' => 'section', 'settings' => ['columns' => 5, 'columns_tablet' => 2, 'columns_mobile' => 1, 'gap' => 16, 'gap_tablet' => 18, 'gap_mobile' => 14, 'min_height' => 540, 'min_height_tablet' => 420, 'min_height_mobile' => 320, 'content_width_mode' => 'custom', 'content_width' => 1380, 'content_width_unit' => 'px', 'content_width_mode_mobile' => 'full', 'align_items' => 'stretch', 'design_background_color_global' => 'surface_alt', 'design_padding_top' => 14, 'design_padding_right' => 24, 'design_padding_bottom' => 64, 'design_padding_left' => 24, 'design_padding_right_mobile' => 18, 'design_padding_left_mobile' => 18, 'advanced_anchor_id' => 'plans']],
                    ['id' => 'pfpr_plan_one', 'type' => 'cta', 'parent_id' => 'pfpr_plans', 'slot' => 0, 'settings' => ['title' => 'Starter - $00/mo', 'text' => "Best for: a focused first step.\nIncludes: core service, standard support, and clear limits.", 'button_label' => 'Choose Starter', 'button_url' => '#contact', 'design_style_preset' => 'card']],
                    ['id' => 'pfpr_plan_two', 'type' => 'cta', 'parent_id' => 'pfpr_plans', 'slot' => 1, 'settings' => ['title' => 'Care - $00/mo', 'text' => "Best for: recurring help.\nIncludes: Starter plus a larger monthly service allowance.", 'button_label' => 'Choose Care', 'button_url' => '#contact', 'design_style_preset' => 'card_soft']],
                    ['id' => 'pfpr_plan_three', 'type' => 'cta', 'parent_id' => 'pfpr_plans', 'slot' => 2, 'settings' => ['title' => 'Growth - $00/mo', 'text' => "Best for: active growth.\nIncludes: recurring production, reporting, and priority work.", 'button_label' => 'Choose Growth', 'button_url' => '#contact', 'design_style_preset' => 'card_elevated']],
                    ['id' => 'pfpr_plan_four', 'type' => 'cta', 'parent_id' => 'pfpr_plans', 'slot' => 3, 'settings' => ['title' => 'Pro - $00/mo', 'text' => "Best for: a larger workload.\nIncludes: expanded capacity, faster routing, and deeper implementation.", 'button_label' => 'Choose Pro', 'button_url' => '#contact', 'design_background_color_global' => 'heading', 'design_text_color_global' => 'on_primary', 'design_padding_top' => 28, 'design_padding_right' => 22, 'design_padding_bottom' => 28, 'design_padding_left' => 22, 'design_border_radius' => 6, 'design_border_radius_unit' => 'px']],
                    ['id' => 'pfpr_plan_five', 'type' => 'cta', 'parent_id' => 'pfpr_plans', 'slot' => 4, 'settings' => ['title' => 'Partner - Custom', 'text' => "Best for: complex operations.\nIncludes: a written scope, custom integrations, and accountable ownership.", 'button_label' => 'Discuss Partner', 'button_url' => '#contact', 'design_style_preset' => 'card']],
                ],
            ],
            [
                'slug' => 'faq-visible-schema-ready',
                'label' => __('FAQ: Visible + Schema Ready', 'help4-builder-suite'),
                'category' => __('FAQ + Support', 'help4-builder-suite'),
                'description' => __('Accessible visible FAQ section prepared for matching FAQPage metadata.', 'help4-builder-suite'),
                'tags' => ['faq', 'questions', 'answers', 'support', 'schema', 'accordion'],
                'layout' => [
                    ['id' => 'fvsr_intro', 'type' => 'section', 'settings' => ['columns' => 2, 'columns_tablet' => 2, 'columns_mobile' => 1, 'column_widths' => '36|64', 'gap' => 42, 'gap_mobile' => 20, 'min_height' => 420, 'content_width_mode' => 'custom', 'content_width' => 1120, 'content_width_unit' => 'px', 'align_items' => 'start', 'design_background_color_global' => 'surface_alt', 'design_padding_top' => 68, 'design_padding_right' => 30, 'design_padding_bottom' => 68, 'design_padding_left' => 30, 'design_padding_top_mobile' => 46, 'design_padding_right_mobile' => 20, 'design_padding_bottom_mobile' => 46, 'design_padding_left_mobile' => 20]],
                    ['id' => 'fvsr_copy', 'type' => 'cta', 'parent_id' => 'fvsr_intro', 'slot' => 0, 'settings' => ['title' => 'Questions customers ask before acting.', 'text' => 'Keep every answer visible on this page, accurate, and consistent with the matching FAQPage schema.', 'button_label' => 'Ask another question', 'button_url' => '#contact']],
                    ['id' => 'fvsr_faq', 'type' => 'faq', 'parent_id' => 'fvsr_intro', 'slot' => 1, 'settings' => ['items' => "What does the service include?::List the actual deliverables and important limits in plain language.\nHow does billing work?::State the amount, billing interval, renewal behavior, and cancellation path.\nWhen will work begin?::Give the expected response and kickoff window without promising a date you cannot control.\nHow do I get support?::Name the support route, normal response window, and what information the customer should send.", 'open_first' => '1', 'design_background_color_global' => 'surface', 'design_text_color_global' => 'text', 'design_padding_top' => 24, 'design_padding_right' => 26, 'design_padding_bottom' => 24, 'design_padding_left' => 26, 'design_border_radius' => 6, 'design_border_radius_unit' => 'px']],
                ],
            ],
            [
                'slug' => 'header-exact-logo-nav-cta',
                'label' => __('Header: Exact Logo, Menu, CTA', 'help4-builder-suite'),
                'category' => __('Headers', 'help4-builder-suite'),
                'description' => __('Exact media-logo header with native dropdown hierarchy, keyboard access, mobile hamburger, and contact action.', 'help4-builder-suite'),
                'tags' => ['header', 'logo', 'menu', 'navigation', 'dropdown', 'mobile'],
                'layout' => [
                    ['id' => 'helnc_shell', 'type' => 'section', 'settings' => ['columns' => 3, 'columns_tablet' => 3, 'columns_mobile' => 1, 'column_widths' => '24|56|20', 'column_widths_tablet' => '28|52|20', 'gap' => 18, 'gap_tablet' => 14, 'gap_mobile' => 12, 'min_height' => 86, 'min_height_mobile' => 72, 'content_width_mode' => 'custom', 'content_width' => 1280, 'content_width_unit' => 'px', 'content_width_mode_mobile' => 'full', 'align_items' => 'center', 'design_background_color_global' => 'surface', 'design_padding_top' => 12, 'design_padding_right' => 24, 'design_padding_bottom' => 12, 'design_padding_left' => 24, 'design_padding_top_mobile' => 10, 'design_padding_right_mobile' => 18, 'design_padding_bottom_mobile' => 10, 'design_padding_left_mobile' => 18]],
                    ['id' => 'helnc_logo', 'type' => 'image', 'parent_id' => 'helnc_shell', 'slot' => 0, 'settings' => ['src' => '', 'alt' => 'Replace with the exact brand logo', 'width' => '176', 'width_unit' => 'px', 'max_width' => '100', 'max_width_unit' => '%']],
                    ['id' => 'helnc_menu', 'type' => 'menu', 'parent_id' => 'helnc_shell', 'slot' => 1, 'settings' => ['menu_slug' => '', 'orientation' => 'horizontal', 'dropdown_mode' => 'hover', 'depth' => '3', 'mobile_hamburger' => '1', 'menu_label' => 'Main Menu', 'design_text_align' => 'center']],
                    ['id' => 'helnc_cta', 'type' => 'button', 'parent_id' => 'helnc_shell', 'slot' => 2, 'settings' => ['label' => 'Contact', 'url' => '#contact', 'design_style_preset' => 'button_primary', 'design_text_align' => 'right', 'design_text_align_mobile' => 'left']],
                ],
            ],
            [
                'slug' => 'proof-case-study-results',
                'label' => __('Proof: Case Study Results', 'help4-builder-suite'),
                'category' => __('Proof + Trust', 'help4-builder-suite'),
                'description' => __('Outcome-led case study with context, measurable results, customer quote, and project action.', 'help4-builder-suite'),
                'tags' => ['proof', 'case study', 'results', 'testimonial', 'portfolio', 'metrics'],
                'layout' => [
                    ['id' => 'pcsr_shell', 'type' => 'section', 'settings' => ['columns' => 2, 'columns_tablet' => 2, 'columns_mobile' => 1, 'column_widths' => '42|58', 'gap' => 44, 'gap_mobile' => 20, 'min_height' => 480, 'content_width_mode' => 'custom', 'content_width' => 1180, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color_global' => 'surface', 'design_padding_top' => 72, 'design_padding_right' => 30, 'design_padding_bottom' => 72, 'design_padding_left' => 30, 'design_padding_top_mobile' => 48, 'design_padding_right_mobile' => 20, 'design_padding_bottom_mobile' => 48, 'design_padding_left_mobile' => 20]],
                    ['id' => 'pcsr_story', 'type' => 'cta', 'parent_id' => 'pcsr_shell', 'slot' => 0, 'settings' => ['title' => 'From the starting problem to a useful result.', 'text' => 'Name the customer, the constraint, what changed, and what the result means. Replace broad claims with evidence people can inspect.', 'button_label' => 'View the project', 'button_url' => '#project', 'design_style_preset' => 'card_soft']],
                    ['id' => 'pcsr_results', 'type' => 'section', 'parent_id' => 'pcsr_shell', 'slot' => 1, 'settings' => ['columns' => 3, 'columns_tablet' => 3, 'columns_mobile' => 1, 'gap' => 14, 'gap_mobile' => 12, 'min_height' => 260, 'content_width_mode' => 'full', 'design_background_color_global' => 'surface_alt', 'design_padding_top' => 22, 'design_padding_right' => 22, 'design_padding_bottom' => 22, 'design_padding_left' => 22, 'design_border_radius' => 6, 'design_border_radius_unit' => 'px']],
                    ['id' => 'pcsr_metric_one', 'type' => 'counter', 'parent_id' => 'pcsr_results', 'slot' => 0, 'settings' => ['value' => 42, 'suffix' => '%', 'label' => 'Replace with a verified result']],
                    ['id' => 'pcsr_metric_two', 'type' => 'counter', 'parent_id' => 'pcsr_results', 'slot' => 1, 'settings' => ['value' => 18, 'suffix' => ' days', 'label' => 'Replace with a verified timeline']],
                    ['id' => 'pcsr_metric_three', 'type' => 'counter', 'parent_id' => 'pcsr_results', 'slot' => 2, 'settings' => ['value' => 3, 'suffix' => 'x', 'label' => 'Replace with a verified comparison']],
                    ['id' => 'pcsr_quote', 'type' => 'quote', 'settings' => ['text' => 'Replace this with a real customer statement you have permission to publish.', 'cite' => 'Customer name and role', 'design_background_color_global' => 'heading', 'design_text_color_global' => 'on_primary', 'design_font_size' => 28, 'design_font_size_unit' => 'px', 'design_padding_top' => 54, 'design_padding_right' => 40, 'design_padding_bottom' => 54, 'design_padding_left' => 40]],
                ],
            ],
            [
                'slug' => 'contact-intake-split',
                'label' => __('Contact: Guided Intake Split', 'help4-builder-suite'),
                'category' => __('Contact + Forms', 'help4-builder-suite'),
                'description' => __('Guided contact section with preparation checklist, expectations, and native Help4 form handoff.', 'help4-builder-suite'),
                'tags' => ['contact', 'form', 'lead', 'intake', 'quote', 'consultation'],
                'layout' => [
                    ['id' => 'cis_shell', 'type' => 'section', 'settings' => ['columns' => 2, 'columns_tablet' => 2, 'columns_mobile' => 1, 'column_widths' => '44|56', 'gap' => 46, 'gap_mobile' => 22, 'min_height' => 620, 'content_width_mode' => 'custom', 'content_width' => 1160, 'content_width_unit' => 'px', 'align_items' => 'start', 'design_background_color_global' => 'surface_alt', 'design_padding_top' => 72, 'design_padding_right' => 30, 'design_padding_bottom' => 72, 'design_padding_left' => 30, 'design_padding_top_mobile' => 48, 'design_padding_right_mobile' => 20, 'design_padding_bottom_mobile' => 48, 'design_padding_left_mobile' => 20, 'advanced_anchor_id' => 'contact']],
                    ['id' => 'cis_intro', 'type' => 'cta', 'parent_id' => 'cis_shell', 'slot' => 0, 'settings' => ['title' => 'Tell us enough to give a useful answer.', 'text' => 'Set the response expectation and explain what information helps the team route the request correctly.', 'button_label' => 'Review service options', 'button_url' => '#services']],
                    ['id' => 'cis_checklist', 'type' => 'list', 'parent_id' => 'cis_shell', 'slot' => 0, 'settings' => ['items' => "Website or project URL\nWhat needs to change\nDeadline or event date\nBudget or service level\nBest reply method", 'ordered' => '0', 'design_background_color_global' => 'heading', 'design_text_color_global' => 'on_primary', 'design_padding_top' => 26, 'design_padding_right' => 26, 'design_padding_bottom' => 26, 'design_padding_left' => 26, 'design_border_radius' => 6, 'design_border_radius_unit' => 'px']],
                    ['id' => 'cis_form', 'type' => 'shortcode', 'parent_id' => 'cis_shell', 'slot' => 1, 'settings' => ['shortcode' => '[h4_contact_form]', 'design_background_color_global' => 'surface', 'design_padding_top' => 30, 'design_padding_right' => 30, 'design_padding_bottom' => 30, 'design_padding_left' => 30, 'design_border_radius' => 6, 'design_border_radius_unit' => 'px']],
                ],
            ],
            [
                'slug' => 'comparison-before-after',
                'label' => __('Comparison: Before + After', 'help4-builder-suite'),
                'category' => __('Comparison + Education', 'help4-builder-suite'),
                'description' => __('Side-by-side problem and outcome comparison that stacks cleanly on mobile.', 'help4-builder-suite'),
                'tags' => ['comparison', 'before', 'after', 'benefits', 'migration', 'transformation'],
                'layout' => [
                    ['id' => 'cba_intro', 'type' => 'cta', 'settings' => ['title' => 'Show what changes after the customer acts.', 'text' => 'Use specific conditions and outcomes instead of vague before-and-after language.', 'button_label' => 'See the process', 'button_url' => '#process', 'design_background_color_global' => 'surface', 'design_text_color_global' => 'text', 'design_padding_top' => 54, 'design_padding_right' => 32, 'design_padding_bottom' => 38, 'design_padding_left' => 32]],
                    ['id' => 'cba_columns', 'type' => 'section', 'settings' => ['columns' => 2, 'columns_tablet' => 2, 'columns_mobile' => 1, 'column_widths' => '50|50', 'gap' => 20, 'gap_mobile' => 14, 'min_height' => 380, 'content_width_mode' => 'custom', 'content_width' => 1080, 'content_width_unit' => 'px', 'align_items' => 'stretch', 'design_padding_top' => 12, 'design_padding_right' => 28, 'design_padding_bottom' => 64, 'design_padding_left' => 28, 'design_padding_right_mobile' => 18, 'design_padding_left_mobile' => 18]],
                    ['id' => 'cba_before', 'type' => 'cta', 'parent_id' => 'cba_columns', 'slot' => 0, 'settings' => ['title' => 'Before', 'text' => 'List the delays, duplicated work, missed opportunities, or confusing steps the customer recognizes.', 'button_label' => 'Understand the problem', 'button_url' => '#problem', 'design_background_color_global' => 'surface_alt', 'design_text_color_global' => 'text', 'design_padding_top' => 34, 'design_padding_right' => 30, 'design_padding_bottom' => 34, 'design_padding_left' => 30, 'design_border_radius' => 6, 'design_border_radius_unit' => 'px']],
                    ['id' => 'cba_after', 'type' => 'cta', 'parent_id' => 'cba_columns', 'slot' => 1, 'settings' => ['title' => 'After', 'text' => 'Describe the clearer workflow, useful deliverable, measurable result, and support path.', 'button_label' => 'Start the change', 'button_url' => '#contact', 'design_background_color_global' => 'primary', 'design_text_color_global' => 'on_primary', 'design_padding_top' => 34, 'design_padding_right' => 30, 'design_padding_bottom' => 34, 'design_padding_left' => 30, 'design_border_radius' => 6, 'design_border_radius_unit' => 'px']],
                ],
            ],
            [
                'slug' => 'trust-authority-team',
                'label' => __('Trust: Authority + Team', 'help4-builder-suite'),
                'category' => __('Proof + Trust', 'help4-builder-suite'),
                'description' => __('Team authority section with expertise, process ownership, standards, and verification paths.', 'help4-builder-suite'),
                'tags' => ['team', 'authority', 'expertise', 'credentials', 'trust', 'about'],
                'layout' => [
                    ['id' => 'tat_intro', 'type' => 'cta', 'settings' => ['title' => 'Show who owns the work.', 'text' => 'Name the people, relevant experience, standards, and accountability behind the service.', 'button_label' => 'Meet the team', 'button_url' => '#team', 'design_background_color_global' => 'heading', 'design_text_color_global' => 'on_primary', 'design_padding_top' => 58, 'design_padding_right' => 38, 'design_padding_bottom' => 46, 'design_padding_left' => 38]],
                    ['id' => 'tat_cards', 'type' => 'section', 'settings' => ['columns' => 3, 'columns_tablet' => 2, 'columns_mobile' => 1, 'gap' => 20, 'gap_mobile' => 14, 'min_height' => 340, 'content_width_mode' => 'custom', 'content_width' => 1160, 'content_width_unit' => 'px', 'design_padding_top' => 54, 'design_padding_right' => 28, 'design_padding_bottom' => 64, 'design_padding_left' => 28, 'design_padding_right_mobile' => 18, 'design_padding_left_mobile' => 18]],
                    ['id' => 'tat_expertise', 'type' => 'cta', 'parent_id' => 'tat_cards', 'slot' => 0, 'settings' => ['title' => 'Relevant expertise', 'text' => 'Explain the experience that directly helps this customer and link to supporting work.', 'button_label' => 'View experience', 'button_url' => '#experience', 'design_style_preset' => 'card']],
                    ['id' => 'tat_owner', 'type' => 'cta', 'parent_id' => 'tat_cards', 'slot' => 1, 'settings' => ['title' => 'Named ownership', 'text' => 'Tell customers who coordinates decisions, communication, and completion.', 'button_label' => 'How we work', 'button_url' => '#process', 'design_style_preset' => 'card_soft']],
                    ['id' => 'tat_standards', 'type' => 'cta', 'parent_id' => 'tat_cards', 'slot' => 2, 'settings' => ['title' => 'Visible standards', 'text' => 'Publish the checks, safeguards, accessibility, privacy, or quality rules that guide delivery.', 'button_label' => 'Review standards', 'button_url' => '#standards', 'design_style_preset' => 'card']],
                ],
            ],
            [
                'slug' => 'page-shell-theme-default',
                'label' => __('Page Shell: Theme Header + Footer', 'help4-builder-suite'),
                'category' => __('Page Shells', 'help4-builder-suite'),
                'description' => __('Ordinary content page that keeps the active theme header and footer.', 'help4-builder-suite'),
                'page_layout' => 'default',
                'layout' => [
                    ['id' => 'pstd_intro', 'type' => 'section', 'settings' => ['columns' => 1, 'gap' => 18, 'min_height' => 260, 'content_width_mode' => 'custom', 'content_width' => 920, 'content_width_unit' => 'px', 'design_padding_top' => 64, 'design_padding_right' => 28, 'design_padding_bottom' => 36, 'design_padding_left' => 28]],
                    ['id' => 'pstd_title', 'type' => 'heading', 'parent_id' => 'pstd_intro', 'slot' => 0, 'settings' => ['text' => 'Page Title', 'tag' => 'h1']],
                    ['id' => 'pstd_lead', 'type' => 'text', 'parent_id' => 'pstd_intro', 'slot' => 0, 'settings' => ['text' => 'Replace this introduction with the plain-language answer the visitor came to find.', 'design_font_size' => 20, 'design_font_size_unit' => 'px']],
                    ['id' => 'pstd_body', 'type' => 'text', 'settings' => ['text' => 'Add sections, images, lists, forms, and calls to action below this content shell.', 'design_padding_top' => 24, 'design_padding_right' => 28, 'design_padding_bottom' => 64, 'design_padding_left' => 28]],
                ],
            ],
            [
                'slug' => 'page-shell-full-width',
                'label' => __('Page Shell: Help4 Full Width', 'help4-builder-suite'),
                'category' => __('Page Shells', 'help4-builder-suite'),
                'description' => __('Full-width Builder page that keeps assigned header and footer template parts.', 'help4-builder-suite'),
                'page_layout' => 'full_width',
                'layout' => [
                    ['id' => 'psfw_hero', 'type' => 'section', 'settings' => ['columns' => 2, 'columns_mobile' => 1, 'column_widths' => '58|42', 'gap' => 36, 'min_height' => 480, 'content_width_mode' => 'custom', 'content_width' => 1240, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color_global' => 'surface_alt', 'design_padding_top' => 72, 'design_padding_right' => 30, 'design_padding_bottom' => 72, 'design_padding_left' => 30]],
                    ['id' => 'psfw_copy', 'type' => 'cta', 'parent_id' => 'psfw_hero', 'slot' => 0, 'settings' => ['title' => 'Build the page around one clear outcome.', 'text' => 'Start with the customer problem, show the result, provide proof, and make the next action obvious.', 'button_label' => 'Primary action', 'button_url' => '#next']],
                    ['id' => 'psfw_media', 'type' => 'image', 'parent_id' => 'psfw_hero', 'slot' => 1, 'settings' => ['src' => '', 'alt' => 'Replace with a relevant page image', 'width' => '100', 'width_unit' => '%', 'max_width' => '640', 'max_width_unit' => 'px']],
                    ['id' => 'psfw_next', 'type' => 'section', 'settings' => ['columns' => 3, 'columns_mobile' => 1, 'gap' => 20, 'min_height' => 260, 'content_width_mode' => 'custom', 'content_width' => 1180, 'content_width_unit' => 'px', 'design_padding_top' => 56, 'design_padding_right' => 28, 'design_padding_bottom' => 56, 'design_padding_left' => 28]],
                    ['id' => 'psfw_one', 'type' => 'cta', 'parent_id' => 'psfw_next', 'slot' => 0, 'settings' => ['title' => 'Outcome one', 'text' => 'Explain the first useful result.', 'button_label' => 'Learn more', 'button_url' => '#one', 'design_style_preset' => 'card']],
                    ['id' => 'psfw_two', 'type' => 'cta', 'parent_id' => 'psfw_next', 'slot' => 1, 'settings' => ['title' => 'Outcome two', 'text' => 'Explain the second useful result.', 'button_label' => 'Learn more', 'button_url' => '#two', 'design_style_preset' => 'card']],
                    ['id' => 'psfw_three', 'type' => 'cta', 'parent_id' => 'psfw_next', 'slot' => 2, 'settings' => ['title' => 'Outcome three', 'text' => 'Explain the third useful result.', 'button_label' => 'Learn more', 'button_url' => '#three', 'design_style_preset' => 'card']],
                ],
            ],
            [
                'slug' => 'page-shell-canvas-landing',
                'label' => __('Page Shell: Canvas Landing', 'help4-builder-suite'),
                'category' => __('Page Shells', 'help4-builder-suite'),
                'description' => __('No-header/no-footer conversion page for campaigns, offers, waitlists, and focused checkout paths.', 'help4-builder-suite'),
                'page_layout' => 'canvas',
                'layout' => [
                    ['id' => 'pscl_hero', 'type' => 'section', 'settings' => ['columns' => 2, 'columns_mobile' => 1, 'column_widths' => '52|48', 'gap' => 40, 'min_height' => 760, 'content_width_mode' => 'custom', 'content_width' => 1180, 'content_width_unit' => 'px', 'align_items' => 'center', 'design_background_color_global' => 'heading', 'design_text_color_global' => 'on_primary', 'design_padding_top' => 72, 'design_padding_right' => 30, 'design_padding_bottom' => 72, 'design_padding_left' => 30]],
                    ['id' => 'pscl_offer', 'type' => 'cta', 'parent_id' => 'pscl_hero', 'slot' => 0, 'settings' => ['title' => 'A literal offer customers understand.', 'text' => 'State who it is for, what changes after they act, what is included, and what happens next.', 'button_label' => 'Start now', 'button_url' => '#form', 'design_text_color_global' => 'on_primary']],
                    ['id' => 'pscl_proof', 'type' => 'list', 'parent_id' => 'pscl_hero', 'slot' => 0, 'settings' => ['items' => "Clear result\nPlain-language scope\nVisible proof\nLow-friction next step", 'ordered' => '0', 'design_text_color_global' => 'on_primary']],
                    ['id' => 'pscl_form', 'type' => 'shortcode', 'parent_id' => 'pscl_hero', 'slot' => 1, 'settings' => ['shortcode' => '[h4_contact_form]', 'design_background_color_global' => 'surface', 'design_padding_top' => 32, 'design_padding_right' => 32, 'design_padding_bottom' => 32, 'design_padding_left' => 32, 'design_border_radius' => 8, 'design_border_radius_unit' => 'px']],
                ],
            ],
        ];
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function themePackCatalog(): array
    {
        $defaultThumb = H4BS_URL . 'assets/demo/responsive-devices-dark.svg';
        $packs = [
            [
                'slug' => 'help4-foundation',
                'label' => 'Help4 Foundation',
                'description' => 'Clean, balanced base pack for service and SaaS sites.',
                'industry' => 'General',
                'page_layout' => 'default',
                'template_slug' => 'local-business-home',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => ['primary' => '#0f6cbf', 'secondary' => '#0a9b7a', 'accent' => '#ff7a33', 'text' => '#1f2937', 'surface' => '#ffffff'],
                    'fonts' => ['heading' => 'Poppins', 'body' => 'Inter', 'base_size' => '16'],
                    'layout' => ['container_max_width' => '1200', 'container_max_width_unit' => 'px', 'structure_columns' => '2', 'structure_gap' => '20', 'structure_min_height' => '160'],
                ],
            ],
            [
                'slug' => 'help4-enterprise-dark',
                'label' => 'Enterprise Dark',
                'description' => 'High-contrast enterprise pack for agencies and managed hosting brands.',
                'industry' => 'Agency + Enterprise',
                'page_layout' => 'full_width',
                'template_slug' => 'local-business-home',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => ['primary' => '#1f78e0', 'secondary' => '#19b79e', 'accent' => '#ffb347', 'text' => '#e8f0f8', 'surface' => '#0f1d2a'],
                    'fonts' => ['heading' => 'Montserrat', 'body' => 'Open Sans', 'base_size' => '16'],
                    'layout' => ['container_max_width' => '1320', 'container_max_width_unit' => 'px', 'structure_columns' => '3', 'structure_gap' => '24', 'structure_min_height' => '180'],
                ],
            ],
            [
                'slug' => 'help4-contractor-pro',
                'label' => 'Contractor Pro',
                'description' => 'Quote-first layout pack for plumbers, roofers, HVAC, and field services.',
                'industry' => 'Contractor + Trades',
                'page_layout' => 'full_width',
                'template_slug' => 'local-business-home',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => ['primary' => '#c15e16', 'secondary' => '#2a7f4f', 'accent' => '#f2b130', 'text' => '#1d242b', 'surface' => '#fffdf8'],
                    'fonts' => ['heading' => 'Oswald', 'body' => 'Roboto', 'base_size' => '16'],
                    'layout' => ['container_max_width' => '1240', 'container_max_width_unit' => 'px', 'structure_columns' => '2', 'structure_gap' => '22', 'structure_min_height' => '170'],
                ],
            ],
            [
                'slug' => 'help4-realtor-luxe',
                'label' => 'Realtor Luxe',
                'description' => 'Listing-forward pack for agent teams with map/search focused sections.',
                'industry' => 'Realtor + IDX',
                'page_layout' => 'full_width',
                'template_slug' => 'realtor-landing',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => ['primary' => '#1a4f93', 'secondary' => '#2f8fb5', 'accent' => '#c68f2f', 'text' => '#1e2c3a', 'surface' => '#f7fafc'],
                    'fonts' => ['heading' => 'Playfair Display', 'body' => 'Lato', 'base_size' => '16'],
                    'layout' => ['container_max_width' => '1360', 'container_max_width_unit' => 'px', 'structure_columns' => '3', 'structure_gap' => '24', 'structure_min_height' => '180'],
                ],
            ],
            [
                'slug' => 'help4-clinic-care',
                'label' => 'Clinic Care',
                'description' => 'Patient-first visual pack for clinics, med spas, and healthcare teams.',
                'industry' => 'Healthcare',
                'page_layout' => 'full_width',
                'template_slug' => 'clinic-booking',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => ['primary' => '#1d6fa5', 'secondary' => '#2fb58f', 'accent' => '#ffd166', 'text' => '#1d2b3b', 'surface' => '#f6fbff'],
                    'fonts' => ['heading' => 'Montserrat', 'body' => 'Inter', 'base_size' => '16'],
                    'layout' => ['container_max_width' => '1320', 'container_max_width_unit' => 'px', 'structure_columns' => '3', 'structure_gap' => '22', 'structure_min_height' => '170'],
                ],
            ],
            [
                'slug' => 'help4-legal-shield',
                'label' => 'Legal Shield',
                'description' => 'Confident legal-theme styling for consultation and authority pages.',
                'industry' => 'Legal',
                'page_layout' => 'full_width',
                'template_slug' => 'law-firm-consult',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => ['primary' => '#1f3659', 'secondary' => '#3e5f8a', 'accent' => '#c6a35b', 'text' => '#1a2330', 'surface' => '#f9f8f4'],
                    'fonts' => ['heading' => 'Playfair Display', 'body' => 'Lato', 'base_size' => '16'],
                    'layout' => ['container_max_width' => '1280', 'container_max_width_unit' => 'px', 'structure_columns' => '2', 'structure_gap' => '20', 'structure_min_height' => '165'],
                ],
            ],
            [
                'slug' => 'help4-commerce-neo',
                'label' => 'Commerce Neo',
                'description' => 'Shop-focused pack with product-grid spacing and checkout-forward defaults.',
                'industry' => 'Commerce',
                'page_layout' => 'full_width',
                'template_slug' => 'store-front',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => ['primary' => '#4f46e5', 'secondary' => '#0ea5e9', 'accent' => '#f97316', 'text' => '#1f2937', 'surface' => '#ffffff'],
                    'fonts' => ['heading' => 'Poppins', 'body' => 'Inter', 'base_size' => '16'],
                    'layout' => ['container_max_width' => '1400', 'container_max_width_unit' => 'px', 'structure_columns' => '4', 'structure_gap' => '20', 'structure_min_height' => '160'],
                ],
            ],
            [
                'slug' => 'help4-catalog-command',
                'label' => 'Catalog Command',
                'description' => 'Dense but readable catalog pack for filters, comparison trays, quick-view product cards, and account checkout pages.',
                'industry' => 'Commerce',
                'page_layout' => 'full_width',
                'template_slug' => 'native-commerce-catalog',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => ['primary' => '#155e75', 'secondary' => '#0f766e', 'accent' => '#f59e0b', 'text' => '#10202b', 'surface' => '#f8fbfc'],
                    'fonts' => ['heading' => 'Montserrat', 'body' => 'Inter', 'base_size' => '16'],
                    'layout' => ['container_max_width' => '1440', 'container_max_width_unit' => 'px', 'structure_columns' => '4', 'structure_gap' => '18', 'structure_min_height' => '150'],
                ],
            ],
            [
                'slug' => 'help4-product-showcase',
                'label' => 'Product Showcase',
                'description' => 'Visual commerce pack for polished cards, image-forward product grids, comparison, and conversion CTAs.',
                'industry' => 'Commerce',
                'page_layout' => 'full_width',
                'template_slug' => 'product-showcase-comparison',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => ['primary' => '#1d4ed8', 'secondary' => '#0891b2', 'accent' => '#db2777', 'text' => '#172033', 'surface' => '#ffffff'],
                    'fonts' => ['heading' => 'Poppins', 'body' => 'Inter', 'base_size' => '16'],
                    'layout' => ['container_max_width' => '1360', 'container_max_width_unit' => 'px', 'structure_columns' => '4', 'structure_gap' => '24', 'structure_min_height' => '190'],
                ],
            ],
            [
                'slug' => 'help4-subscription-clarity',
                'label' => 'Subscription Clarity',
                'description' => 'Plan-first pack for recurring products, care plans, memberships, renewals, and support expectations.',
                'industry' => 'Commerce + Membership',
                'page_layout' => 'full_width',
                'template_slug' => 'subscription-offer-catalog',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => ['primary' => '#365314', 'secondary' => '#0f766e', 'accent' => '#7c3aed', 'text' => '#202719', 'surface' => '#fbfdf5'],
                    'fonts' => ['heading' => 'Poppins', 'body' => 'Inter', 'base_size' => '16'],
                    'layout' => ['container_max_width' => '1280', 'container_max_width_unit' => 'px', 'structure_columns' => '3', 'structure_gap' => '22', 'structure_min_height' => '170'],
                ],
            ],
            [
                'slug' => 'help4-checkout-recovery',
                'label' => 'Checkout Recovery',
                'description' => 'Support-forward pack for failed payments, restock questions, price-watch prompts, and account rescue pages.',
                'industry' => 'Commerce Support',
                'page_layout' => 'full_width',
                'template_slug' => 'checkout-recovery-support',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => ['primary' => '#9f1239', 'secondary' => '#1e40af', 'accent' => '#ea580c', 'text' => '#211821', 'surface' => '#fff8f9'],
                    'fonts' => ['heading' => 'Montserrat', 'body' => 'Inter', 'base_size' => '16'],
                    'layout' => ['container_max_width' => '1240', 'container_max_width_unit' => 'px', 'structure_columns' => '2', 'structure_gap' => '20', 'structure_min_height' => '160'],
                ],
            ],
            [
                'slug' => 'help4-membership-core',
                'label' => 'Membership Core',
                'description' => 'Portal-first token set for gated resources and member dashboards.',
                'industry' => 'Membership + Portal',
                'page_layout' => 'full_width',
                'template_slug' => 'membership-portal-launch',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => ['primary' => '#3b5bdb', 'secondary' => '#22b8cf', 'accent' => '#ffd43b', 'text' => '#1f2a37', 'surface' => '#f7f9ff'],
                    'fonts' => ['heading' => 'Poppins', 'body' => 'Inter', 'base_size' => '16'],
                    'layout' => ['container_max_width' => '1320', 'container_max_width_unit' => 'px', 'structure_columns' => '3', 'structure_gap' => '20', 'structure_min_height' => '160'],
                ],
            ],
            [
                'slug' => 'help4-handled-wordpress',
                'label' => 'Handled WordPress',
                'description' => 'Editorial managed-service system with a clear plan path, product-health visual, and plain-language outcomes.',
                'industry' => 'Managed WordPress',
                'page_layout' => 'full_width',
                'template_slug' => 'handled-wordpress-service',
                'replace_layout' => true,
                'thumbnail' => H4BS_URL . 'assets/demo/theme-pack-handled-wordpress.webp',
                'tokens' => [
                    'colors' => ['primary' => '#155eef', 'secondary' => '#12b76a', 'accent' => '#ff6b35', 'text' => '#081a38', 'surface' => '#ffffff'],
                    'fonts' => ['heading' => 'Montserrat', 'body' => 'Inter', 'base_size' => '17'],
                    'layout' => ['container_max_width' => '1240', 'container_max_width_unit' => 'px', 'structure_columns' => '2', 'structure_gap' => '32', 'structure_min_height' => '180'],
                ],
            ],
            [
                'slug' => 'help4-catalog-command-design',
                'label' => 'Catalog Command',
                'description' => 'Operational commerce design for filters, variations, subscriptions, comparisons, stock states, and checkout routing.',
                'industry' => 'Native Commerce',
                'page_layout' => 'full_width',
                'template_slug' => 'catalog-command-store',
                'replace_layout' => true,
                'thumbnail' => H4BS_URL . 'assets/demo/theme-pack-catalog-command.webp',
                'tokens' => [
                    'colors' => ['primary' => '#155eef', 'secondary' => '#0f766e', 'accent' => '#eab308', 'text' => '#111827', 'surface' => '#ffffff'],
                    'fonts' => ['heading' => 'Inter', 'body' => 'Inter', 'base_size' => '15'],
                    'layout' => ['container_max_width' => '1440', 'container_max_width_unit' => 'px', 'structure_columns' => '4', 'structure_gap' => '16', 'structure_min_height' => '150'],
                ],
            ],
            [
                'slug' => 'help4-proof-studio',
                'label' => 'Proof Studio',
                'description' => 'Image-led agency system for design handoffs, visible recent work, process proof, and project inquiries.',
                'industry' => 'Agency + Portfolio',
                'page_layout' => 'full_width',
                'template_slug' => 'proof-studio-portfolio',
                'replace_layout' => true,
                'thumbnail' => H4BS_URL . 'assets/demo/theme-pack-proof-studio.webp',
                'tokens' => [
                    'colors' => ['primary' => '#155eef', 'secondary' => '#039855', 'accent' => '#ff4d2e', 'text' => '#0b0d12', 'surface' => '#ffffff'],
                    'fonts' => ['heading' => 'Montserrat', 'body' => 'Inter', 'base_size' => '17'],
                    'layout' => ['container_max_width' => '1320', 'container_max_width_unit' => 'px', 'structure_columns' => '2', 'structure_gap' => '28', 'structure_min_height' => '190'],
                ],
            ],
            [
                'slug' => 'help4-speed-lite',
                'label' => 'Speed Lite',
                'description' => 'Lean performance preset with tighter spacing and lighter defaults.',
                'industry' => 'Performance',
                'page_layout' => 'default',
                'template_slug' => '',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => ['primary' => '#0f6cbf', 'secondary' => '#0a9b7a', 'accent' => '#ff7a33', 'text' => '#1d2935', 'surface' => '#ffffff'],
                    'fonts' => ['heading' => 'Poppins', 'body' => 'Inter', 'base_size' => '15'],
                    'layout' => ['container_max_width' => '1120', 'container_max_width_unit' => 'px', 'structure_columns' => '2', 'structure_gap' => '14', 'structure_min_height' => '120'],
                ],
            ],
            [
                'slug' => 'help4-holistic-studio',
                'label' => 'Holistic Studio',
                'description' => 'Warm high-contrast learning and membership palette with accessible pink, violet, and calm surface tokens.',
                'industry' => 'Learning + Membership',
                'page_layout' => 'full_width',
                'template_slug' => '',
                'replace_layout' => false,
                'thumbnail' => $defaultThumb,
                'tokens' => [
                    'colors' => [
                        'primary' => '#ff4fb8',
                        'primary_hover' => '#e6389f',
                        'on_primary' => '#25102d',
                        'secondary' => '#8b5cf6',
                        'secondary_hover' => '#7445df',
                        'on_secondary' => '#ffffff',
                        'accent' => '#ff2f92',
                        'heading' => '#25102d',
                        'text' => '#25102d',
                        'muted' => '#72556f',
                        'link' => '#a22974',
                        'link_hover' => '#7d1856',
                        'surface' => '#fff7fd',
                        'surface_alt' => '#f8eafa',
                        'border' => '#e9c9e2',
                    ],
                    'fonts' => ['display' => 'Poppins', 'heading' => 'Poppins', 'body' => 'Inter', 'ui' => 'Inter', 'base_size' => '16'],
                    'typography' => ['h1_size' => '54', 'h2_size' => '42', 'h3_size' => '32', 'heading_weight' => '700', 'body_weight' => '400', 'heading_line_height' => '1.12', 'body_line_height' => '1.65'],
                    'spacing' => ['page_gap' => '16', 'section_x' => '28', 'section_y' => '68', 'card_gap' => '22'],
                    'shape' => ['radius_sm' => '5', 'radius_md' => '8', 'radius_lg' => '16'],
                    'components' => ['button_padding_x' => '22', 'button_padding_y' => '12', 'button_radius' => '8', 'button_weight' => '700', 'card_padding' => '28', 'field_height' => '48', 'field_padding_x' => '14', 'field_radius' => '7'],
                    'layout' => ['container_max_width' => '1280', 'container_max_width_unit' => 'px', 'structure_columns' => '3', 'structure_gap' => '22', 'structure_min_height' => '170'],
                ],
            ],
        ];

        $catalog = [];
        foreach ($packs as $pack) {
            if (! is_array($pack)) {
                continue;
            }
            $slug = sanitize_key((string) ($pack['slug'] ?? ''));
            if ($slug === '') {
                continue;
            }
            $catalog[] = [
                'slug' => $slug,
                'label' => sanitize_text_field((string) ($pack['label'] ?? $slug)),
                'description' => sanitize_text_field((string) ($pack['description'] ?? '')),
                'industry' => sanitize_text_field((string) ($pack['industry'] ?? 'General')),
                'page_layout' => $this->normalizePageLayout((string) ($pack['page_layout'] ?? 'default')),
                'template_slug' => sanitize_key((string) ($pack['template_slug'] ?? '')),
                'replace_layout' => ! empty($pack['replace_layout']),
                'thumbnail' => esc_url_raw((string) ($pack['thumbnail'] ?? $defaultThumb)),
                'tokens' => $this->sanitizeGlobalDesignTokenPatch($pack['tokens'] ?? []),
            ];
        }

        $catalog = array_merge($catalog, $this->customThemePacks());
        $filtered = apply_filters('h4bs_builder_theme_pack_catalog', $catalog);
        return $this->sanitizeThemePackCatalog(is_array($filtered) ? $filtered : $catalog);
    }

    /**
     * @param mixed $raw
     * @return array<int, array<string, mixed>>
     */
    private function sanitizeThemePackCatalog($raw, bool $customOnly = false): array
    {
        if (is_string($raw)) {
            $decoded = json_decode($raw, true);
            $raw = is_array($decoded) ? $decoded : [];
        }
        $source = is_array($raw) ? $raw : [];
        $defaultThumb = H4BS_URL . 'assets/demo/responsive-devices-dark.svg';
        $catalog = [];
        $seen = [];
        foreach ($source as $pack) {
            if (! is_array($pack)) {
                continue;
            }
            $slug = sanitize_key((string) ($pack['slug'] ?? ''));
            if ($slug === '' || isset($seen[$slug])) {
                continue;
            }
            $seen[$slug] = true;
            $isCustom = $customOnly || ! empty($pack['custom']);
            $catalog[] = [
                'slug' => $slug,
                'label' => sanitize_text_field((string) ($pack['label'] ?? $slug)),
                'description' => sanitize_text_field((string) ($pack['description'] ?? '')),
                'industry' => sanitize_text_field((string) ($pack['industry'] ?? ($customOnly ? 'Custom' : 'General'))),
                'page_layout' => $this->normalizePageLayout((string) ($pack['page_layout'] ?? 'default')),
                'template_slug' => $isCustom ? '' : sanitize_key((string) ($pack['template_slug'] ?? '')),
                'replace_layout' => $isCustom ? false : ! empty($pack['replace_layout']),
                'thumbnail' => esc_url_raw((string) ($pack['thumbnail'] ?? $defaultThumb)),
                'tokens' => $this->sanitizeGlobalDesignTokenPatch($pack['tokens'] ?? []),
                'custom' => $isCustom,
            ];
        }
        return array_slice($catalog, 0, 100);
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function customThemePacks(): array
    {
        return $this->sanitizeThemePackCatalog(get_option('h4bs_custom_theme_packs', []), true);
    }

    /**
     * @param mixed $raw
     * @return array<string, array<string, string>>
     */
    private function sanitizeGlobalDesignTokenPatch($raw): array
    {
        if (is_string($raw)) {
            $decoded = json_decode($raw, true);
            $raw = is_array($decoded) ? $decoded : [];
        }
        $source = is_array($raw) ? $raw : [];
        $normalized = $this->sanitizeGlobalDesignTokens($source);
        $patch = [];
        foreach (['colors', 'fonts', 'typography', 'spacing', 'shape', 'components', 'layout'] as $group) {
            $sourceGroup = is_array($source[$group] ?? null) ? $source[$group] : [];
            $normalizedGroup = is_array($normalized[$group] ?? null) ? $normalized[$group] : [];
            foreach (array_keys($sourceGroup) as $key) {
                $safeKey = sanitize_key((string) $key);
                if ($safeKey !== '' && array_key_exists($safeKey, $normalizedGroup)) {
                    $patch[$group][$safeKey] = (string) $normalizedGroup[$safeKey];
                }
            }
        }
        return $patch;
    }

    /**
     * @return array<int, array{slug:string,label:string,url:string,category:string}>
     */
    private function defaultIconCatalog(): array
    {
        $icons = [];
        $manifest = H4BS_PATH . 'assets/icons/defaults/icons.json';
        if (is_readable($manifest)) {
            $decoded = json_decode((string) file_get_contents($manifest), true);
            if (is_array($decoded)) {
                $icons = array_values(
                    array_filter(
                        $decoded,
                        static fn ($icon): bool => is_array($icon)
                            && sanitize_key((string) ($icon['slug'] ?? '')) !== ''
                    )
                );
            }
        }

        if ($icons === []) {
            $icons = [
                ['slug' => 'managed-hosting', 'label' => 'Managed Hosting', 'category' => 'WordPress'],
                ['slug' => 'builder-blocks', 'label' => 'Builder Blocks', 'category' => 'Builder'],
                ['slug' => 'template-pack', 'label' => 'Template Pack', 'category' => 'Builder'],
                ['slug' => 'speed-tuning', 'label' => 'Speed Tuning', 'category' => 'Performance'],
                ['slug' => 'security-shield', 'label' => 'Security Shield', 'category' => 'Security'],
                ['slug' => 'migration-path', 'label' => 'Migration Path', 'category' => 'Support'],
                ['slug' => 'support-ticket', 'label' => 'Support Ticket', 'category' => 'Support'],
                ['slug' => 'seo-growth', 'label' => 'SEO Growth', 'category' => 'Marketing'],
                ['slug' => 'commerce-cart', 'label' => 'Commerce Cart', 'category' => 'Commerce'],
                ['slug' => 'checkout-link', 'label' => 'Checkout Link', 'category' => 'Commerce'],
                ['slug' => 'backup-restore', 'label' => 'Backup Restore', 'category' => 'Operations'],
                ['slug' => 'launch-rocket', 'label' => 'Launch Rocket', 'category' => 'Launch'],
            ];
        }

        return array_map(
            fn (array $icon): array => [
                'slug' => sanitize_key((string) $icon['slug']),
                'label' => sanitize_text_field((string) $icon['label']),
                'category' => sanitize_text_field((string) $icon['category']),
                'url' => $this->defaultIconUrl((string) $icon['slug']),
            ],
            $icons
        );
    }

    private function defaultIconUrl(string $slug): string
    {
        $slug = sanitize_key($slug);
        if ($slug === '') {
            $slug = 'managed-hosting';
        }

        if (! is_readable(H4BS_PATH . 'assets/icons/defaults/' . $slug . '.svg')) {
            $slug = 'managed-hosting';
        }

        return H4BS_URL . 'assets/icons/defaults/' . $slug . '.svg';
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function moduleSkinCatalog(): array
    {
        $contractVersion = '1.0';
        $catalog = [
            ['module_slug' => 'library', 'skin_slug' => 'library', 'controls' => ['colors', 'typography', 'spacing', 'cards'], 'style_handle' => '', 'script_handles' => [], 'shortcode_tags' => ['h4_lms_courses']],
            ['module_slug' => 'course-cards', 'skin_slug' => 'cards', 'controls' => ['colors', 'typography', 'spacing', 'shape'], 'style_handle' => '', 'script_handles' => [], 'shortcode_tags' => ['h4_lms_courses']],
            ['module_slug' => 'membership', 'skin_slug' => 'membership', 'controls' => ['colors', 'typography', 'spacing', 'forms'], 'style_handle' => '', 'script_handles' => [], 'shortcode_tags' => []],
            ['module_slug' => 'checkout', 'skin_slug' => 'checkout', 'controls' => ['colors', 'typography', 'spacing', 'forms', 'buttons'], 'style_handle' => '', 'script_handles' => [], 'shortcode_tags' => ['h4_checkout']],
            ['module_slug' => 'dashboard', 'skin_slug' => 'dashboard', 'controls' => ['colors', 'typography', 'spacing', 'cards'], 'style_handle' => '', 'script_handles' => [], 'shortcode_tags' => ['h4_lms_progress']],
            ['module_slug' => 'forms', 'skin_slug' => 'forms', 'controls' => ['colors', 'typography', 'spacing', 'fields', 'buttons'], 'style_handle' => '', 'script_handles' => [], 'shortcode_tags' => ['h4_contact_form']],
            ['module_slug' => 'progress', 'skin_slug' => 'progress', 'controls' => ['colors', 'spacing', 'shape'], 'style_handle' => '', 'script_handles' => [], 'shortcode_tags' => ['h4_lms_progress']],
            ['module_slug' => 'certificates', 'skin_slug' => 'certificate', 'controls' => ['colors', 'typography', 'spacing', 'cards'], 'style_handle' => '', 'script_handles' => [], 'shortcode_tags' => ['h4_lms_certificate']],
            ['module_slug' => 'video', 'skin_slug' => 'video', 'controls' => ['colors', 'spacing', 'shape', 'motion'], 'style_handle' => '', 'script_handles' => ['h4bs-lms-video'], 'shortcode_tags' => ['h4_lms_video', 'holistic_lms_video']],
            ['module_slug' => 'protected-pdf', 'skin_slug' => 'protected-pdf', 'controls' => ['colors', 'spacing', 'shape'], 'style_handle' => '', 'script_handles' => [], 'shortcode_tags' => []],
        ];
        foreach ($catalog as &$contract) {
            $contract['contract_version'] = $contractVersion;
        }
        unset($contract);

        $filtered = apply_filters('h4bs_builder_module_catalog', $catalog, $contractVersion);
        $source = is_array($filtered) ? $filtered : $catalog;
        $normalized = [];
        $seen = [];
        foreach ($source as $contract) {
            if (! is_array($contract)) {
                continue;
            }
            $moduleSlug = sanitize_key((string) ($contract['module_slug'] ?? ''));
            if ($moduleSlug === '' || isset($seen[$moduleSlug])) {
                continue;
            }
            $seen[$moduleSlug] = true;
            $normalized[] = [
                'module_slug' => $moduleSlug,
                'skin_slug' => sanitize_key((string) ($contract['skin_slug'] ?? $moduleSlug)),
                'controls' => array_values(array_filter(array_map('sanitize_key', (array) ($contract['controls'] ?? [])))),
                'style_handle' => sanitize_key((string) ($contract['style_handle'] ?? '')),
                'script_handles' => array_values(array_filter(array_map('sanitize_key', (array) ($contract['script_handles'] ?? [])))),
                'shortcode_tags' => array_values(array_filter(array_map('sanitize_key', (array) ($contract['shortcode_tags'] ?? [])))),
                'contract_version' => sanitize_text_field((string) ($contract['contract_version'] ?? $contractVersion)),
            ];
        }
        return $normalized;
    }

    /**
     * @return array<string, mixed>
     */
    private function previewAssetManifest(): array
    {
        $styles = ['h4bs-frontend', 'h4bs-builder', 'h4bs-builder-module-skins', 'h4bs-fontawesome-free-frontend'];
        $scripts = ['h4bs-builder-frontend'];
        foreach ($this->moduleSkinCatalog() as $contract) {
            $styleHandle = sanitize_key((string) ($contract['style_handle'] ?? ''));
            if ($styleHandle !== '') {
                $styles[] = $styleHandle;
            }
            foreach ((array) ($contract['script_handles'] ?? []) as $scriptHandle) {
                $safeHandle = sanitize_key((string) $scriptHandle);
                if ($safeHandle !== '') {
                    $scripts[] = $safeHandle;
                }
            }
        }
        $manifest = [
            'contract_version' => '1.0',
            'styles' => array_values(array_unique($styles)),
            'scripts' => array_values(array_unique($scripts)),
            'modules' => $this->moduleSkinCatalog(),
        ];
        $filtered = apply_filters('h4bs_builder_preview_asset_manifest', $manifest, '1.0');
        return is_array($filtered) ? $filtered : $manifest;
    }

    private function imageAltTextFromUrl(string $url): string
    {
        $path = (string) wp_parse_url($url, PHP_URL_PATH);
        $name = pathinfo($path, PATHINFO_FILENAME);
        $name = is_string($name) ? $name : '';
        $name = (string) preg_replace('/[-_]+/', ' ', $name);
        $name = (string) preg_replace('/\b(crop|cropped|scaled|final|image|img|photo|screenshot)\b/i', ' ', $name);
        $name = (string) preg_replace('/\b20\d{4,}\b/', ' ', $name);
        $name = trim((string) preg_replace('/\s+/', ' ', $name));

        if ($name === '') {
            return __('Gallery image', 'help4-builder-suite');
        }

        return sanitize_text_field(ucwords($name) . ' image');
    }

    private function activeThemePackSlug(): string
    {
        $slug = sanitize_key((string) get_option('h4bs_active_theme_pack', ''));
        if ($slug === '') {
            return '';
        }
        return $this->findThemePack($slug) ? $slug : '';
    }

    /**
     * @return array<string, mixed>|null
     */
    private function findThemePack(string $slug): ?array
    {
        $needle = sanitize_key($slug);
        if ($needle === '') {
            return null;
        }
        foreach ($this->themePackCatalog() as $pack) {
            if ((string) ($pack['slug'] ?? '') === $needle) {
                return $pack;
            }
        }
        return null;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function widgetCatalog(): array
    {
        if ($this->runtimeWidgetCatalog !== null) {
            return $this->runtimeWidgetCatalog;
        }

        $catalog = [
            ['slug' => 'search', 'label' => 'Advanced Search', 'category' => 'Commerce + Content', 'shortcode' => '[h4_search type="all" placeholder="Search products, posts, pages..."]', 'status' => 'ready'],
            ['slug' => 'website-snapshot', 'label' => 'Website Snapshot', 'category' => 'Content Widgets', 'shortcode' => '[h4_website_snapshot url="https://example.com" width="1200" height="760" fit="cover" link="true"]', 'status' => 'ready'],
            ['slug' => 'products-grid', 'label' => 'Products Grid', 'category' => 'Commerce + Content', 'shortcode' => '[h4_products limit="12"]', 'status' => 'ready'],
            ['slug' => 'products-quick-view-grid', 'label' => 'Quick View Product Grid', 'category' => 'Commerce + Content', 'shortcode' => '[h4_products limit="12" card_style="overlay" hover="quick_actions" show_filters="1" show_quick_view="1" show_compare="1" show_price_watch="1" show_waitlist="1"]', 'status' => 'ready'],
            ['slug' => 'products-compact-list', 'label' => 'Compact Product List', 'category' => 'Commerce + Content', 'shortcode' => '[h4_products limit="24" card_style="list" hover="none" columns="1" columns_tablet="1" columns_mobile="1" show_filters="1" filter_style="compact" show_quick_view="1" show_compare="0"]', 'status' => 'ready'],
            ['slug' => 'products-showcase-cards', 'label' => 'Showcase Product Cards', 'category' => 'Commerce + Content', 'shortcode' => '[h4_products limit="8" card_style="showcase" hover="image_zoom" columns="4" columns_tablet="2" columns_mobile="1" show_filters="0" show_quick_view="1" show_compare="1"]', 'status' => 'ready'],
            ['slug' => 'products-filter-drawer', 'label' => 'Catalog Filter Drawer', 'category' => 'Commerce + Content', 'shortcode' => '[h4_products limit="20" card_style="clean" hover="lift" show_filters="1" filter_style="drawer" cursor="1" show_mobile_toolbar="1"]', 'status' => 'ready'],
            ['slug' => 'products-restock-watch', 'label' => 'Restock + Price Watch Products', 'category' => 'Commerce + Content', 'shortcode' => '[h4_products limit="12" in_stock_only="0" card_style="compact" show_quick_view="1" show_price_watch="1" show_waitlist="1" request_anchor="h4bs-contact"]', 'status' => 'ready'],
            ['slug' => 'products-subscription-catalog', 'label' => 'Subscription Catalog', 'category' => 'Commerce + Content', 'shortcode' => '[h4_products limit="12" search="subscription" search_mode="contains" card_style="showcase" show_type_badges="1" show_quick_view="1" show_compare="1"]', 'status' => 'ready'],
            ['slug' => 'products-digital-downloads', 'label' => 'Digital Downloads Catalog', 'category' => 'Commerce + Content', 'shortcode' => '[h4_products limit="12" search="digital" search_mode="contains" card_style="clean" show_type_badges="1" show_quick_view="1" show_compare="0"]', 'status' => 'ready'],
            ['slug' => 'products-large-catalog', 'label' => 'Large Catalog Cursor Grid', 'category' => 'Commerce + Content', 'shortcode' => '[h4_products limit="48" card_style="clean" hover="lift" show_filters="1" filter_style="toolbar" cursor="1" columns="4" columns_tablet="2" columns_mobile="1"]', 'status' => 'ready'],
            ['slug' => 'checkout', 'label' => 'Checkout Shell', 'category' => 'Commerce + Content', 'shortcode' => '[h4_checkout]', 'status' => 'ready'],
            ['slug' => 'checkout-connected-path', 'label' => 'Connected Checkout Path', 'category' => 'Commerce + Content', 'shortcode' => '[h4_checkout]', 'status' => 'ready'],
            ['slug' => 'slider', 'label' => 'Advanced Slider', 'category' => 'Design + Motion', 'shortcode' => '[h4_slider id="1" autoplay="3500"]', 'status' => 'ready'],
            ['slug' => 'template-library', 'label' => 'Template Library', 'category' => 'Design + Motion', 'shortcode' => '[h4_template_library limit="12"]', 'status' => 'ready'],
            ['slug' => 'featured-template', 'label' => 'Template Section', 'category' => 'Design + Motion', 'shortcode' => '[h4_template id="1"]', 'status' => 'ready'],
            ['slug' => 'popup-template', 'label' => 'Popup Template', 'category' => 'Design + Motion', 'shortcode' => '[h4_popup id="1"]', 'status' => 'ready'],
            ['slug' => 'dynamic-field', 'label' => 'Dynamic Field Output', 'category' => 'Content Widgets', 'shortcode' => '[h4_field key="hero_subtitle" fallback=""]', 'status' => 'ready'],
            ['slug' => 'dynamic-field-image', 'label' => 'Dynamic Field Image', 'category' => 'Content Widgets', 'shortcode' => '[h4_field key="hero_image" format="image"]', 'status' => 'ready'],
            ['slug' => 'dynamic-field-html', 'label' => 'Dynamic Field HTML', 'category' => 'Content Widgets', 'shortcode' => '[h4_field key="hero_content" format="html"]', 'status' => 'ready'],
            ['slug' => 'dynamic-field-json', 'label' => 'Dynamic Field JSON', 'category' => 'Content Widgets', 'shortcode' => '[h4_field key="feature_items" format="json"]', 'status' => 'ready'],
            ['slug' => 'dynamic-field-list', 'label' => 'Dynamic Field List', 'category' => 'Content Widgets', 'shortcode' => '[h4_field key="feature_list" format="list" separator=" | "]', 'status' => 'ready'],
            ['slug' => 'dynamic-data', 'label' => 'Dynamic Data', 'category' => 'Content Widgets', 'shortcode' => '[h4_dynamic source="post" key="title" fallback=""]', 'status' => 'ready'],
            ['slug' => 'dynamic-product-data', 'label' => 'Dynamic Product Data', 'category' => 'Commerce + Content', 'shortcode' => '[h4_dynamic source="product" product_id="1" key="price" transform="currency:USD"]', 'status' => 'ready'],
            ['slug' => 'dynamic-variant-data', 'label' => 'Dynamic Variant Data', 'category' => 'Commerce + Content', 'shortcode' => '[h4_dynamic source="variant" product_id="1" variant_id="1" key="attributes" format="list"]', 'status' => 'ready'],
            ['slug' => 'dynamic-taxonomy-data', 'label' => 'Dynamic Taxonomy Data', 'category' => 'Content Widgets', 'shortcode' => '[h4_dynamic source="term" taxonomy="category" key="name"]', 'status' => 'ready'],
            ['slug' => 'dynamic-author-data', 'label' => 'Dynamic Author Data', 'category' => 'Content Widgets', 'shortcode' => '[h4_dynamic source="user" key="display_name"]', 'status' => 'ready'],
            ['slug' => 'lms-courses', 'label' => 'LMS Course Catalog', 'category' => 'LMS + Certification', 'shortcode' => '[h4_lms_courses limit="12"]', 'status' => 'ready'],
            ['slug' => 'lms-progress', 'label' => 'LMS Progress Dashboard', 'category' => 'LMS + Certification', 'shortcode' => '[h4_lms_progress]', 'status' => 'ready'],
            ['slug' => 'lms-video', 'label' => 'Protected Course Video', 'category' => 'LMS + Certification', 'shortcode' => '[h4_lms_video access="logged_in" context="lesson"]', 'status' => 'ready'],
            ['slug' => 'lms-certificate', 'label' => 'Certificate Verifier', 'category' => 'LMS + Certification', 'shortcode' => '[h4_lms_certificate code="H4CERT-XXXX"]', 'status' => 'ready'],
            ['slug' => 'faq-schema', 'label' => 'FAQ Schema Block', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_faq title="Popular Questions"]', 'status' => 'ready'],
            ['slug' => 'pricing-table', 'label' => 'Pricing Table', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_pricing_table plan="starter"]', 'status' => 'ready'],
            ['slug' => 'comparison', 'label' => 'Comparison Table', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_comparison_table id="1"]', 'status' => 'ready'],
            ['slug' => 'contact-form', 'label' => 'Contact Form', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_contact_form]', 'status' => 'ready'],
            ['slug' => 'newsletter-form', 'label' => 'Newsletter Form', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_newsletter_form]', 'status' => 'ready'],
            ['slug' => 'lead-form', 'label' => 'Lead Capture Form', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_form id="1"]', 'status' => 'ready'],
            ['slug' => 'booking', 'label' => 'Booking Scheduler', 'category' => 'Booking + CRM', 'shortcode' => '[h4_booking id="1"]', 'status' => 'ready'],
            ['slug' => 'quote-request', 'label' => 'Quote Request Form', 'category' => 'Booking + CRM', 'shortcode' => '[h4_quote_request id="1"]', 'status' => 'ready'],
            ['slug' => 'callback-request', 'label' => 'Callback Request', 'category' => 'Booking + CRM', 'shortcode' => '[h4_callback_request id="1"]', 'status' => 'ready'],
            ['slug' => 'pipeline-board', 'label' => 'CRM Pipeline Board', 'category' => 'Booking + CRM', 'shortcode' => '[h4_crm_pipeline id="1"]', 'status' => 'ready'],
            ['slug' => 'realtor-map', 'label' => 'Realtor Map Search', 'category' => 'Vertical Packs', 'shortcode' => '[h4_realtor_map_search id="1"]', 'status' => 'ready'],
            ['slug' => 'featured-listings', 'label' => 'Featured Listings', 'category' => 'Vertical Packs', 'shortcode' => '[h4_featured_listings limit="9"]', 'status' => 'ready'],
            ['slug' => 'contractor-services', 'label' => 'Contractor Services Grid', 'category' => 'Vertical Packs', 'shortcode' => '[h4_services_grid id="1"]', 'status' => 'ready'],
            ['slug' => 'before-after', 'label' => 'Before / After Gallery', 'category' => 'Vertical Packs', 'shortcode' => '[h4_before_after_gallery id="1"]', 'status' => 'ready'],
            ['slug' => 'review-wall', 'label' => 'Review Wall', 'category' => 'Social + Trust', 'shortcode' => '[h4_review_wall limit="20"]', 'status' => 'ready'],
            ['slug' => 'social-feed', 'label' => 'Social Feed', 'category' => 'Social + Trust', 'shortcode' => '[h4_social_feed source="instagram"]', 'status' => 'ready'],
            ['slug' => 'team-cards', 'label' => 'Team Cards', 'category' => 'Content Widgets', 'shortcode' => '[h4_team limit="6"]', 'status' => 'ready'],
            ['slug' => 'testimonials', 'label' => 'Testimonials', 'category' => 'Content Widgets', 'shortcode' => '[h4_testimonials limit="8"]', 'status' => 'ready'],
            ['slug' => 'tabs', 'label' => 'Tabs', 'category' => 'Content Widgets', 'shortcode' => '[h4_tabs id="1"]', 'status' => 'ready'],
            ['slug' => 'accordion', 'label' => 'Accordion', 'category' => 'Content Widgets', 'shortcode' => '[h4_accordion id="1"]', 'status' => 'ready'],
            ['slug' => 'mega-menu', 'label' => 'Mega Menu', 'category' => 'Navigation', 'shortcode' => '[h4_mega_menu id="1"]', 'status' => 'ready'],
            ['slug' => 'directory', 'label' => 'Business Directory', 'category' => 'Portal + Membership', 'shortcode' => '[h4_directory id="1"]', 'status' => 'ready'],
            ['slug' => 'member-dashboard', 'label' => 'Member Dashboard', 'category' => 'Portal + Membership', 'shortcode' => '[h4_member_dashboard]', 'status' => 'ready'],
            ['slug' => 'downloads', 'label' => 'Digital Downloads', 'category' => 'Commerce + Content', 'shortcode' => '[h4_downloads customer="current"]', 'status' => 'ready'],
            ['slug' => 'wishlist', 'label' => 'Wishlist', 'category' => 'Commerce + Content', 'shortcode' => '[h4_wishlist]', 'status' => 'ready'],
            ['slug' => 'recently-viewed', 'label' => 'Recently Viewed Products', 'category' => 'Commerce + Content', 'shortcode' => '[h4_recently_viewed_products limit="8"]', 'status' => 'ready'],
            ['slug' => 'product-filters', 'label' => 'AJAX Product Filters', 'category' => 'Commerce + Content', 'shortcode' => '[h4_product_filters]', 'status' => 'ready'],
            ['slug' => 'product-compare', 'label' => 'Product Comparison', 'category' => 'Commerce + Content', 'shortcode' => '[h4_product_compare]', 'status' => 'ready'],
            ['slug' => 'abandoned-cart', 'label' => 'Abandoned Cart Recovery', 'category' => 'Commerce + Content', 'shortcode' => '[h4_abandoned_cart_recovery]', 'status' => 'ready'],
            ['slug' => 'one-page-checkout', 'label' => 'One Page Checkout', 'category' => 'Commerce + Content', 'shortcode' => '[h4_one_page_checkout]', 'status' => 'ready'],
            ['slug' => 'subscriptions', 'label' => 'Subscriptions', 'category' => 'Commerce + Content', 'shortcode' => '[h4_subscriptions_dashboard]', 'status' => 'ready'],
            ['slug' => 'gift-cards', 'label' => 'Gift Cards', 'category' => 'Commerce + Content', 'shortcode' => '[h4_gift_cards]', 'status' => 'ready'],
            ['slug' => 'order-history', 'label' => 'Customer Order History', 'category' => 'Commerce + Content', 'shortcode' => '[h4_order_history]', 'status' => 'ready'],
            ['slug' => 'product-reviews', 'label' => 'Product Reviews (Verified)', 'category' => 'Commerce + Content', 'block_type' => 'product_reviews', 'settings' => ['product_id' => '0', 'limit' => '5', 'show_form' => '1', 'show_summary' => '1', 'title' => '', 'empty_text' => ''], 'status' => 'ready'],
            ['slug' => 'cart-indicator', 'label' => 'Cart Indicator', 'category' => 'Commerce + Content', 'block_type' => 'commerce_indicator', 'settings' => ['source' => 'cart', 'label' => 'Cart', 'url' => '/cart/', 'show_when_zero' => '0', 'show_quantity' => '1', 'show_label' => '0', 'max_count' => '99'], 'status' => 'ready'],
            ['slug' => 'wishlist-indicator', 'label' => 'Wishlist Indicator', 'category' => 'Commerce + Content', 'block_type' => 'commerce_indicator', 'settings' => ['source' => 'wishlist', 'label' => 'Wishlist', 'url' => '/wishlist/', 'show_when_zero' => '0', 'show_quantity' => '1', 'show_label' => '0', 'max_count' => '99'], 'status' => 'ready'],
            ['slug' => 'compare-indicator', 'label' => 'Compare Indicator', 'category' => 'Commerce + Content', 'block_type' => 'commerce_indicator', 'settings' => ['source' => 'compare', 'label' => 'Compare', 'url' => '/compare/', 'show_when_zero' => '0', 'show_quantity' => '1', 'show_label' => '0', 'max_count' => '99'], 'status' => 'ready'],
            ['slug' => 'notification-indicator', 'label' => 'Notification Indicator', 'category' => 'Commerce + Content', 'block_type' => 'commerce_indicator', 'settings' => ['source' => 'notifications', 'label' => 'Notifications', 'url' => '/account/', 'show_when_zero' => '1', 'show_quantity' => '1', 'show_label' => '0', 'max_count' => '99'], 'status' => 'ready'],
            ['slug' => 'inventory-status', 'label' => 'Inventory Status Board', 'category' => 'Commerce + Content', 'shortcode' => '[h4_inventory_status]', 'status' => 'ready'],
            ['slug' => 'gateway-status', 'label' => 'Payment Gateway Status', 'category' => 'Payments', 'shortcode' => '[h4_gateway_status]', 'status' => 'ready'],
            ['slug' => 'stripe-express', 'label' => 'Stripe Express Checkout', 'category' => 'Payments', 'shortcode' => '[h4_stripe_express_checkout]', 'status' => 'ready'],
            ['slug' => 'paypal-express', 'label' => 'PayPal Express Checkout', 'category' => 'Payments', 'shortcode' => '[h4_paypal_express_checkout]', 'status' => 'ready'],
            ['slug' => 'square-checkout', 'label' => 'Square Checkout', 'category' => 'Payments', 'shortcode' => '[h4_square_checkout]', 'status' => 'ready'],
            ['slug' => 'braintree-checkout', 'label' => 'Braintree Checkout', 'category' => 'Payments', 'shortcode' => '[h4_braintree_checkout]', 'status' => 'ready'],
            ['slug' => 'wise-payments', 'label' => 'Wise Payments', 'category' => 'Payments', 'shortcode' => '[h4_wise_payments]', 'status' => 'ready'],
            ['slug' => 'authorize-payments', 'label' => 'Authorize.Net Payments', 'category' => 'Payments', 'shortcode' => '[h4_authorize_net_checkout]', 'status' => 'ready'],
            ['slug' => 'local-seo-checklist', 'label' => 'Local SEO Checklist', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_local_seo_checklist]', 'status' => 'ready'],
            ['slug' => 'schema-generator', 'label' => 'Schema Generator', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_schema_generator]', 'status' => 'ready'],
            ['slug' => 'breadcrumbs', 'label' => 'Breadcrumbs', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_breadcrumbs]', 'status' => 'ready'],
            ['slug' => 'utm-tracker', 'label' => 'UTM Tracker', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_utm_tracker]', 'status' => 'ready'],
            ['slug' => 'heatmap', 'label' => 'Heatmap Snapshot', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_heatmap_report]', 'status' => 'ready'],
            ['slug' => 'ab-testing', 'label' => 'A/B Test Launcher', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_ab_test_launcher]', 'status' => 'ready'],
            ['slug' => 'funnel-report', 'label' => 'Funnel Report', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_funnel_report]', 'status' => 'ready'],
            ['slug' => 'traffic-overview', 'label' => 'Traffic Overview', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_traffic_overview]', 'status' => 'ready'],
            ['slug' => 'scroll-depth', 'label' => 'Scroll Depth Report', 'category' => 'Marketing + SEO', 'shortcode' => '[h4_scroll_depth_report]', 'status' => 'ready'],
            ['slug' => 'booking-calendar', 'label' => 'Booking Calendar', 'category' => 'Booking + CRM', 'shortcode' => '[h4_booking_calendar]', 'status' => 'ready'],
            ['slug' => 'appointment-list', 'label' => 'Appointments', 'category' => 'Booking + CRM', 'shortcode' => '[h4_appointments]', 'status' => 'ready'],
            ['slug' => 'crm-leads', 'label' => 'CRM Leads', 'category' => 'Booking + CRM', 'shortcode' => '[h4_crm_leads]', 'status' => 'ready'],
            ['slug' => 'crm-pipeline', 'label' => 'CRM Opportunity Pipeline', 'category' => 'Booking + CRM', 'shortcode' => '[h4_crm_opportunities]', 'status' => 'ready'],
            ['slug' => 'sms-reminders', 'label' => 'SMS Reminder Queue', 'category' => 'Booking + CRM', 'shortcode' => '[h4_sms_reminders]', 'status' => 'ready'],
            ['slug' => 'email-automations', 'label' => 'Email Automations', 'category' => 'Booking + CRM', 'shortcode' => '[h4_email_automations]', 'status' => 'ready'],
            ['slug' => 'realtor-listings', 'label' => 'Property Listings', 'category' => 'Vertical Packs', 'shortcode' => '[h4_property_listings limit="12"]', 'status' => 'ready'],
            ['slug' => 'idx-mls-search', 'label' => 'IDX/MLS Search', 'category' => 'Vertical Packs', 'shortcode' => '[h4_idx_search]', 'status' => 'ready'],
            ['slug' => 'open-house', 'label' => 'Open House Scheduler', 'category' => 'Vertical Packs', 'shortcode' => '[h4_open_house_schedule]', 'status' => 'ready'],
            ['slug' => 'mortgage-calc', 'label' => 'Mortgage Calculator', 'category' => 'Vertical Packs', 'shortcode' => '[h4_mortgage_calculator]', 'status' => 'ready'],
            ['slug' => 'service-area', 'label' => 'Service Area Map', 'category' => 'Vertical Packs', 'shortcode' => '[h4_service_area_map]', 'status' => 'ready'],
            ['slug' => 'estimate-builder', 'label' => 'Estimate Builder', 'category' => 'Vertical Packs', 'shortcode' => '[h4_estimate_builder]', 'status' => 'ready'],
            ['slug' => 'tech-profiles', 'label' => 'Technician Profiles', 'category' => 'Vertical Packs', 'shortcode' => '[h4_technician_profiles]', 'status' => 'ready'],
            ['slug' => 'project-showcase', 'label' => 'Project Showcase', 'category' => 'Vertical Packs', 'shortcode' => '[h4_project_showcase]', 'status' => 'ready'],
            ['slug' => 'maintenance-plans', 'label' => 'Maintenance Plans', 'category' => 'Vertical Packs', 'shortcode' => '[h4_maintenance_plans]', 'status' => 'ready'],
            ['slug' => 'social-proof', 'label' => 'Social Proof Widget', 'category' => 'Social + Trust', 'shortcode' => '[h4_social_proof]', 'status' => 'ready'],
            ['slug' => 'google-business-profile', 'label' => 'Google Business Profile', 'category' => 'Social + Trust', 'block_type' => 'google_business', 'settings' => ['domain' => '', 'view' => 'business', 'reviews' => '5', 'location' => '1', 'height' => '360', 'show_map' => '1', 'show_reviews' => '1', 'show_header' => '1', 'show_aggregate' => '1', 'show_service_areas' => '1', 'empty_text' => ''], 'status' => 'ready'],
            ['slug' => 'google-reviews', 'label' => 'Google Reviews Feed', 'category' => 'Social + Trust', 'shortcode' => '[h4_google_reviews]', 'status' => 'ready'],
            ['slug' => 'yelp-reviews', 'label' => 'Yelp Reviews Feed', 'category' => 'Social + Trust', 'shortcode' => '[h4_yelp_reviews]', 'status' => 'ready'],
            ['slug' => 'click-to-call', 'label' => 'Click To Call', 'category' => 'Social + Trust', 'shortcode' => '[h4_click_to_call]', 'status' => 'ready'],
            ['slug' => 'directions', 'label' => 'Directions Button', 'category' => 'Social + Trust', 'shortcode' => '[h4_click_for_directions]', 'status' => 'ready'],
            ['slug' => 'share-buttons', 'label' => 'Share Buttons', 'category' => 'Social + Trust', 'shortcode' => '[h4_share_buttons]', 'status' => 'ready'],
            ['slug' => 'resource-library', 'label' => 'Resource Library', 'category' => 'Portal + Membership', 'shortcode' => '[h4_resource_library]', 'status' => 'ready'],
            ['slug' => 'member-directory', 'label' => 'Member Directory', 'category' => 'Portal + Membership', 'shortcode' => '[h4_member_directory]', 'status' => 'ready'],
            ['slug' => 'customer-dashboard', 'label' => 'Customer Dashboard', 'category' => 'Portal + Membership', 'shortcode' => '[h4_customer_dashboard]', 'status' => 'ready'],
            ['slug' => 'vendor-dashboard', 'label' => 'Vendor Dashboard', 'category' => 'Portal + Membership', 'shortcode' => '[h4_vendor_dashboard]', 'status' => 'ready'],
            ['slug' => 'support-tickets', 'label' => 'Support Tickets', 'category' => 'Portal + Membership', 'shortcode' => '[h4_support_tickets]', 'status' => 'ready'],
            ['slug' => 'cookie-consent', 'label' => 'Cookie Consent', 'category' => 'Compliance', 'shortcode' => '[h4_cookie_consent]', 'status' => 'ready'],
            ['slug' => 'privacy-request', 'label' => 'Privacy Request Form', 'category' => 'Compliance', 'shortcode' => '[h4_privacy_request_form]', 'status' => 'ready'],
            ['slug' => 'accessibility-check', 'label' => 'Accessibility Checker', 'category' => 'Compliance', 'shortcode' => '[h4_accessibility_checker]', 'status' => 'ready'],
            ['slug' => 'legal-disclaimer', 'label' => 'Legal Disclaimer', 'category' => 'Compliance', 'shortcode' => '[h4_legal_disclaimer]', 'status' => 'ready'],
        ];

        $filtered = apply_filters('h4bs_builder_widget_catalog', $catalog);
        $resolved = is_array($filtered) ? array_values($filtered) : $catalog;
        $schemas = $this->widgetControlSchemas();
        foreach ($resolved as $index => $widget) {
            if (! is_array($widget)) {
                continue;
            }
            $tag = $this->extractShortcodeTag((string) ($widget['shortcode'] ?? ''));
            if ($tag !== '' && isset($schemas[$tag]) && ! isset($widget['controls'])) {
                $widget['controls'] = $schemas[$tag];
                $resolved[$index] = $widget;
            }
        }

        $this->runtimeWidgetCatalog = $resolved;
        return $this->runtimeWidgetCatalog;
    }

    /**
     * @return array<string, array<int, array<string, mixed>>>
     */
    private function widgetControlSchemas(): array
    {
        $yesNo = [
            ['value' => '1', 'label' => __('Yes', 'help4-builder-suite')],
            ['value' => '0', 'label' => __('No', 'help4-builder-suite')],
        ];

        $schemas = [
            'h4_products' => [
                ['key' => 'limit', 'label' => __('Products per page', 'help4-builder-suite'), 'type' => 'number', 'default' => '20'],
                ['key' => 'search', 'label' => __('Search phrase', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'status', 'label' => __('Product status', 'help4-builder-suite'), 'type' => 'text', 'default' => 'active'],
                ['key' => 'search_mode', 'label' => __('Search matching', 'help4-builder-suite'), 'type' => 'select', 'options' => ['prefix', 'contains', 'exact'], 'default' => 'prefix'],
                ['key' => 'sort', 'label' => __('Sort order', 'help4-builder-suite'), 'type' => 'select', 'options' => ['updated_desc', 'updated_asc', 'price_asc', 'price_desc', 'title_asc', 'title_desc'], 'default' => 'updated_desc'],
                ['key' => 'min_price', 'label' => __('Minimum price', 'help4-builder-suite'), 'type' => 'number', 'default' => ''],
                ['key' => 'max_price', 'label' => __('Maximum price', 'help4-builder-suite'), 'type' => 'number', 'default' => ''],
                ['key' => 'in_stock_only', 'label' => __('In-stock products only', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '0'],
                ['key' => 'show_filters', 'label' => __('Show filters', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'filter_style', 'label' => __('Filter layout', 'help4-builder-suite'), 'type' => 'select', 'options' => ['toolbar', 'drawer', 'compact'], 'default' => 'toolbar'],
                ['key' => 'cursor', 'label' => __('Cursor pagination', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'query_prefix', 'label' => __('URL query prefix', 'help4-builder-suite'), 'type' => 'text', 'default' => 'h4p'],
                ['key' => 'card_style', 'label' => __('Product card style', 'help4-builder-suite'), 'type' => 'select', 'options' => ['overlay', 'list', 'showcase', 'clean', 'compact'], 'default' => 'overlay'],
                ['key' => 'hover', 'label' => __('Card hover behavior', 'help4-builder-suite'), 'type' => 'select', 'options' => ['lift', 'image_zoom', 'quick_actions', 'none'], 'default' => 'lift'],
                ['key' => 'columns', 'label' => __('Desktop columns', 'help4-builder-suite'), 'type' => 'number', 'default' => '4'],
                ['key' => 'columns_tablet', 'label' => __('Tablet columns', 'help4-builder-suite'), 'type' => 'number', 'default' => '2'],
                ['key' => 'columns_mobile', 'label' => __('Mobile columns', 'help4-builder-suite'), 'type' => 'number', 'default' => '1'],
                ['key' => 'show_images', 'label' => __('Show product images', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'show_quick_view', 'label' => __('Show quick view', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'show_compare', 'label' => __('Show comparison', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'show_stock_badges', 'label' => __('Show stock badges', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'show_sku', 'label' => __('Show SKU', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'show_type_badges', 'label' => __('Show product type badges', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'show_mobile_toolbar', 'label' => __('Show mobile filter toolbar', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'show_price_watch', 'label' => __('Show price-watch action', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'show_waitlist', 'label' => __('Show waitlist action', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'request_anchor', 'label' => __('Request link anchor', 'help4-builder-suite'), 'type' => 'text', 'default' => 'h4bs-contact'],
                ['key' => 'bg_color', 'label' => __('Catalog background color', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'bg_gradient', 'label' => __('Catalog background gradient', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'bg_image', 'label' => __('Catalog background image URL', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'panel_bg', 'label' => __('Filter panel background', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'panel_border', 'label' => __('Filter panel border', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'panel_padding', 'label' => __('Filter panel padding', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'panel_radius', 'label' => __('Filter panel radius', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
            ],
            'h4_checkout' => [
                ['key' => 'product_id', 'label' => __('Product ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'variant_id', 'label' => __('Variant ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'quantity', 'label' => __('Starting quantity (ignored for fixed-line products)', 'help4-builder-suite'), 'type' => 'number', 'default' => '1'],
                ['key' => 'title', 'label' => __('Checkout title', 'help4-builder-suite'), 'type' => 'text', 'default' => __('Secure Checkout', 'help4-builder-suite')],
                ['key' => 'button_label', 'label' => __('Payment button label', 'help4-builder-suite'), 'type' => 'text', 'default' => __('Continue to Payment', 'help4-builder-suite')],
            ],
            'h4_search' => [
                ['key' => 'type', 'label' => __('Search scope', 'help4-builder-suite'), 'type' => 'select', 'options' => ['all', 'products', 'posts', 'pages', 'google'], 'default' => 'all'],
                ['key' => 'placeholder', 'label' => __('Search placeholder', 'help4-builder-suite'), 'type' => 'text', 'default' => __('Search posts and products...', 'help4-builder-suite')],
                ['key' => 'exclude_posts', 'label' => __('Excluded post IDs', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'exclude_pages', 'label' => __('Excluded page IDs', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'exclude_products', 'label' => __('Excluded product IDs', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'google_fallback', 'label' => __('Allow Google fallback', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '0'],
                ['key' => 'google_site', 'label' => __('Google fallback site URL', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'google_extra', 'label' => __('Google query suffix', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'show_scope', 'label' => __('Show scope selector', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
            ],
            'h4_field' => [
                ['key' => 'source', 'label' => __('Data source', 'help4-builder-suite'), 'type' => 'select', 'options' => ['field', 'post', 'post_meta', 'product', 'product_field', 'variant', 'term', 'user', 'site', 'option', 'request'], 'default' => 'field'],
                ['key' => 'key', 'label' => __('Field key', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'post_id', 'label' => __('Source post ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'object_id', 'label' => __('Source object ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'product_id', 'label' => __('Product ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'variant_id', 'label' => __('Variant ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'term_id', 'label' => __('Term ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'user_id', 'label' => __('User ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'taxonomy', 'label' => __('Taxonomy', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'sku', 'label' => __('Variant SKU', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'fallback', 'label' => __('Fallback value', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'format', 'label' => __('Output format', 'help4-builder-suite'), 'type' => 'select', 'options' => ['text', 'html', 'image', 'gallery', 'file', 'link', 'url', 'json', 'list'], 'default' => 'text'],
                ['key' => 'path', 'label' => __('Nested value path', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'transform', 'label' => __('Transform pipeline', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'currency', 'label' => __('Currency code', 'help4-builder-suite'), 'type' => 'text', 'default' => 'USD'],
                ['key' => 'separator', 'label' => __('List separator', 'help4-builder-suite'), 'type' => 'text', 'default' => ', '],
                ['key' => 'before', 'label' => __('Prefix markup', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'after', 'label' => __('Suffix markup', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'image_size', 'label' => __('Image size', 'help4-builder-suite'), 'type' => 'select', 'options' => ['thumbnail', 'medium', 'large', 'full'], 'default' => 'large'],
            ],
            'h4_dynamic' => [
                ['key' => 'source', 'label' => __('Data source', 'help4-builder-suite'), 'type' => 'select', 'options' => ['field', 'post', 'post_meta', 'product', 'product_field', 'variant', 'term', 'user', 'site', 'option', 'request'], 'default' => 'post'],
                ['key' => 'key', 'label' => __('Property or field key', 'help4-builder-suite'), 'type' => 'text', 'default' => 'title'],
                ['key' => 'post_id', 'label' => __('Post ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'object_id', 'label' => __('Object ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'product_id', 'label' => __('Product ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'variant_id', 'label' => __('Variant ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'term_id', 'label' => __('Term ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'user_id', 'label' => __('User ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'taxonomy', 'label' => __('Taxonomy', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'sku', 'label' => __('Variant SKU', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'path', 'label' => __('Nested value path', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'transform', 'label' => __('Transform pipeline', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'format', 'label' => __('Output format', 'help4-builder-suite'), 'type' => 'select', 'options' => ['text', 'html', 'image', 'gallery', 'file', 'link', 'url', 'json', 'list'], 'default' => 'text'],
                ['key' => 'fallback', 'label' => __('Fallback value', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'before', 'label' => __('Text before value', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'after', 'label' => __('Text after value', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'separator', 'label' => __('List separator', 'help4-builder-suite'), 'type' => 'text', 'default' => ', '],
                ['key' => 'currency', 'label' => __('Currency code', 'help4-builder-suite'), 'type' => 'text', 'default' => 'USD'],
                ['key' => 'image_size', 'label' => __('Image size', 'help4-builder-suite'), 'type' => 'select', 'options' => ['thumbnail', 'medium', 'large', 'full'], 'default' => 'large'],
            ],
            'h4_lms_courses' => [
                ['key' => 'limit', 'label' => __('Courses to show', 'help4-builder-suite'), 'type' => 'number', 'default' => '12'],
            ],
            'h4_lms_certificate' => [
                ['key' => 'code', 'label' => __('Certificate code', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
            ],
            'h4_lms_video' => [
                ['key' => 'post_id', 'label' => __('Lesson or course post ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'context', 'label' => __('Video context', 'help4-builder-suite'), 'type' => 'text', 'default' => 'lesson'],
                ['key' => 'access', 'label' => __('Access requirement', 'help4-builder-suite'), 'type' => 'select', 'options' => ['public', 'logged_in'], 'default' => 'logged_in'],
                ['key' => 'source', 'label' => __('Primary video source', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'hls', 'label' => __('HLS URL', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'mp4', 'label' => __('MP4 URL', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'poster', 'label' => __('Poster image URL', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'fallback_image', 'label' => __('Playback failure image URL', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'title', 'label' => __('Accessible video title', 'help4-builder-suite'), 'type' => 'text', 'default' => __('Course video', 'help4-builder-suite')],
                ['key' => 'autoplay', 'label' => __('Autoplay', 'help4-builder-suite'), 'type' => 'select', 'options' => ['0', '1'], 'default' => '0'],
                ['key' => 'muted', 'label' => __('Muted', 'help4-builder-suite'), 'type' => 'select', 'options' => ['0', '1'], 'default' => '0'],
                ['key' => 'loop', 'label' => __('Loop', 'help4-builder-suite'), 'type' => 'select', 'options' => ['0', '1'], 'default' => '0'],
            ],
            'h4_slider' => [
                ['key' => 'id', 'label' => __('Slider ID', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'source', 'label' => __('Slide source', 'help4-builder-suite'), 'type' => 'select', 'options' => ['manual', 'posts'], 'default' => 'manual'],
                ['key' => 'post_type', 'label' => __('Dynamic post type', 'help4-builder-suite'), 'type' => 'text', 'default' => 'post'],
                ['key' => 'limit', 'label' => __('Dynamic slide limit', 'help4-builder-suite'), 'type' => 'number', 'default' => '8'],
                ['key' => 'taxonomy', 'label' => __('Dynamic taxonomy', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'terms', 'label' => __('Dynamic term slugs', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
                ['key' => 'orderby', 'label' => __('Dynamic order by', 'help4-builder-suite'), 'type' => 'select', 'options' => ['date', 'modified', 'title', 'menu_order', 'rand'], 'default' => 'date'],
                ['key' => 'order', 'label' => __('Dynamic order', 'help4-builder-suite'), 'type' => 'select', 'options' => ['DESC', 'ASC'], 'default' => 'DESC'],
                ['key' => 'autoplay', 'label' => __('Autoplay interval (ms)', 'help4-builder-suite'), 'type' => 'number', 'default' => '4500'],
                ['key' => 'start_index', 'label' => __('Starting slide', 'help4-builder-suite'), 'type' => 'number', 'default' => '0'],
                ['key' => 'transition', 'label' => __('Transition', 'help4-builder-suite'), 'type' => 'select', 'options' => ['slide', 'fade', 'zoom', 'flip', 'stack', 'none'], 'default' => 'slide'],
                ['key' => 'transition_duration', 'label' => __('Transition duration (ms)', 'help4-builder-suite'), 'type' => 'number', 'default' => '600'],
                ['key' => 'easing', 'label' => __('Transition easing', 'help4-builder-suite'), 'type' => 'select', 'options' => ['ease', 'ease_in', 'ease_out', 'ease_in_out', 'linear', 'spring'], 'default' => 'ease'],
                ['key' => 'motion', 'label' => __('Background motion', 'help4-builder-suite'), 'type' => 'select', 'options' => ['none', 'zoom_in', 'zoom_out', 'pan_left', 'pan_right', 'pan_up', 'pan_down'], 'default' => 'none'],
                ['key' => 'motion_duration', 'label' => __('Background motion duration (ms)', 'help4-builder-suite'), 'type' => 'number', 'default' => '9000'],
                ['key' => 'fit', 'label' => __('Media fit', 'help4-builder-suite'), 'type' => 'select', 'options' => ['cover', 'contain', 'fill', 'none'], 'default' => 'cover'],
                ['key' => 'height_mode', 'label' => __('Height mode', 'help4-builder-suite'), 'type' => 'select', 'options' => ['fixed', 'auto'], 'default' => 'fixed'],
                ['key' => 'height_px', 'label' => __('Desktop height', 'help4-builder-suite'), 'type' => 'number', 'default' => '540'],
                ['key' => 'height_tablet', 'label' => __('Tablet height', 'help4-builder-suite'), 'type' => 'number', 'default' => '480'],
                ['key' => 'height_mobile', 'label' => __('Mobile height', 'help4-builder-suite'), 'type' => 'number', 'default' => '420'],
                ['key' => 'loop', 'label' => __('Loop slides', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'shuffle', 'label' => __('Shuffle on load', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '0'],
                ['key' => 'arrows', 'label' => __('Show arrows', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'dots', 'label' => __('Show dots', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'thumbnails', 'label' => __('Show thumbnails', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '0'],
                ['key' => 'progress', 'label' => __('Show autoplay progress', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'pause_button', 'label' => __('Show play/pause', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'pause_on_hover', 'label' => __('Pause on hover', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'pause_out_of_view', 'label' => __('Pause outside viewport', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'keyboard', 'label' => __('Keyboard navigation', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'swipe', 'label' => __('Touch and pointer swipe', 'help4-builder-suite'), 'type' => 'select', 'options' => $yesNo, 'default' => '1'],
                ['key' => 'label', 'label' => __('Accessible slider label', 'help4-builder-suite'), 'type' => 'text', 'default' => ''],
            ],
        ];

        $filtered = apply_filters('h4bs_builder_widget_control_schemas', $schemas);
        return is_array($filtered) ? $filtered : $schemas;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function viewportPresets(): array
    {
        return [
            ['slug' => 'tv_8k', 'label' => '8K TV', 'width' => 7680, 'height' => 4320, 'group' => 'tv', 'tier' => 'TV'],
            ['slug' => 'tv_4k', 'label' => '4K UHD TV', 'width' => 3840, 'height' => 2160, 'group' => 'tv', 'tier' => 'TV'],
            ['slug' => 'tv_uhd_hdmi', 'label' => 'UHD Living Room', 'width' => 3840, 'height' => 2160, 'group' => 'tv', 'tier' => 'TV'],

            ['slug' => 'imac_5k', 'label' => '5K Display', 'width' => 5120, 'height' => 2880, 'group' => 'monitor', 'tier' => 'High-Res'],
            ['slug' => 'monitor_4k', 'label' => '4K Monitor', 'width' => 3840, 'height' => 2160, 'group' => 'monitor', 'tier' => 'High-Res'],
            ['slug' => 'ultrawide_34', 'label' => 'Ultrawide 34"', 'width' => 3440, 'height' => 1440, 'group' => 'monitor', 'tier' => 'Ultrawide'],
            ['slug' => 'qhd_27', 'label' => 'QHD 27"', 'width' => 2560, 'height' => 1440, 'group' => 'monitor', 'tier' => 'Desktop'],
            ['slug' => 'fhd_1080p', 'label' => 'Full HD 1080p', 'width' => 1920, 'height' => 1080, 'group' => 'monitor', 'tier' => 'Desktop'],
            ['slug' => 'wsxga_plus', 'label' => 'Laptop 1680x1050', 'width' => 1680, 'height' => 1050, 'group' => 'monitor', 'tier' => 'Laptop'],
            ['slug' => 'hd_plus', 'label' => 'Laptop 1600x900', 'width' => 1600, 'height' => 900, 'group' => 'monitor', 'tier' => 'Laptop'],
            ['slug' => 'odd_laptop_1536', 'label' => 'Odd Laptop 1536x864', 'width' => 1536, 'height' => 864, 'group' => 'monitor', 'tier' => 'Laptop'],
            ['slug' => 'macbook_air_13', 'label' => 'MacBook Air 13"', 'width' => 1440, 'height' => 900, 'group' => 'monitor', 'tier' => 'Laptop'],
            ['slug' => 'common_laptop', 'label' => 'Laptop 1366x768', 'width' => 1366, 'height' => 768, 'group' => 'monitor', 'tier' => 'Laptop'],
            ['slug' => 'hd_720p', 'label' => 'HD 720p', 'width' => 1280, 'height' => 720, 'group' => 'monitor', 'tier' => 'Laptop'],

            ['slug' => 'ipad_pro_13', 'label' => 'iPad Pro 13"', 'width' => 1024, 'height' => 1366, 'group' => 'tablet', 'tier' => 'Apple iPad'],
            ['slug' => 'ipad_pro_11', 'label' => 'iPad Pro 11"', 'width' => 834, 'height' => 1194, 'group' => 'tablet', 'tier' => 'Apple iPad'],
            ['slug' => 'ipad_air', 'label' => 'iPad Air', 'width' => 820, 'height' => 1180, 'group' => 'tablet', 'tier' => 'Apple iPad'],
            ['slug' => 'ipad_10', 'label' => 'iPad 10.2"', 'width' => 810, 'height' => 1080, 'group' => 'tablet', 'tier' => 'Apple iPad'],
            ['slug' => 'galaxy_tab_s9', 'label' => 'Galaxy Tab S9', 'width' => 800, 'height' => 1280, 'group' => 'tablet', 'tier' => 'Android Tablet'],
            ['slug' => 'surface_pro_9', 'label' => 'Surface Pro 9', 'width' => 912, 'height' => 1368, 'group' => 'tablet', 'tier' => 'Windows Tablet'],
            ['slug' => 'tablet_landscape', 'label' => 'Tablet Landscape', 'width' => 1024, 'height' => 768, 'group' => 'tablet', 'tier' => 'Generic'],
            ['slug' => 'tablet_768', 'label' => 'Tablet Portrait 768', 'width' => 768, 'height' => 1024, 'group' => 'tablet', 'tier' => 'Generic'],

            ['slug' => 'iphone_17_pro_max', 'label' => 'iPhone 17 Pro Max', 'width' => 440, 'height' => 956, 'group' => 'mobile', 'tier' => 'Apple iPhone'],
            ['slug' => 'iphone_17_pro', 'label' => 'iPhone 17 Pro', 'width' => 402, 'height' => 874, 'group' => 'mobile', 'tier' => 'Apple iPhone'],
            ['slug' => 'iphone_16_pro_max', 'label' => 'iPhone 16 Pro Max', 'width' => 440, 'height' => 956, 'group' => 'mobile', 'tier' => 'Apple iPhone'],
            ['slug' => 'iphone_16_pro', 'label' => 'iPhone 16 Pro', 'width' => 402, 'height' => 874, 'group' => 'mobile', 'tier' => 'Apple iPhone'],
            ['slug' => 'iphone_15_pro_max', 'label' => 'iPhone 15 Pro Max', 'width' => 430, 'height' => 932, 'group' => 'mobile', 'tier' => 'Apple iPhone'],
            ['slug' => 'iphone_15_pro', 'label' => 'iPhone 15 Pro', 'width' => 393, 'height' => 852, 'group' => 'mobile', 'tier' => 'Apple iPhone'],
            ['slug' => 'iphone_14_pro_max', 'label' => 'iPhone 14 Pro Max', 'width' => 430, 'height' => 932, 'group' => 'mobile', 'tier' => 'Apple iPhone'],
            ['slug' => 'iphone_14_pro', 'label' => 'iPhone 14 Pro', 'width' => 393, 'height' => 852, 'group' => 'mobile', 'tier' => 'Apple iPhone'],
            ['slug' => 'iphone_14', 'label' => 'iPhone 14', 'width' => 390, 'height' => 844, 'group' => 'mobile', 'tier' => 'Apple iPhone'],
            ['slug' => 'iphone_13_mini', 'label' => 'iPhone 13 Mini', 'width' => 375, 'height' => 812, 'group' => 'mobile', 'tier' => 'Apple iPhone'],
            ['slug' => 'iphone_se', 'label' => 'iPhone SE', 'width' => 320, 'height' => 568, 'group' => 'mobile', 'tier' => 'Apple iPhone'],
            ['slug' => 'pixel_9_pro_xl', 'label' => 'Pixel 9 Pro XL', 'width' => 412, 'height' => 915, 'group' => 'mobile', 'tier' => 'Google Pixel'],
            ['slug' => 'pixel_8_pro', 'label' => 'Pixel 8 Pro', 'width' => 412, 'height' => 915, 'group' => 'mobile', 'tier' => 'Google Pixel'],
            ['slug' => 'galaxy_s24_ultra', 'label' => 'Galaxy S24 Ultra', 'width' => 412, 'height' => 915, 'group' => 'mobile', 'tier' => 'Samsung Galaxy'],
            ['slug' => 'galaxy_s24', 'label' => 'Galaxy S24', 'width' => 360, 'height' => 780, 'group' => 'mobile', 'tier' => 'Samsung Galaxy'],
            ['slug' => 'android_common', 'label' => 'Android 360x800', 'width' => 360, 'height' => 800, 'group' => 'mobile', 'tier' => 'Generic Android'],
        ];
    }

    /**
     * @param array<int, array<string, mixed>> $layout
     */
    private function renderLayoutBlocks(array $layout, int $postId): string
    {
        $templateRole = get_post_type($postId) === 'h4_template'
            ? sanitize_key((string) get_post_meta($postId, '_h4_template_role', true))
            : '';
        $this->renderContextStack[] = [
            'post_id' => $postId,
            'template_role' => $templateRole,
            'image_count' => 0,
        ];

        $html = '';
        try {
            foreach ($this->topLevelLayoutBlocks($layout) as $block) {
                $html .= $this->renderBlock($block, $layout);
            }
        } finally {
            array_pop($this->renderContextStack);
        }

        return $html;
    }

    /**
     * @param array<string, mixed> $settings
     * @return array{width:int,height:int}
     */
    private function builderImageIntrinsicDimensions(int $attachmentId, string $size, array $settings): array
    {
        if ($attachmentId > 0) {
            $imageSource = wp_get_attachment_image_src($attachmentId, $size);
            if (is_array($imageSource)) {
                $width = absint($imageSource[1] ?? 0);
                $height = absint($imageSource[2] ?? 0);
                if ($width > 0 && $height > 0) {
                    return ['width' => $width, 'height' => $height];
                }
            }
        }

        $width = absint($settings['intrinsic_width'] ?? 0);
        $height = absint($settings['intrinsic_height'] ?? 0);
        return $width > 0 && $height > 0
            ? ['width' => $width, 'height' => $height]
            : ['width' => 0, 'height' => 0];
    }

    /**
     * @param array<string, mixed> $settings
     * @return array{attributes:array<string,string>,policy:string}
     */
    private function builderImageLoadingAttributes(array $settings): array
    {
        $requested = sanitize_key((string) ($settings['loading'] ?? 'auto'));
        if (! in_array($requested, ['auto', 'eager', 'lazy'], true)) {
            $requested = 'auto';
        }

        $contextIndex = count($this->renderContextStack) - 1;
        $templateRole = '';
        $imageIndex = 0;
        if ($contextIndex >= 0) {
            $templateRole = sanitize_key((string) ($this->renderContextStack[$contextIndex]['template_role'] ?? ''));
            $imageIndex = max(0, (int) ($this->renderContextStack[$contextIndex]['image_count'] ?? 0));
            $this->renderContextStack[$contextIndex]['image_count'] = $imageIndex + 1;
        }

        $automaticHeaderPriority = $requested === 'auto'
            && $templateRole === 'header'
            && $imageIndex === 0;
        $loading = $requested === 'auto'
            ? ($automaticHeaderPriority ? 'eager' : 'lazy')
            : $requested;
        $attributes = [
            'loading' => $loading,
            'decoding' => 'async',
        ];
        if ($loading === 'eager') {
            $attributes['fetchpriority'] = 'high';
        }

        return [
            'attributes' => $attributes,
            'policy' => $automaticHeaderPriority ? 'header-auto' : $loading,
        ];
    }

    /**
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    private function commerceIndicatorContext(array $settings): array
    {
        $source = $this->enumToken(
            (string) ($settings['source'] ?? 'cart'),
            ['cart', 'wishlist', 'compare', 'notifications', 'custom'],
            'cart'
        );
        $defaults = [
            'cart' => [
                'label' => __('Cart', 'help4-builder-suite'),
                'url' => home_url('/cart/'),
                'icon' => 'fa-solid fa-cart-shopping',
                'singular' => __('item', 'help4-builder-suite'),
                'plural' => __('items', 'help4-builder-suite'),
                'storage' => 'h4bsCommerceCart',
            ],
            'wishlist' => [
                'label' => __('Wishlist', 'help4-builder-suite'),
                'url' => home_url('/wishlist/'),
                'icon' => 'fa-solid fa-heart',
                'singular' => __('item', 'help4-builder-suite'),
                'plural' => __('items', 'help4-builder-suite'),
                'storage' => 'h4bsWishlist',
            ],
            'compare' => [
                'label' => __('Compare', 'help4-builder-suite'),
                'url' => home_url('/compare/'),
                'icon' => 'fa-solid fa-code-compare',
                'singular' => __('product', 'help4-builder-suite'),
                'plural' => __('products', 'help4-builder-suite'),
                'storage' => 'h4bsProductCompare',
            ],
            'notifications' => [
                'label' => __('Notifications', 'help4-builder-suite'),
                'url' => home_url('/account/'),
                'icon' => 'fa-solid fa-bell',
                'singular' => __('notification', 'help4-builder-suite'),
                'plural' => __('notifications', 'help4-builder-suite'),
                'storage' => 'h4bsNotifications',
            ],
            'custom' => [
                'label' => __('Updates', 'help4-builder-suite'),
                'url' => home_url('/'),
                'icon' => 'fa-solid fa-circle-info',
                'singular' => __('update', 'help4-builder-suite'),
                'plural' => __('updates', 'help4-builder-suite'),
                'storage' => '',
            ],
        ];
        $default = $defaults[$source];
        $label = sanitize_text_field((string) ($settings['label'] ?? $default['label']));
        if ($label === '') {
            $label = (string) $default['label'];
        }
        $url = esc_url_raw((string) ($settings['url'] ?? $default['url']));
        if ($url === '') {
            $url = (string) $default['url'];
        }
        $iconClass = trim((string) ($settings['icon_class'] ?? $default['icon']));
        $iconClass = preg_replace('/[^a-zA-Z0-9\-\s_]/', '', $iconClass) ?: (string) $default['icon'];
        $storageKey = preg_replace('/[^a-zA-Z0-9_.:-]/', '', (string) ($settings['storage_key'] ?? $default['storage'])) ?: '';
        $showWhenZero = rest_sanitize_boolean($settings['show_when_zero'] ?? false);
        $showQuantity = ! array_key_exists('show_quantity', $settings)
            || rest_sanitize_boolean($settings['show_quantity']);
        $showLabel = rest_sanitize_boolean($settings['show_label'] ?? false);
        $maxCount = max(1, min(9999, (int) ($settings['max_count'] ?? 99)));
        $initialCount = max(0, (int) ($settings['preview_count'] ?? 0));
        $filteredCount = apply_filters(
            'h4bs_commerce_indicator_count',
            $initialCount,
            $source,
            $settings
        );
        $count = max(0, min(9999999, is_numeric($filteredCount) ? (int) $filteredCount : $initialCount));
        $unitSingular = sanitize_text_field((string) ($settings['unit_singular'] ?? $default['singular']));
        $unitPlural = sanitize_text_field((string) ($settings['unit_plural'] ?? $default['plural']));
        if ($unitSingular === '') {
            $unitSingular = (string) $default['singular'];
        }
        if ($unitPlural === '') {
            $unitPlural = (string) $default['plural'];
        }
        $countLabel = $count === 1 ? $unitSingular : $unitPlural;

        return [
            'source' => $source,
            'label' => $label,
            'url' => $url,
            'icon_class' => $iconClass,
            'storage_key' => $storageKey,
            'show_when_zero' => $showWhenZero,
            'show_quantity' => $showQuantity,
            'show_label' => $showLabel,
            'max_count' => $maxCount,
            'count' => $count,
            'display_count' => $count > $maxCount ? (string) $maxCount . '+' : (string) $count,
            'visible' => $count > 0 || $showWhenZero,
            'unit_singular' => $unitSingular,
            'unit_plural' => $unitPlural,
            'aria_label' => sprintf(
                /* translators: 1: indicator label, 2: count, 3: count unit */
                __('%1$s: %2$d %3$s', 'help4-builder-suite'),
                $label,
                $count,
                $countLabel
            ),
        ];
    }

    private function normalizeMultilineDelimiters(string $value): string
    {
        return str_replace(['\\r\\n', '\\n', '\\r'], "\n", $value);
    }

    /**
     * @return array<int, string>
     */
    private function splitListItems(string $value): array
    {
        $items = preg_split('/\r\n|\r|\n|\|/', $this->normalizeMultilineDelimiters($value)) ?: [];

        return array_values(
            array_filter(
                array_map(
                    static fn($item): string => trim((string) $item),
                    $items
                ),
                static fn(string $item): bool => $item !== ''
            )
        );
    }

    /**
     * @param array<string, mixed> $block
     */
    private function renderBlock(array $block, array $layout = []): string
    {
        if (! $this->blockVisibleForCurrentRequest($block)) {
            return '';
        }

        $id = sanitize_html_class((string) ($block['id'] ?? 'block'));
        $type = (string) ($block['type'] ?? 'text');
        $settings = is_array($block['settings'] ?? null) ? $block['settings'] : [];
        $commerceIndicator = $type === 'commerce_indicator'
            ? $this->commerceIndicatorContext($settings)
            : null;
        $viewportClass = $this->viewportClassForBlock($block);
        $classNames = ['h4bs-block', 'h4bs-block-' . sanitize_html_class($type)];
        if (is_array($commerceIndicator) && ! $commerceIndicator['visible']) {
            $classNames[] = 'is-h4bs-count-hidden';
        }
        $customClasses = preg_split('/\s+/', trim((string) ($settings['advanced_css_classes'] ?? ''))) ?: [];
        foreach ($customClasses as $customClass) {
            $customClass = sanitize_html_class((string) $customClass);
            if ($customClass !== '') {
                $classNames[] = $customClass;
            }
        }
        if ($viewportClass !== '') {
            $classNames[] = sanitize_html_class($viewportClass);
        }
        if (
            in_array($type, ['section', 'container'], true)
            && rest_sanitize_boolean($settings['background_video_enabled'] ?? false)
        ) {
            $classNames[] = 'has-h4bs-background-video';
        }
        $stylePreset = $this->enumToken(
            (string) ($settings['design_style_preset'] ?? ''),
            ['button_primary', 'button_secondary', 'button_outline', 'button_ghost', 'card', 'card_elevated', 'card_soft', 'callout'],
            ''
        );
        if ($stylePreset !== '') {
            $classNames[] = 'h4bs-style-preset-' . str_replace('_', '-', $stylePreset);
        }
        $entranceMotion = $this->enumToken((string) ($settings['design_motion_entrance'] ?? ''), ['fade_up', 'fade_down', 'fade_left', 'fade_right', 'zoom_in'], '');
        $hoverMotion = $this->enumToken((string) ($settings['design_motion_hover'] ?? ''), ['lift', 'grow', 'tilt', 'glow'], '');
        $backgroundMotion = $this->enumToken((string) ($settings['design_motion_background'] ?? ''), ['drift_x', 'drift_y', 'pulse'], '');
        $motionTrigger = $this->enumToken((string) ($settings['design_motion_trigger'] ?? 'page_load'), ['page_load', 'in_view', 'interaction'], 'page_load');
        $reducedMotion = $this->enumToken((string) ($settings['design_motion_reduced'] ?? 'respect'), ['respect', 'disable', 'allow'], 'respect');
        if ($entranceMotion !== '') {
            $classNames[] = 'h4bs-motion-entrance-' . str_replace('_', '-', $entranceMotion);
        }
        if ($hoverMotion !== '') {
            $classNames[] = 'h4bs-motion-hover-' . str_replace('_', '-', $hoverMotion);
        }
        if ($backgroundMotion !== '') {
            $classNames[] = 'h4bs-motion-background-' . str_replace('_', '-', $backgroundMotion);
        }
        if ($entranceMotion !== '' || $backgroundMotion !== '') {
            $classNames[] = 'h4bs-has-motion';
            $classNames[] = 'h4bs-motion-trigger-' . str_replace('_', '-', $motionTrigger);
        }
        $classNames[] = 'h4bs-motion-reduced-' . $reducedMotion;
        $headingTag = '';
        if ($type === 'heading') {
            $headingTag = sanitize_key((string) ($settings['tag'] ?? 'h2'));
            if (! in_array($headingTag, ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], true)) {
                $headingTag = 'h2';
            }
            $classNames[] = 'h4bs-heading-' . $headingTag;
        }
        $classes = implode(' ', array_values(array_unique($classNames)));
        $styleSettings = $type === 'button' ? $this->buttonWrapperDesignSettings($settings) : $settings;
        $style = $this->buildDesignStyle($styleSettings);
        $styleAttr = $style !== '' ? ' style="' . esc_attr($style) . '"' : '';

        $anchorId = sanitize_title((string) ($settings['advanced_anchor_id'] ?? ''));
        $anchor = $anchorId !== ''
            ? '<span class="h4bs-block-anchor" id="' . esc_attr($anchorId) . '" aria-hidden="true"></span>'
            : '';
        $open = $anchor . '<section class="' . $classes . '" id="' . esc_attr($id) . '"' . $this->advancedBlockAttributes($settings) . $styleAttr . '>';
        $close = '</section>';
        $structureDefaults = $this->structureLayoutDefaults();
        if (str_contains($type, ':')) {
            $extensionHtml = apply_filters('h4bs_builder_render_extension_block', '', $block, $layout, '1.0');
            return $open . (is_string($extensionHtml) ? $extensionHtml : '') . $close;
        }

        switch ($type) {
            case 'section':
                $columns = max(1, min(self::MAX_COLUMNS, (int) ($settings['columns'] ?? $structureDefaults['columns'])));
                $gap = max(0, min(160, (int) ($settings['gap'] ?? $structureDefaults['gap'])));
                $minHeight = max(80, min(1800, (int) ($settings['min_height'] ?? $structureDefaults['min_height'])));
                $sectionContext = $this->sectionLayoutContext($settings, $columns, $gap, $minHeight);
                $labelsRaw = (string) ($settings['column_labels'] ?? 'Column 1|Column 2');
                $labels = preg_split('/\s*\|\s*/', $labelsRaw) ?: [];
                $slotsHtml = '';
                for ($i = 0; $i < $columns; $i++) {
                    if (isset($labels[$i]) && trim((string) $labels[$i]) !== '') {
                        $label = trim((string) $labels[$i]);
                    } else {
                        /* translators: %d: Column number. */
                        $label = sprintf(__('Column %d', 'help4-builder-suite'), $i + 1);
                    }
                    $columnStyle = $this->sectionColumnStyle(
                        (string) ($sectionContext['layout_mode'] ?? 'grid'),
                        is_array($sectionContext['column_widths'] ?? null) ? $sectionContext['column_widths'] : [],
                        $i,
                        is_array($sectionContext['column_widths_tablet'] ?? null) ? $sectionContext['column_widths_tablet'] : [],
                        is_array($sectionContext['column_widths_mobile'] ?? null) ? $sectionContext['column_widths_mobile'] : []
                    );
                    $columnStyleAttr = $columnStyle !== '' ? ' style="' . esc_attr($columnStyle) . '"' : '';
                    $slotBlocks = $this->childLayoutBlocks($layout, (string) ($block['id'] ?? ''), $i);
                    $slotBody = '';
                    foreach ($slotBlocks as $slotBlock) {
                        $slotBody .= $this->renderBlock($slotBlock, $layout);
                    }
                    $slotsHtml .= '<div class="h4bs-builder-section-col"' . $columnStyleAttr . '>'
                        . '<span class="h4bs-builder-section-col-label">' . esc_html($label) . '</span>'
                        . '<div class="h4bs-builder-section-slot">'
                        . $slotBody
                        . '</div>'
                        . '</div>';
                }
                return $open
                    . $this->renderBackgroundVideo($settings)
                    . '<div class="h4bs-builder-section-grid is-' . esc_attr($sectionContext['layout_mode']) . '" style="' . esc_attr($sectionContext['style']) . '">'
                    . $slotsHtml
                    . '</div>'
                    . $close;

            case 'heading':
                $text = esc_html((string) ($settings['text'] ?? 'Heading'));
                return $open . '<' . $headingTag . '>' . $text . '</' . $headingTag . '>' . $close;

            case 'text':
                $text = esc_html($this->replaceBuilderTextTokens((string) ($settings['text'] ?? '')));
                return $open . '<p>' . $text . '</p>' . $close;

            case 'image':
                $attachmentId = absint($settings['attachment_id'] ?? 0);
                $src = esc_url((string) ($settings['src'] ?? $settings['url'] ?? ''));
                $alt = esc_attr((string) ($settings['alt'] ?? ''));
                $title = esc_attr((string) ($settings['title'] ?? ''));
                $caption = wp_kses_post((string) ($settings['caption'] ?? ''));
                $align = sanitize_key((string) ($settings['align'] ?? 'none'));
                if (! in_array($align, ['none', 'left', 'center', 'right'], true)) {
                    $align = 'none';
                }
                $imgWidth = $this->cssDimension(
                    (string) ($settings['width'] ?? ''),
                    $this->cssUnit((string) ($settings['width_unit'] ?? '%'), '%')
                );
                $imgMaxWidth = $this->cssDimension(
                    (string) ($settings['max_width'] ?? ''),
                    $this->cssUnit((string) ($settings['max_width_unit'] ?? 'px'), 'px')
                );
                $imgHeight = $this->cssDimension(
                    (string) ($settings['height'] ?? ''),
                    $this->cssUnit((string) ($settings['height_unit'] ?? 'px'), 'px')
                );
                $imgRadius = $this->cssDimension(
                    (string) ($settings['border_radius'] ?? ''),
                    $this->cssUnit((string) ($settings['border_radius_unit'] ?? 'px'), 'px')
                );
                $objectFit = $this->cssToken((string) ($settings['object_fit'] ?? ''));
                $boxShadow = $this->cssToken((string) ($settings['box_shadow'] ?? ''));
                $hoverScale = $this->cssToken((string) ($settings['hover_scale'] ?? ''));
                $overlayLayer = $this->overlayColorLayer(
                    (string) ($settings['overlay_color'] ?? ''),
                    (string) ($settings['overlay_opacity'] ?? '0')
                );
                $lightbox = rest_sanitize_boolean($settings['lightbox'] ?? false);
                if ($attachmentId < 1 && $src === '') {
                    return $open . $close;
                }
                $imageSize = sanitize_key((string) ($settings['size'] ?? 'full'));
                $intrinsic = $this->builderImageIntrinsicDimensions($attachmentId, $imageSize, $settings);
                $loadingPolicy = $this->builderImageLoadingAttributes($settings);
                $loadingAttributes = $loadingPolicy['attributes'];

                $imageStyles = [];
                if ($imgHeight !== '') {
                    $imageStyles[] = 'height:100%';
                }
                if ($imgRadius !== '') {
                    $imageStyles[] = 'border-radius:' . $imgRadius;
                }
                if ($objectFit !== '') {
                    $imageStyles[] = 'object-fit:' . $objectFit;
                }
                if ($boxShadow !== '') {
                    $imageStyles[] = 'box-shadow:' . $boxShadow;
                }
                if ($hoverScale !== '') {
                    $imageStyles[] = '--h4bs-image-hover-scale:' . $hoverScale;
                }
                $imageStyleAttr = $imageStyles ? ' style="' . esc_attr(implode(';', $imageStyles)) . '"' : '';

                $innerStyles = [];
                if ($imgWidth !== '') {
                    $innerStyles[] = 'width:' . $imgWidth;
                } elseif ($intrinsic['width'] > 0) {
                    $innerStyles[] = 'width:min(100%,' . $intrinsic['width'] . 'px)';
                }
                $innerStyles[] = 'max-width:' . ($imgMaxWidth !== '' ? $imgMaxWidth : '100%');
                if ($imgHeight !== '') {
                    $innerStyles[] = 'height:' . $imgHeight;
                } elseif ($intrinsic['width'] > 0 && $intrinsic['height'] > 0) {
                    $innerStyles[] = 'aspect-ratio:' . $intrinsic['width'] . ' / ' . $intrinsic['height'];
                }
                $innerStyleAttr = $innerStyles ? ' style="' . esc_attr(implode(';', $innerStyles)) . '"' : '';
                $intrinsicAttr = $intrinsic['width'] > 0 && $intrinsic['height'] > 0
                    ? ' data-h4bs-image-reserved="' . esc_attr($intrinsic['width'] . 'x' . $intrinsic['height']) . '"'
                    : '';
                $policyAttr = ' data-h4bs-image-loading="' . esc_attr($loadingPolicy['policy']) . '"';

                $figureStyles = [];
                if ($align === 'left') {
                    $figureStyles[] = 'text-align:left';
                } elseif ($align === 'center') {
                    $figureStyles[] = 'text-align:center';
                } elseif ($align === 'right') {
                    $figureStyles[] = 'text-align:right';
                }
                $figureStyleAttr = $figureStyles ? ' style="' . esc_attr(implode(';', $figureStyles)) . '"' : '';

                if ($attachmentId) {
                    $attachmentAttributes = [
                        'class' => 'h4bs-builder-image',
                        'loading' => $loadingAttributes['loading'],
                        'decoding' => $loadingAttributes['decoding'],
                        'alt' => $alt,
                        'title' => $title,
                        'style' => $imageStyles ? implode(';', $imageStyles) : null,
                    ];
                    if (isset($loadingAttributes['fetchpriority'])) {
                        $attachmentAttributes['fetchpriority'] = $loadingAttributes['fetchpriority'];
                    }
                    $image = wp_get_attachment_image(
                        $attachmentId,
                        $imageSize,
                        false,
                        $attachmentAttributes
                    );
                } else {
                    $rawImageDimensions = $intrinsic['width'] > 0 && $intrinsic['height'] > 0
                        ? ' width="' . esc_attr((string) $intrinsic['width']) . '" height="' . esc_attr((string) $intrinsic['height']) . '"'
                        : '';
                    $rawImagePriority = isset($loadingAttributes['fetchpriority'])
                        ? ' fetchpriority="' . esc_attr($loadingAttributes['fetchpriority']) . '"'
                        : '';
                    $image = $src !== ''
                        ? '<img class="h4bs-builder-image" loading="' . esc_attr($loadingAttributes['loading']) . '" decoding="' . esc_attr($loadingAttributes['decoding']) . '"' . $rawImagePriority . $rawImageDimensions . ' src="' . $src . '" alt="' . $alt . '" title="' . $title . '"' . $imageStyleAttr . '>'
                        : '';
                }
                if ($image !== '' && $lightbox && $src !== '') {
                    $image = '<a class="h4bs-builder-image-link" href="' . $src . '" target="_blank" rel="noopener">' . $image . '</a>';
                }
                if ($image === '') {
                    return $open . $close;
                }
                $overlay = $overlayLayer !== '' ? '<span class="h4bs-builder-image-overlay" style="background-image:' . esc_attr($overlayLayer) . ';"></span>' : '';
                $captionHtml = $caption !== '' ? '<figcaption class="h4bs-builder-image-caption">' . $caption . '</figcaption>' : '';
                $imageHtml = '<figure class="h4bs-builder-image-wrap"' . $figureStyleAttr . '><span class="h4bs-builder-image-inner"' . $innerStyleAttr . $intrinsicAttr . $policyAttr . '>' . $image . $overlay . '</span>' . $captionHtml . '</figure>';
                return $open . $imageHtml . $close;

            case 'button':
                $label = esc_html((string) ($settings['label'] ?? 'Click'));
                $url = esc_url((string) ($settings['url'] ?? '#'));
                $buttonStyle = $this->buildDesignStyle($this->buttonElementDesignSettings($settings));
                $buttonStyleAttr = $buttonStyle !== '' ? ' style="' . esc_attr($buttonStyle) . '"' : '';
                $buttonLinkAttrs = $this->builderLinkAttributes(
                    (string) ($settings['target'] ?? 'same'),
                    (string) ($settings['rel'] ?? '')
                );
                return $open . '<a class="h4bs-builder-button" href="' . $url . '"' . $buttonLinkAttrs . $buttonStyleAttr . '>' . $label . '</a>' . $close;

            case 'shortcode':
                $shortcode = trim((string) ($settings['shortcode'] ?? ''));
                if ($shortcode === '') {
                    return $open . $close;
                }
                $normalized = html_entity_decode($shortcode, ENT_QUOTES, (string) get_bloginfo('charset'));
                return $open . shortcode_unautop(do_shortcode(wp_kses_post($normalized))) . $close;

            case 'product_reviews':
                $productId = absint($settings['product_id'] ?? 0);
                $limit = max(1, min(20, (int) ($settings['limit'] ?? 5)));
                $showForm = ! array_key_exists('show_form', $settings) || rest_sanitize_boolean($settings['show_form']);
                $showSummary = ! array_key_exists('show_summary', $settings) || rest_sanitize_boolean($settings['show_summary']);
                $title = sanitize_text_field((string) ($settings['title'] ?? ''));
                $emptyText = sanitize_text_field((string) ($settings['empty_text'] ?? ''));
                $titleAttr = $title !== '' ? ' title="' . esc_attr($title) . '"' : '';
                $emptyAttr = $emptyText !== '' ? ' empty_text="' . esc_attr($emptyText) . '"' : '';
                $reviewShortcode = '[h4_product_reviews product_id="' . $productId . '" limit="' . $limit . '" show_form="' . ($showForm ? '1' : '0') . '" show_summary="' . ($showSummary ? '1' : '0') . '"' . $titleAttr . $emptyAttr . ']';
                return $open . shortcode_unautop(do_shortcode($reviewShortcode)) . $close;

            case 'commerce_indicator':
                if (! is_array($commerceIndicator)) {
                    return $open . $close;
                }
                $badgeHidden = $commerceIndicator['show_quantity'] ? '' : ' hidden';
                $labelHtml = $commerceIndicator['show_label']
                    ? '<span class="h4bs-commerce-indicator-label">' . esc_html($commerceIndicator['label']) . '</span>'
                    : '';
                return $open
                    . '<a class="h4bs-commerce-indicator h4bs-commerce-indicator--' . esc_attr($commerceIndicator['source']) . '"'
                    . ' href="' . esc_url($commerceIndicator['url']) . '"'
                    . ' aria-label="' . esc_attr($commerceIndicator['aria_label']) . '"'
                    . ' data-h4bs-commerce-indicator'
                    . ' data-h4bs-count-source="' . esc_attr($commerceIndicator['source']) . '"'
                    . ' data-h4bs-storage-key="' . esc_attr($commerceIndicator['storage_key']) . '"'
                    . ' data-h4bs-count="' . esc_attr((string) $commerceIndicator['count']) . '"'
                    . ' data-h4bs-show-zero="' . ($commerceIndicator['show_when_zero'] ? '1' : '0') . '"'
                    . ' data-h4bs-show-quantity="' . ($commerceIndicator['show_quantity'] ? '1' : '0') . '"'
                    . ' data-h4bs-max-count="' . esc_attr((string) $commerceIndicator['max_count']) . '"'
                    . ' data-h4bs-label="' . esc_attr($commerceIndicator['label']) . '"'
                    . ' data-h4bs-unit-singular="' . esc_attr($commerceIndicator['unit_singular']) . '"'
                    . ' data-h4bs-unit-plural="' . esc_attr($commerceIndicator['unit_plural']) . '">'
                    . '<i class="' . esc_attr($commerceIndicator['icon_class']) . ' h4bs-commerce-indicator-icon" aria-hidden="true"></i>'
                    . $labelHtml
                    . '<span class="h4bs-commerce-indicator-count" data-h4bs-commerce-indicator-count aria-hidden="true"' . $badgeHidden . '>'
                    . esc_html($commerceIndicator['display_count'])
                    . '</span>'
                    . '</a>'
                    . $close;

            case 'google_business':
                $domain = sanitize_text_field((string) ($settings['domain'] ?? ''));
                $domainAttr = $domain !== '' ? ' domain="' . esc_attr($domain) . '"' : '';
                $view = sanitize_key((string) ($settings['view'] ?? 'business'));
                if (! in_array($view, ['business', 'reviews', 'map'], true)) {
                    $view = 'business';
                }
                $reviews = max(1, min(10, (int) ($settings['reviews'] ?? 5)));
                $location = max(1, min(20, (int) ($settings['location'] ?? 1)));
                $height = max(220, min(900, (int) ($settings['height'] ?? 360)));
                $showMap = ! array_key_exists('show_map', $settings) || rest_sanitize_boolean($settings['show_map']);
                $showReviews = ! array_key_exists('show_reviews', $settings) || rest_sanitize_boolean($settings['show_reviews']);
                $showHeader = ! array_key_exists('show_header', $settings) || rest_sanitize_boolean($settings['show_header']);
                $showAggregate = ! array_key_exists('show_aggregate', $settings) || rest_sanitize_boolean($settings['show_aggregate']);
                $showServiceAreas = ! array_key_exists('show_service_areas', $settings) || rest_sanitize_boolean($settings['show_service_areas']);
                $emptyText = sanitize_text_field((string) ($settings['empty_text'] ?? ''));
                $emptyAttr = $emptyText !== '' ? ' empty_text="' . esc_attr($emptyText) . '"' : '';

                if ($view === 'reviews' || ($view === 'business' && ! $showMap && $showReviews)) {
                    $reviewShortcode = '[h4_google_reviews' . $domainAttr . ' limit="' . $reviews . '"' . $emptyAttr . ']';
                    return $open . shortcode_unautop(do_shortcode($reviewShortcode)) . $close;
                }
                if ($view === 'map' || ($view === 'business' && $showMap && ! $showReviews)) {
                    $mapShortcode = '[h4_google_map' . $domainAttr . ' location="' . $location . '" height="' . $height . '" show_service_areas="' . ($showServiceAreas ? '1' : '0') . '"' . $emptyAttr . ']';
                    return $open . shortcode_unautop(do_shortcode($mapShortcode)) . $close;
                }
                if ($showMap || $showReviews) {
                    $businessShortcode = '[h4_google_business' . $domainAttr . ' reviews="' . $reviews . '" location="' . $location . '" map_height="' . $height . '" show_map="' . ($showMap ? '1' : '0') . '" show_reviews="' . ($showReviews ? '1' : '0') . '" show_header="' . ($showHeader ? '1' : '0') . '" show_aggregate="' . ($showAggregate ? '1' : '0') . '" show_service_areas="' . ($showServiceAreas ? '1' : '0') . '"' . $emptyAttr . ']';
                    return $open . shortcode_unautop(do_shortcode($businessShortcode)) . $close;
                }
                return $open . '<div class="h4bs-preview-shortcode">' . esc_html__('Enable map or reviews in Google Business settings.', 'help4-builder-suite') . '</div>' . $close;

            case 'slider':
                $sliderId = absint($settings['slider_id'] ?? 0);
                $fit = sanitize_key((string) ($settings['fit'] ?? 'contain'));
                if (! in_array($fit, ['cover', 'contain', 'fill', 'stretch', 'none'], true)) {
                    $fit = 'contain';
                }
                $snapshotFit = sanitize_key((string) ($settings['snapshot_fit'] ?? ($settings['siteshot_fit'] ?? 'contain')));
                if (! in_array($snapshotFit, ['cover', 'contain', 'fill', 'stretch', 'none'], true)) {
                    $snapshotFit = 'contain';
                }
                $bg = sanitize_hex_color((string) ($settings['bg'] ?? '#0b1118'));
                if (! is_string($bg) || $bg === '') {
                    $bg = '#0b1118';
                }
                $heightMode = sanitize_key((string) ($settings['height_mode'] ?? 'fixed'));
                if (! in_array($heightMode, ['fixed', 'auto'], true)) {
                    $heightMode = 'fixed';
                }
                $heightPx = max(120, min(2160, (int) ($settings['height_px'] ?? 540)));
                $heightTablet = max(120, min(2160, (int) ($settings['height_tablet'] ?? 480)));
                $heightMobile = max(120, min(2160, (int) ($settings['height_mobile'] ?? 420)));
                $transition = sanitize_key((string) ($settings['transition'] ?? 'slide'));
                if (! in_array($transition, ['slide', 'fade', 'zoom', 'flip', 'stack', 'none'], true)) {
                    $transition = 'slide';
                }
                $motion = sanitize_key((string) ($settings['motion'] ?? 'none'));
                if (! in_array($motion, ['none', 'zoom_in', 'zoom_out', 'pan_left', 'pan_right', 'pan_up', 'pan_down'], true)) {
                    $motion = 'none';
                }
                $motionDuration = max(2000, min(30000, (int) ($settings['motion_duration'] ?? 9000)));
                $motionForce = array_key_exists('motion_force', $settings) && rest_sanitize_boolean($settings['motion_force']);
                $autoplay = max(0, min(60000, (int) ($settings['autoplay'] ?? 4500)));
                $source = sanitize_key((string) ($settings['source'] ?? 'manual'));
                if (! in_array($source, ['manual', 'posts'], true)) {
                    $source = 'manual';
                }
                $sliderAttributes = [
                    'id' => (string) $sliderId,
                    'source' => $source,
                    'post_type' => sanitize_key((string) ($settings['post_type'] ?? 'post')),
                    'limit' => (string) max(1, min(100, (int) ($settings['limit'] ?? 8))),
                    'taxonomy' => sanitize_key((string) ($settings['taxonomy'] ?? '')),
                    'terms' => sanitize_text_field((string) ($settings['terms'] ?? '')),
                    'orderby' => sanitize_key((string) ($settings['orderby'] ?? 'date')),
                    'order' => strtoupper((string) ($settings['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC',
                    'fit' => $fit,
                    'snapshot_fit' => $snapshotFit,
                    'bg' => $bg,
                    'height_mode' => $heightMode,
                    'height_px' => (string) $heightPx,
                    'height_tablet' => (string) $heightTablet,
                    'height_mobile' => (string) $heightMobile,
                    'transition' => $transition,
                    'transition_duration' => (string) max(0, min(5000, (int) ($settings['transition_duration'] ?? 600))),
                    'easing' => sanitize_key((string) ($settings['easing'] ?? 'ease')),
                    'motion' => $motion,
                    'motion_duration' => (string) $motionDuration,
                    'motion_force' => $motionForce ? 'true' : 'false',
                    'autoplay' => (string) $autoplay,
                    'start_index' => (string) max(0, (int) ($settings['start_index'] ?? 0)),
                    'loop' => (! array_key_exists('loop', $settings) || rest_sanitize_boolean($settings['loop'])) ? 'true' : 'false',
                    'shuffle' => rest_sanitize_boolean($settings['shuffle'] ?? false) ? 'true' : 'false',
                    'arrows' => (! array_key_exists('arrows', $settings) || rest_sanitize_boolean($settings['arrows'])) ? 'true' : 'false',
                    'dots' => (! array_key_exists('dots', $settings) || rest_sanitize_boolean($settings['dots'])) ? 'true' : 'false',
                    'thumbnails' => rest_sanitize_boolean($settings['thumbnails'] ?? false) ? 'true' : 'false',
                    'progress' => (! array_key_exists('progress', $settings) || rest_sanitize_boolean($settings['progress'])) ? 'true' : 'false',
                    'pause_button' => (! array_key_exists('pause_button', $settings) || rest_sanitize_boolean($settings['pause_button'])) ? 'true' : 'false',
                    'pause_on_hover' => (! array_key_exists('pause_on_hover', $settings) || rest_sanitize_boolean($settings['pause_on_hover'])) ? 'true' : 'false',
                    'pause_out_of_view' => (! array_key_exists('pause_out_of_view', $settings) || rest_sanitize_boolean($settings['pause_out_of_view'])) ? 'true' : 'false',
                    'keyboard' => (! array_key_exists('keyboard', $settings) || rest_sanitize_boolean($settings['keyboard'])) ? 'true' : 'false',
                    'swipe' => (! array_key_exists('swipe', $settings) || rest_sanitize_boolean($settings['swipe'])) ? 'true' : 'false',
                ];
                $serializedAttributes = [];
                foreach ($sliderAttributes as $attribute => $value) {
                    $serializedAttributes[] = sanitize_key($attribute) . '="' . esc_attr($value) . '"';
                }
                $shortcode = '[h4_slider ' . implode(' ', $serializedAttributes) . ']';
                return $open . do_shortcode($shortcode) . $close;

            case 'html':
                $html = $this->sanitizeHtmlBlockContent((string) ($settings['html'] ?? ''));
                if ($html !== '') {
                    $html = shortcode_unautop(do_shortcode($html));
                }
                return $open . $html . $close;

            case 'spacer':
                $height = max(4, min(320, (int) ($settings['height'] ?? 24)));
                return $open . '<div style="height:' . esc_attr((string) $height) . 'px"></div>' . $close;

            case 'divider':
                $thickness = max(1, min(24, (int) ($settings['thickness'] ?? 1)));
                $style = sanitize_key((string) ($settings['style'] ?? 'solid'));
                if (! in_array($style, ['solid', 'dashed', 'dotted'], true)) {
                    $style = 'solid';
                }
                $color = sanitize_hex_color((string) ($settings['color'] ?? '#dbe7f3'));
                if (! is_string($color) || $color === '') {
                    $color = '#dbe7f3';
                }
                return $open . '<hr style="border:0;border-top:' . esc_attr((string) $thickness) . 'px ' . esc_attr($style) . ' ' . esc_attr($color) . ';">' . $close;

            case 'video':
                $srcRaw = trim((string) ($settings['src'] ?? $settings['url'] ?? ''));
                if ($srcRaw === '') {
                    $srcRaw = trim((string) ($settings['media_src'] ?? ''));
                }
                $provider = sanitize_key((string) ($settings['provider'] ?? 'youtube'));
                $height = max(180, min(1080, (int) ($settings['height'] ?? 360)));
                $autoplay = ! empty($settings['autoplay']) && (string) $settings['autoplay'] !== '0';
                $controls = ! array_key_exists('controls', $settings) || ((string) ($settings['controls'] ?? '1') !== '0');
                $muted = ! empty($settings['muted']) && (string) $settings['muted'] !== '0';
                $loop = ! empty($settings['loop']) && (string) $settings['loop'] !== '0';
                if (trim($srcRaw) === '' && trim((string) ($settings['hls_src'] ?? '')) === '') {
                    return $open . '<div class="h4bs-preview-shortcode">' . esc_html__('Video block (set URL in element settings).', 'help4-builder-suite') . '</div>' . $close;
                }
                if ($provider === 'youtube') {
                    $embed = $this->youtubeEmbedUrl($srcRaw, $autoplay, $controls, $muted, $loop);
                    if ($embed === '') {
                        return $open . '<div class="h4bs-preview-shortcode">' . esc_html__('Invalid YouTube URL/ID.', 'help4-builder-suite') . '</div>' . $close;
                    }
                    return $open . '<iframe class="h4bs-builder-video-embed" src="' . esc_url($embed) . '" loading="lazy" allowfullscreen style="width:100%;height:' . esc_attr((string) $height) . 'px;"></iframe>' . $close;
                }
                if ($provider === 'vimeo') {
                    $embed = $this->vimeoEmbedUrl($srcRaw, $autoplay, $controls, $muted, $loop);
                    if ($embed === '') {
                        return $open . '<div class="h4bs-preview-shortcode">' . esc_html__('Invalid Vimeo URL/ID.', 'help4-builder-suite') . '</div>' . $close;
                    }
                    return $open . '<iframe class="h4bs-builder-video-embed" src="' . esc_url($embed) . '" loading="lazy" allowfullscreen style="width:100%;height:' . esc_attr((string) $height) . 'px;"></iframe>' . $close;
                }
                if ($provider === 'embed') {
                    $embedSrc = esc_url($srcRaw);
                    if ($embedSrc === '') {
                        return $open . '<div class="h4bs-preview-shortcode">' . esc_html__('Invalid embed URL.', 'help4-builder-suite') . '</div>' . $close;
                    }
                    return $open . '<iframe class="h4bs-builder-video-embed" src="' . $embedSrc . '" loading="lazy" allowfullscreen style="width:100%;height:' . esc_attr((string) $height) . 'px;"></iframe>' . $close;
                }
                $hlsRaw = trim((string) ($settings['hls_src'] ?? ''));
                if ($provider === 'hls' && $hlsRaw === '') {
                    $hlsRaw = $srcRaw;
                    $srcRaw = '';
                }
                $fallbackVideo = trim((string) ($settings['fallback_src'] ?? ''));
                if ($fallbackVideo === '') {
                    $fallbackVideo = trim((string) ($settings['fallback_media_src'] ?? ''));
                }
                if ($provider === 'hls' && $fallbackVideo === '') {
                    $fallbackVideo = trim((string) ($settings['media_src'] ?? ''));
                }
                $poster = trim((string) ($settings['poster_src'] ?? $settings['poster'] ?? ''));
                if ($poster === '') {
                    $poster = trim((string) ($settings['poster_media_src'] ?? ''));
                }
                $fallbackImage = trim((string) ($settings['fallback_image_src'] ?? ''));
                if ($fallbackImage === '') {
                    $fallbackImage = trim((string) ($settings['fallback_image_media_src'] ?? ''));
                }
                $player = VideoPlayer::render(
                    [
                        'primary' => $srcRaw,
                        'hls' => $hlsRaw,
                        'fallback_video' => $fallbackVideo,
                        'poster' => $poster,
                        'fallback_image' => $fallbackImage,
                        'title' => (string) ($settings['title'] ?? __('Video player', 'help4-builder-suite')),
                        'height' => $height,
                        'aspect_ratio' => (string) ($settings['aspect_ratio'] ?? '16:9'),
                        'preload' => (string) ($settings['preload'] ?? 'metadata'),
                        'autoplay' => $autoplay,
                        'controls' => $controls,
                        'muted' => $muted,
                        'loop' => $loop,
                    ]
                );
                if ($player === '') {
                    return $open . '<div class="h4bs-preview-shortcode">' . esc_html__('Invalid self-hosted video URL.', 'help4-builder-suite') . '</div>' . $close;
                }
                return $open . $player . $close;

            case 'icon':
                $icon = sanitize_html_class((string) ($settings['icon'] ?? 'dashicons-star-filled'));
                $iconSet = sanitize_key((string) ($settings['icon_set'] ?? 'dashicons'));
                if (! in_array($iconSet, ['dashicons', 'fontawesome'], true)) {
                    $iconSet = 'dashicons';
                }
                $faClass = trim((string) ($settings['fa_class'] ?? 'fa-solid fa-star'));
                $faClass = preg_replace('/[^a-zA-Z0-9\-\s_]/', '', $faClass) ?: 'fa-solid fa-star';
                $color = sanitize_hex_color((string) ($settings['color'] ?? '#0f6cbf'));
                if (! is_string($color) || $color === '') {
                    $color = '#0f6cbf';
                }
                $size = max(12, min(160, (int) ($settings['size'] ?? 28)));
                $label = esc_html((string) ($settings['label'] ?? ''));
                $iconHtml = $iconSet === 'fontawesome'
                    ? '<i class="' . esc_attr($faClass) . ' h4bs-builder-icon" style="color:' . esc_attr($color) . ';font-size:' . esc_attr((string) $size) . 'px;" aria-hidden="true"></i>'
                    : '<span class="h4bs-builder-icon dashicons ' . esc_attr($icon) . '" style="color:' . esc_attr($color) . ';font-size:' . esc_attr((string) $size) . 'px;width:' . esc_attr((string) $size) . 'px;height:' . esc_attr((string) $size) . 'px;" aria-hidden="true"></span>';
                return $open
                    . '<span class="h4bs-builder-icon-wrap">'
                    . $iconHtml
                    . ($label !== '' ? '<span class="h4bs-builder-icon-label">' . $label . '</span>' : '')
                    . '</span>'
                    . $close;

            case 'list':
                $items = array_map('esc_html', $this->splitListItems((string) ($settings['items'] ?? '')));
                if (! $items) {
                    return $open . '<div class="h4bs-preview-shortcode">' . esc_html__('List block (add items in element settings).', 'help4-builder-suite') . '</div>' . $close;
                }
                $ordered = ! empty($settings['ordered']) && (string) $settings['ordered'] !== '0';
                $tag = $ordered ? 'ol' : 'ul';
                return $open . '<' . $tag . '><li>' . implode('</li><li>', $items) . '</li></' . $tag . '>' . $close;

            case 'quote':
                $quote = esc_html((string) ($settings['text'] ?? ''));
                $cite = esc_html((string) ($settings['cite'] ?? ''));
                return $open . '<blockquote><p>' . $quote . '</p>' . ($cite !== '' ? '<cite>' . $cite . '</cite>' : '') . '</blockquote>' . $close;

            case 'cta':
                $title = esc_html((string) ($settings['title'] ?? ''));
                $text = esc_html((string) ($settings['text'] ?? ''));
                $headingTag = sanitize_key((string) ($settings['heading_tag'] ?? 'h3'));
                if (! in_array($headingTag, ['h2', 'h3', 'h4', 'h5', 'h6'], true)) {
                    $headingTag = 'h3';
                }
                $buttonLabel = esc_html((string) ($settings['button_label'] ?? __('Learn More', 'help4-builder-suite')));
                $buttonUrl = esc_url((string) ($settings['button_url'] ?? '#'));
                $ctaLinkAttrs = $this->builderLinkAttributes(
                    (string) ($settings['button_target'] ?? 'same'),
                    (string) ($settings['button_rel'] ?? '')
                );
                return $open
                    . '<div class="h4bs-builder-cta">'
                    . ($title !== '' ? '<' . $headingTag . '>' . $title . '</' . $headingTag . '>' : '')
                    . ($text !== '' ? '<p>' . $text . '</p>' : '')
                    . '<a class="h4bs-builder-button" href="' . $buttonUrl . '"' . $ctaLinkAttrs . '>' . $buttonLabel . '</a>'
                    . '</div>'
                    . $close;

            case 'gallery':
                $imagesRaw = (string) ($settings['images'] ?? '');
                $columns = max(1, min(self::MAX_COLUMNS, (int) ($settings['columns'] ?? 3)));
                $sources = preg_split('/\s*,\s*/', trim($imagesRaw)) ?: [];
                $cells = [];
                foreach ($sources as $src) {
                    $srcRaw = (string) $src;
                    $src = esc_url($srcRaw);
                    if ($src !== '') {
                        $alt = esc_attr($this->imageAltTextFromUrl($srcRaw));
                        $cells[] = '<figure><img loading="lazy" src="' . $src . '" alt="' . $alt . '"></figure>';
                    }
                }
                if (! $cells) {
                    return $open . '<div class="h4bs-preview-shortcode">' . esc_html__('Gallery block (add image URLs in element settings).', 'help4-builder-suite') . '</div>' . $close;
                }
                return $open . '<div class="h4bs-builder-gallery" style="grid-template-columns:repeat(' . esc_attr((string) $columns) . ',minmax(0,1fr));">' . implode('', $cells) . '</div>' . $close;

            case 'map':
                $embedUrl = str_ireplace('%2C', ',', esc_url((string) ($settings['embed_url'] ?? '')));
                $height = max(180, min(1080, (int) ($settings['height'] ?? 360)));
                if ($embedUrl === '') {
                    return $open . '<div class="h4bs-preview-shortcode">' . esc_html__('Map block (set embed URL in element settings).', 'help4-builder-suite') . '</div>' . $close;
                }
                return $open . '<iframe class="h4bs-builder-map" src="' . $embedUrl . '" loading="lazy" allowfullscreen style="width:100%;height:' . esc_attr((string) $height) . 'px;"></iframe>' . $close;

            case 'faq':
                $itemsRaw = (string) ($settings['items'] ?? '');
                $itemsRaw = str_replace(['\\r\\n', '\\n', '\\r'], "\n", $itemsRaw);
                $openFirst = ! empty($settings['open_first']) && (string) $settings['open_first'] !== '0';
                $lines = preg_split('/\r\n|\r|\n/', $itemsRaw) ?: [];
                $items = [];
                foreach ($lines as $line) {
                    $parts = explode('::', (string) $line, 2);
                    if (count($parts) < 2) {
                        continue;
                    }
                    $q = trim((string) $parts[0]);
                    $a = trim((string) $parts[1]);
                    if ($q === '' || $a === '') {
                        continue;
                    }
                    $items[] = [
                        'q' => esc_html($q),
                        'a' => esc_html($a),
                    ];
                }
                if (! $items) {
                    return $open . '<div class="h4bs-preview-shortcode">' . esc_html__('FAQ block (add Question::Answer lines in element settings).', 'help4-builder-suite') . '</div>' . $close;
                }
                $html = '<div class="h4bs-builder-faq">';
                foreach ($items as $index => $item) {
                    $html .= '<details' . (($openFirst && $index === 0) ? ' open' : '') . '><summary>' . $item['q'] . '</summary><p>' . $item['a'] . '</p></details>';
                }
                $html .= '</div>';
                return $open . $html . $close;

            case 'collection':
                return $open . $this->renderCollectionBlock($id, $settings) . $close;

            case 'calendar':
                return $open . CalendarWorkspace::render($settings) . $close;

            case 'staff_directory':
                return $open . StaffDirectoryWorkspace::render($settings) . $close;

            case 'counter':
                $value = (int) ($settings['value'] ?? 0);
                $prefix = esc_html((string) ($settings['prefix'] ?? ''));
                $suffix = esc_html((string) ($settings['suffix'] ?? ''));
                $label = esc_html((string) ($settings['label'] ?? ''));
                return $open . '<div class="h4bs-builder-counter"><strong>' . $prefix . esc_html((string) $value) . $suffix . '</strong>' . ($label !== '' ? '<span>' . $label . '</span>' : '') . '</div>' . $close;

            case 'progress':
                $label = esc_html((string) ($settings['label'] ?? 'Progress'));
                $max = max(1, (int) ($settings['max'] ?? 100));
                $value = max(0, min($max, (int) ($settings['value'] ?? 0)));
                $percent = (int) round(($value / $max) * 100);
                return $open
                    . '<div class="h4bs-builder-progress">'
                    . '<div class="h4bs-builder-progress-head"><span>' . $label . '</span><span>' . esc_html((string) $percent) . '%</span></div>'
                    . '<div class="h4bs-builder-progress-track"><i style="width:' . esc_attr((string) $percent) . '%;"></i></div>'
                    . '</div>'
                    . $close;

            case 'menu':
                $menuSlug = sanitize_text_field((string) ($settings['menu_slug'] ?? ''));
                $orientation = sanitize_key((string) ($settings['orientation'] ?? 'horizontal'));
                if (! in_array($orientation, ['horizontal', 'vertical'], true)) {
                    $orientation = 'horizontal';
                }
                $dropdownMode = sanitize_key((string) ($settings['dropdown_mode'] ?? 'hover'));
                if (! in_array($dropdownMode, ['hover', 'open', 'none'], true)) {
                    $dropdownMode = 'hover';
                }
                $depth = max(0, min(6, (int) ($settings['depth'] ?? 0)));
                $mobileHamburger = ! array_key_exists('mobile_hamburger', $settings)
                    || rest_sanitize_boolean($settings['mobile_hamburger']);
                $menuLabel = sanitize_text_field((string) ($settings['menu_label'] ?? __('Site Menu', 'help4-builder-suite')));
                if ($menuLabel === '') {
                    $menuLabel = __('Site Menu', 'help4-builder-suite');
                }
                $menuDomId = sanitize_html_class($id . '-menu');
                $menuHtml = '';
                if ($menuSlug !== '') {
                    $menuHtml = (string) wp_nav_menu(
                        [
                            'menu'            => $menuSlug,
                            'container'       => false,
                            'echo'            => false,
                            'fallback_cb'     => false,
                            'depth'           => $depth,
                            'menu_id'         => $menuDomId,
                            'menu_class'      => 'h4bs-builder-menu-list',
                            'items_wrap'      => '<ul id="%1$s" class="%2$s">%3$s</ul>',
                        ]
                    );
                }
                if ($menuHtml === '') {
                    $menuHtml = '<span>' . esc_html__('Set a WordPress menu slug in element settings.', 'help4-builder-suite') . '</span>';
                }
                $menuClasses = [
                    'h4bs-builder-menu',
                    'is-' . $orientation,
                    'has-dropdown-' . $dropdownMode,
                ];
                if ($mobileHamburger) {
                    $menuClasses[] = 'has-mobile-hamburger';
                }
                $toggle = $mobileHamburger
                    ? '<button class="h4bs-builder-menu-toggle" type="button" aria-expanded="false" aria-controls="' . esc_attr($menuDomId) . '" data-h4bs-menu-toggle>'
                        . '<span class="h4bs-builder-menu-toggle-icon" aria-hidden="true"><i></i><i></i><i></i></span>'
                        . '<span class="h4bs-builder-menu-toggle-label">' . esc_html($menuLabel) . '</span>'
                    . '</button>'
                    : '';
                return $open
                    . '<nav class="' . esc_attr(implode(' ', $menuClasses)) . '" aria-label="' . esc_attr($menuLabel) . '" data-h4bs-builder-menu>'
                    . $toggle
                    . '<div class="h4bs-builder-menu-panel" data-h4bs-menu-panel>'
                    . $menuHtml
                    . '</div>'
                    . '</nav>'
                    . $close;

            case 'container':
                $columns = max(1, min(self::MAX_COLUMNS, (int) ($settings['columns'] ?? $structureDefaults['columns'])));
                $gap = max(0, min(120, (int) ($settings['gap'] ?? $structureDefaults['gap'])));
                $minHeight = max(60, min(1000, (int) ($settings['min_height'] ?? $structureDefaults['min_height'])));
                $containerContext = $this->sectionLayoutContext($settings, $columns, $gap, $minHeight);
                $labelsRaw = (string) ($settings['column_labels'] ?? 'Column 1|Column 2');
                $labels = preg_split('/\s*\|\s*/', $labelsRaw) ?: [];
                $note = trim((string) ($settings['note'] ?? ''));
                $cells = [];
                for ($i = 0; $i < $columns; $i++) {
                    if (isset($labels[$i]) && trim((string) $labels[$i]) !== '') {
                        $label = trim((string) $labels[$i]);
                    } else {
                        /* translators: %d: Column number. */
                        $label = sprintf(__('Column %d', 'help4-builder-suite'), $i + 1);
                    }
                    $columnStyle = $this->sectionColumnStyle(
                        (string) ($containerContext['layout_mode'] ?? 'grid'),
                        is_array($containerContext['column_widths'] ?? null) ? $containerContext['column_widths'] : [],
                        $i,
                        is_array($containerContext['column_widths_tablet'] ?? null) ? $containerContext['column_widths_tablet'] : [],
                        is_array($containerContext['column_widths_mobile'] ?? null) ? $containerContext['column_widths_mobile'] : []
                    );
                    $columnStyleAttr = $columnStyle !== '' ? ' style="' . esc_attr($columnStyle) . '"' : '';
                    $slotBlocks = $this->childLayoutBlocks($layout, (string) ($block['id'] ?? ''), $i);
                    $slotBody = '';
                    foreach ($slotBlocks as $slotBlock) {
                        $slotBody .= $this->renderBlock($slotBlock, $layout);
                    }
                    $cells[] = '<div class="h4bs-builder-section-col"' . $columnStyleAttr . '>'
                        . '<span class="h4bs-builder-section-col-label">' . esc_html($label) . '</span>'
                        . '<div class="h4bs-builder-section-slot">'
                        . $slotBody
                        . '</div>'
                        . '</div>';
                }
                return $open
                    . $this->renderBackgroundVideo($settings)
                    . '<div class="h4bs-builder-container is-' . esc_attr($containerContext['layout_mode']) . '" style="' . esc_attr($containerContext['style']) . '">'
                    . ($note !== '' ? '<div class="h4bs-builder-container-note">' . esc_html($note) . '</div>' : '')
                    . implode('', $cells)
                    . '</div>'
                    . $close;

            case 'inner_section':
                $columns = max(1, min(self::MAX_COLUMNS, (int) ($settings['columns'] ?? $structureDefaults['columns'])));
                $gap = max(0, min(120, (int) ($settings['gap'] ?? $structureDefaults['gap'])));
                $minHeight = max(60, min(1000, (int) ($settings['min_height'] ?? $structureDefaults['min_height'])));
                $innerSectionContext = $this->sectionLayoutContext($settings, $columns, $gap, $minHeight);
                $labelsRaw = (string) ($settings['column_labels'] ?? 'Column 1|Column 2');
                $labels = preg_split('/\s*\|\s*/', $labelsRaw) ?: [];
                $cells = [];
                for ($i = 0; $i < $columns; $i++) {
                    if (isset($labels[$i]) && trim((string) $labels[$i]) !== '') {
                        $label = trim((string) $labels[$i]);
                    } else {
                        /* translators: %d: Column number. */
                        $label = sprintf(__('Column %d', 'help4-builder-suite'), $i + 1);
                    }
                    $columnStyle = $this->sectionColumnStyle(
                        (string) ($innerSectionContext['layout_mode'] ?? 'grid'),
                        is_array($innerSectionContext['column_widths'] ?? null) ? $innerSectionContext['column_widths'] : [],
                        $i,
                        is_array($innerSectionContext['column_widths_tablet'] ?? null) ? $innerSectionContext['column_widths_tablet'] : [],
                        is_array($innerSectionContext['column_widths_mobile'] ?? null) ? $innerSectionContext['column_widths_mobile'] : []
                    );
                    $columnStyleAttr = $columnStyle !== '' ? ' style="' . esc_attr($columnStyle) . '"' : '';
                    $slotBlocks = $this->childLayoutBlocks($layout, (string) ($block['id'] ?? ''), $i);
                    $slotBody = '';
                    foreach ($slotBlocks as $slotBlock) {
                        $slotBody .= $this->renderBlock($slotBlock, $layout);
                    }
                    $cells[] = '<div class="h4bs-builder-inner-col"' . $columnStyleAttr . '>'
                        . '<span class="h4bs-builder-section-col-label">' . esc_html($label) . '</span>'
                        . '<div class="h4bs-builder-section-slot h4bs-builder-inner-slot">'
                        . $slotBody
                        . '</div>'
                        . '</div>';
                }
                return $open
                    . '<div class="h4bs-builder-inner-section is-' . esc_attr($innerSectionContext['layout_mode']) . '" style="' . esc_attr($innerSectionContext['style']) . '">'
                    . implode('', $cells)
                    . '</div>'
                    . $close;
        }

        return $open . $close;
    }

    /** @param array<string, mixed> $settings */
    private function renderCollectionBlock(string $id, array $settings): string
    {
        $items = is_array($settings['items'] ?? null) ? $settings['items'] : [];
        $filters = is_array($settings['filters'] ?? null) ? $settings['filters'] : [];
        $defaultView = in_array(($settings['default_view'] ?? 'grid'), ['grid', 'list'], true) ? (string) $settings['default_view'] : 'grid';
        $columns = max(1, min(6, (int) ($settings['columns'] ?? 3)));
        $tablet = max(1, min(4, (int) ($settings['columns_tablet'] ?? 2)));
        $mobile = max(1, min(2, (int) ($settings['columns_mobile'] ?? 1)));
        $filterKeys = [];
        $filterHtml = '';
        foreach ($filters as $filter) {
            if (! is_array($filter)) {
                continue;
            }
            $key = sanitize_key((string) ($filter['key'] ?? ''));
            if ($key === '' || in_array($key, $filterKeys, true)) {
                continue;
            }
            $filterKeys[] = $key;
            $label = sanitize_text_field((string) ($filter['label'] ?? ucfirst($key)));
            $values = [];
            foreach ($items as $item) {
                if (is_array($item) && trim((string) ($item[$key] ?? '')) !== '') {
                    $values[] = trim((string) $item[$key]);
                }
            }
            $values = array_values(array_unique($values));
            natcasesort($values);
            $filterId = sanitize_html_class($id . '-filter-' . $key);
            /* translators: %s: collection filter label. */
            $allLabel = sprintf(__('All %s', 'help4-builder-suite'), $label);
            $filterHtml .= '<label for="' . esc_attr($filterId) . '"><span>' . esc_html($label) . '</span><select id="' . esc_attr($filterId) . '" data-h4bs-collection-filter="' . esc_attr($key) . '"><option value="">' . esc_html($allLabel) . '</option>';
            foreach ($values as $value) {
                $filterHtml .= '<option value="' . esc_attr(strtolower($value)) . '">' . esc_html($value) . '</option>';
            }
            $filterHtml .= '</select></label>';
        }
        $cards = '';
        foreach ($items as $index => $item) {
            if (! is_array($item)) {
                continue;
            }
            $title = sanitize_text_field((string) ($item['title'] ?? ''));
            if ($title === '') {
                continue;
            }
            $description = sanitize_textarea_field((string) ($item['description'] ?? ''));
            $group = sanitize_text_field((string) ($item['group'] ?? 'Other')) ?: 'Other';
            $badge = sanitize_text_field((string) ($item['badge'] ?? ''));
            $url = esc_url((string) ($item['url'] ?? ''));
            $attrs = '';
            foreach ($filterKeys as $key) {
                $attrs .= ' data-filter-' . esc_attr($key) . '="' . esc_attr(strtolower(trim((string) ($item[$key] ?? '')))) . '"';
            }
            $search = strtolower(trim($title . ' ' . $description . ' ' . implode(' ', array_map('strval', $item))));
            $cards .= '<article class="h4bs-collection-item" data-h4bs-collection-item data-group="' . esc_attr($group) . '" data-search="' . esc_attr($search) . '"' . $attrs . '>'
                . ($badge !== '' ? '<span class="h4bs-collection-badge">' . esc_html($badge) . '</span>' : '')
                . '<h3>' . ($url !== '' ? '<a href="' . $url . '">' . esc_html($title) . '</a>' : esc_html($title)) . '</h3>'
                . ($description !== '' ? '<p>' . nl2br(esc_html($description)) . '</p>' : '')
                . '</article>';
        }
        $searchId = sanitize_html_class($id . '-search');
        return '<section class="h4bs-collection is-' . esc_attr($defaultView) . '" data-h4bs-collection data-default-view="' . esc_attr($defaultView) . '" style="--h4bs-collection-columns:' . $columns . ';--h4bs-collection-columns-tablet:' . $tablet . ';--h4bs-collection-columns-mobile:' . $mobile . '">'
            . '<div class="h4bs-collection-toolbar"><div class="h4bs-collection-view-switch" role="group" aria-label="' . esc_attr__('Collection view', 'help4-builder-suite') . '"><button type="button" data-h4bs-collection-view="grid" aria-pressed="' . ($defaultView === 'grid' ? 'true' : 'false') . '">' . esc_html((string) ($settings['grid_label'] ?? __('Grid by group', 'help4-builder-suite'))) . '</button><button type="button" data-h4bs-collection-view="list" aria-pressed="' . ($defaultView === 'list' ? 'true' : 'false') . '">' . esc_html((string) ($settings['list_label'] ?? __('Filterable list', 'help4-builder-suite'))) . '</button></div>'
            . '<label class="h4bs-collection-search" for="' . esc_attr($searchId) . '"><span>' . esc_html((string) ($settings['search_label'] ?? __('Search items', 'help4-builder-suite'))) . '</span><input id="' . esc_attr($searchId) . '" type="search" data-h4bs-collection-search></label>'
            . '<div class="h4bs-collection-filters">' . $filterHtml . '</div></div>'
            . '<div class="h4bs-collection-results" data-h4bs-collection-results>' . $cards . '</div>'
            . '<p class="h4bs-collection-empty" data-h4bs-collection-empty hidden>' . esc_html((string) ($settings['empty_text'] ?? __('No items match your filters.', 'help4-builder-suite'))) . '</p><p class="screen-reader-text" aria-live="polite" data-h4bs-collection-status></p></section>';
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function advancedBlockAttributes(array $settings): string
    {
        $attributes = [];
        $title = sanitize_text_field((string) ($settings['advanced_html_title'] ?? ''));
        if ($title !== '') {
            $attributes[] = 'title="' . esc_attr($title) . '"';
        }

        $ariaLabel = sanitize_text_field((string) ($settings['advanced_aria_label'] ?? ''));
        if ($ariaLabel !== '') {
            $attributes[] = 'aria-label="' . esc_attr($ariaLabel) . '"';
        }

        $role = $this->enumToken(
            (string) ($settings['advanced_role'] ?? ''),
            ['region', 'navigation', 'main', 'banner', 'contentinfo', 'complementary', 'article', 'form', 'search', 'group'],
            ''
        );
        if ($role !== '') {
            $attributes[] = 'role="' . esc_attr($role) . '"';
        }

        $tabindex = (string) ($settings['advanced_tabindex'] ?? '');
        if (in_array($tabindex, ['-1', '0'], true)) {
            $attributes[] = 'tabindex="' . esc_attr($tabindex) . '"';
        }
        if (rest_sanitize_boolean($settings['advanced_aria_hidden'] ?? false)) {
            $attributes[] = 'aria-hidden="true"';
        }

        $rawData = (string) ($settings['advanced_data_attributes'] ?? '');
        $lines = preg_split('/\r\n|\r|\n/', $rawData) ?: [];
        foreach ($lines as $line) {
            if (preg_match('/^\s*(data-[a-z0-9_.:-]+)\s*=\s*(.*?)\s*$/i', (string) $line, $matches) !== 1) {
                continue;
            }
            $name = strtolower((string) ($matches[1] ?? ''));
            if (str_starts_with($name, 'data-h4bs-')) {
                continue;
            }
            $value = sanitize_text_field((string) ($matches[2] ?? ''));
            $attributes[] = esc_attr($name) . '="' . esc_attr($value) . '"';
        }

        return $attributes ? ' ' . implode(' ', array_values(array_unique($attributes))) : '';
    }

    private function builderLinkAttributes(string $targetRaw, string $relRaw): string
    {
        $newWindow = sanitize_key($targetRaw) === 'new';
        $relTokens = preg_split('/\s+/', strtolower(trim($relRaw))) ?: [];
        $relTokens = array_values(
            array_unique(
                array_filter(
                    array_map(
                        static function (string $token): string {
                            return preg_replace('/[^a-z0-9_-]/', '', $token) ?: '';
                        },
                        $relTokens
                    )
                )
            )
        );
        if ($newWindow) {
            $relTokens[] = 'noopener';
            $relTokens[] = 'noreferrer';
            $relTokens = array_values(array_unique($relTokens));
        }

        $attributes = $newWindow ? ' target="_blank"' : '';
        if ($relTokens) {
            $attributes .= ' rel="' . esc_attr(implode(' ', $relTokens)) . '"';
        }

        return $attributes;
    }

    /**
     * @param array<int, array<string, mixed>> $layout
     * @return array<int, array<string, mixed>>
     */
    private function topLevelLayoutBlocks(array $layout): array
    {
        $containerIds = [];
        foreach ($layout as $block) {
            if (! is_array($block)) {
                continue;
            }
            $type = (string) ($block['type'] ?? '');
            if (! in_array($type, ['section', 'container', 'inner_section'], true)) {
                continue;
            }
            $containerId = sanitize_key((string) ($block['id'] ?? ''));
            if ($containerId !== '') {
                $containerIds[$containerId] = true;
            }
        }

        $out = [];
        foreach ($layout as $block) {
            if (! is_array($block)) {
                continue;
            }
            $parentId = sanitize_key((string) ($block['parent_id'] ?? ''));
            if ($parentId !== '' && isset($containerIds[$parentId])) {
                continue;
            }
            $out[] = $block;
        }
        return $out;
    }

    /**
     * @param array<int, array<string, mixed>> $layout
     * @return array<int, array<string, mixed>>
     */
    private function childLayoutBlocks(array $layout, string $parentId, int $slot): array
    {
        $out = [];
        $parent = sanitize_key($parentId);
        $targetSlot = max(0, min(self::MAX_SLOT, $slot));
        foreach ($layout as $block) {
            if (! is_array($block)) {
                continue;
            }
            $rowParent = sanitize_key((string) ($block['parent_id'] ?? ''));
            $rowSlot = max(0, min(self::MAX_SLOT, (int) ($block['slot'] ?? 0)));
            if ($rowParent === $parent && $rowSlot === $targetSlot) {
                $out[] = $block;
            }
        }
        return $out;
    }

    /**
     * @param array<string, mixed> $settings
     * @return array{layout_mode:string, style:string, column_widths:array<int, float>}
     */
    private function sectionLayoutContext(array $settings, int $columns, int $gap, int $minHeight): array
    {
        $structureDefaults = $this->structureLayoutDefaults();
        $layoutMode = $this->enumToken(
            (string) ($settings['layout_mode'] ?? $structureDefaults['layout_mode']),
            ['grid', 'flex'],
            'grid'
        );
        $widthMode = $this->enumToken(
            (string) ($settings['content_width_mode'] ?? $structureDefaults['content_width_mode']),
            ['inherit', 'custom', 'full'],
            'inherit'
        );
        $customWidth = $this->cssDimension(
            (string) ($settings['content_width'] ?? ''),
            (string) ($settings['content_width_unit'] ?? 'px')
        );
        $columnWidths = $this->normalizedColumnWidths(
            (string) ($settings['column_widths'] ?? ''),
            $columns
        );
        $maxWidth = $widthMode === 'custom' && $customWidth !== ''
            ? $customWidth
            : $this->globalContainerMaxWidthCss();

        $responsiveInteger = static function (array $source, string $key, int $fallback, int $minimum, int $maximum): int {
            $raw = trim((string) ($source[$key] ?? ''));
            if ($raw === '' || ! is_numeric($raw)) {
                return max($minimum, min($maximum, $fallback));
            }
            return max($minimum, min($maximum, (int) $raw));
        };
        $tabletColumns = $responsiveInteger($settings, 'columns_tablet', min($columns, 2), 1, self::MAX_COLUMNS);
        $mobileColumns = $responsiveInteger($settings, 'columns_mobile', 1, 1, self::MAX_COLUMNS);
        $tabletColumnWidths = $this->normalizedColumnWidths((string) ($settings['column_widths_tablet'] ?? ''), $tabletColumns);
        $mobileColumnWidths = $this->normalizedColumnWidths((string) ($settings['column_widths_mobile'] ?? ''), $mobileColumns);
        $tabletGap = $responsiveInteger($settings, 'gap_tablet', $gap, 0, 180);
        $mobileGap = $responsiveInteger($settings, 'gap_mobile', $gap, 0, 180);
        $tabletMinHeight = $responsiveInteger($settings, 'min_height_tablet', $minHeight, 40, 2400);
        $mobileMinHeight = $responsiveInteger($settings, 'min_height_mobile', max(80, min($minHeight, 900)), 40, 2400);
        $tabletWidth = $this->cssDimension(
            (string) ($settings['content_width_tablet'] ?? ''),
            (string) ($settings['content_width_tablet_unit'] ?? 'px')
        );
        $mobileWidth = $this->cssDimension(
            (string) ($settings['content_width_mobile'] ?? ''),
            (string) ($settings['content_width_mobile_unit'] ?? '%')
        );
        $desktopAlignItems = $this->enumToken(
            (string) ($settings['align_items'] ?? $structureDefaults['align_items']),
            ['stretch', 'flex-start', 'center', 'flex-end', 'baseline'],
            'stretch'
        );
        $desktopJustifyContent = $this->enumToken(
            (string) ($settings['justify_content'] ?? $structureDefaults['justify_content']),
            ['flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'],
            'flex-start'
        );
        $desktopFlexDirection = $this->enumToken(
            (string) ($settings['flex_direction'] ?? $structureDefaults['flex_direction']),
            ['row', 'row-reverse', 'column', 'column-reverse'],
            'row'
        );
        $desktopFlexWrap = $this->enumToken(
            (string) ($settings['flex_wrap'] ?? $structureDefaults['flex_wrap']),
            ['nowrap', 'wrap', 'wrap-reverse'],
            'wrap'
        );
        $desktopAlignContent = $this->enumToken(
            (string) ($settings['align_content'] ?? $structureDefaults['align_content']),
            ['stretch', 'flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'],
            'stretch'
        );
        $tabletLayoutMode = $this->enumToken((string) ($settings['layout_mode_tablet'] ?? ''), ['grid', 'flex'], $layoutMode);
        $mobileLayoutMode = $this->enumToken((string) ($settings['layout_mode_mobile'] ?? ''), ['grid', 'flex'], $tabletLayoutMode);
        $tabletWidthMode = $this->enumToken((string) ($settings['content_width_mode_tablet'] ?? ''), ['inherit', 'custom', 'full'], $widthMode);
        $mobileWidthMode = $this->enumToken((string) ($settings['content_width_mode_mobile'] ?? ''), ['inherit', 'custom', 'full'], $tabletWidthMode);
        $tabletAlignItems = $this->enumToken((string) ($settings['align_items_tablet'] ?? ''), ['stretch', 'flex-start', 'center', 'flex-end', 'baseline'], $desktopAlignItems);
        $mobileAlignItems = $this->enumToken((string) ($settings['align_items_mobile'] ?? ''), ['stretch', 'flex-start', 'center', 'flex-end', 'baseline'], $tabletAlignItems);
        $tabletJustifyContent = $this->enumToken((string) ($settings['justify_content_tablet'] ?? ''), ['flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'], $desktopJustifyContent);
        $mobileJustifyContent = $this->enumToken((string) ($settings['justify_content_mobile'] ?? ''), ['flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'], $tabletJustifyContent);
        $tabletFlexDirection = $this->enumToken((string) ($settings['flex_direction_tablet'] ?? ''), ['row', 'row-reverse', 'column', 'column-reverse'], $desktopFlexDirection);
        $mobileFlexDirection = $this->enumToken((string) ($settings['flex_direction_mobile'] ?? ''), ['row', 'row-reverse', 'column', 'column-reverse'], $tabletFlexDirection);
        $tabletFlexWrap = $this->enumToken((string) ($settings['flex_wrap_tablet'] ?? ''), ['nowrap', 'wrap', 'wrap-reverse'], $desktopFlexWrap);
        $mobileFlexWrap = $this->enumToken((string) ($settings['flex_wrap_mobile'] ?? ''), ['nowrap', 'wrap', 'wrap-reverse'], $tabletFlexWrap);
        $tabletAlignContent = $this->enumToken((string) ($settings['align_content_tablet'] ?? ''), ['stretch', 'flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'], $desktopAlignContent);
        $mobileAlignContent = $this->enumToken((string) ($settings['align_content_mobile'] ?? ''), ['stretch', 'flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'], $tabletAlignContent);

        $styles = [
            '--h4bs-col:' . (string) max(1, min(self::MAX_COLUMNS, $columns)),
            '--h4bs-col-tablet:' . (string) $tabletColumns,
            '--h4bs-col-mobile:' . (string) $mobileColumns,
            '--h4bs-gap:' . (string) max(0, min(180, $gap)) . 'px',
            '--h4bs-gap-tablet:' . (string) $tabletGap . 'px',
            '--h4bs-gap-mobile:' . (string) $mobileGap . 'px',
            '--h4bs-min-height:' . (string) max(40, min(2400, $minHeight)) . 'px',
            '--h4bs-min-height-tablet:' . (string) $tabletMinHeight . 'px',
            '--h4bs-min-height-mobile:' . (string) $mobileMinHeight . 'px',
            '--h4bs-align-items:' . $desktopAlignItems,
            '--h4bs-align-items-tablet:' . $tabletAlignItems,
            '--h4bs-align-items-mobile:' . $mobileAlignItems,
            '--h4bs-justify-content:' . $desktopJustifyContent,
            '--h4bs-justify-content-tablet:' . $tabletJustifyContent,
            '--h4bs-justify-content-mobile:' . $mobileJustifyContent,
            '--h4bs-flex-direction:' . $desktopFlexDirection,
            '--h4bs-flex-direction-tablet:' . $tabletFlexDirection,
            '--h4bs-flex-direction-mobile:' . $mobileFlexDirection,
            '--h4bs-flex-wrap:' . $desktopFlexWrap,
            '--h4bs-flex-wrap-tablet:' . $tabletFlexWrap,
            '--h4bs-flex-wrap-mobile:' . $mobileFlexWrap,
            '--h4bs-align-content:' . $desktopAlignContent,
            '--h4bs-align-content-tablet:' . $tabletAlignContent,
            '--h4bs-align-content-mobile:' . $mobileAlignContent,
            '--h4bs-display-tablet:' . ($tabletLayoutMode === 'flex' ? 'flex' : 'grid'),
            '--h4bs-display-mobile:' . ($mobileLayoutMode === 'flex' ? 'flex' : 'grid'),
            'min-height:var(--h4bs-min-height)',
        ];
        $responsiveWidthFallback = $widthMode === 'full' ? 'none' : ($maxWidth !== '' ? $maxWidth : '100%');
        $tabletMaxWidth = $tabletWidthMode === 'full'
            ? 'none'
            : ($tabletWidthMode === 'custom' && $tabletWidth !== '' ? $tabletWidth : $responsiveWidthFallback);
        $mobileMaxWidth = $mobileWidthMode === 'full'
            ? 'none'
            : ($mobileWidthMode === 'custom' && $mobileWidth !== '' ? $mobileWidth : $tabletMaxWidth);
        $styles[] = '--h4bs-max-width-tablet:' . $tabletMaxWidth;
        $styles[] = '--h4bs-max-width-mobile:' . $mobileMaxWidth;
        $styles[] = '--h4bs-grid-template:' . $this->gridTrackTemplate($columnWidths, $columns);
        $styles[] = '--h4bs-grid-template-tablet:' . $this->gridTrackTemplate($tabletColumnWidths, $tabletColumns);
        $styles[] = '--h4bs-grid-template-mobile:' . $this->gridTrackTemplate($mobileColumnWidths, $mobileColumns);

        if ($widthMode === 'full') {
            $styles[] = 'width:100%';
            $styles[] = 'max-width:none';
        } elseif ($maxWidth !== '') {
            $styles[] = 'width:100%';
            $styles[] = 'max-width:' . $maxWidth;
            $styles[] = 'margin-left:auto';
            $styles[] = 'margin-right:auto';
        }

        if ($layoutMode === 'flex') {
            $styles[] = 'display:flex';
            $styles[] = 'flex-direction:var(--h4bs-flex-direction)';
            $styles[] = 'flex-wrap:var(--h4bs-flex-wrap)';
            $styles[] = 'justify-content:var(--h4bs-justify-content)';
            $styles[] = 'align-items:var(--h4bs-align-items)';
            $styles[] = 'align-content:var(--h4bs-align-content)';
        }
        if ($layoutMode === 'grid' && count($columnWidths) === max(1, min(self::MAX_COLUMNS, $columns))) {
            $template = [];
            foreach ($columnWidths as $columnWidth) {
                $value = $this->fractionValue((float) $columnWidth);
                if ($value !== '') {
                    $template[] = 'minmax(0,' . $value . ')';
                }
            }
            if ($template) {
                $styles[] = 'grid-template-columns:' . implode(' ', $template);
            }
        }

        return [
            'layout_mode' => $layoutMode,
            'style' => implode(';', $styles),
            'column_widths' => $columnWidths,
            'column_widths_tablet' => $tabletColumnWidths,
            'column_widths_mobile' => $mobileColumnWidths,
        ];
    }

    /**
     * @return array{
     *   columns:int,
     *   gap:int,
     *   min_height:int,
     *   layout_mode:string,
     *   content_width_mode:string,
     *   flex_direction:string,
     *   flex_wrap:string,
     *   justify_content:string,
     *   align_items:string,
     *   align_content:string
     * }
     */
    private function structureLayoutDefaults(): array
    {
        $tokens = $this->globalDesignTokens();
        $layout = is_array($tokens['layout'] ?? null) ? $tokens['layout'] : [];

        return [
            'columns' => max(1, min(self::MAX_COLUMNS, (int) ($layout['structure_columns'] ?? 2))),
            'gap' => max(0, min(160, (int) ($layout['structure_gap'] ?? 18))),
            'min_height' => max(40, min(2400, (int) ($layout['structure_min_height'] ?? 140))),
            'layout_mode' => $this->enumToken((string) ($layout['structure_layout_mode'] ?? 'grid'), ['grid', 'flex'], 'grid'),
            'content_width_mode' => $this->enumToken((string) ($layout['structure_content_width_mode'] ?? 'inherit'), ['inherit', 'custom', 'full'], 'inherit'),
            'flex_direction' => $this->enumToken((string) ($layout['structure_flex_direction'] ?? 'row'), ['row', 'row-reverse', 'column', 'column-reverse'], 'row'),
            'flex_wrap' => $this->enumToken((string) ($layout['structure_flex_wrap'] ?? 'wrap'), ['nowrap', 'wrap', 'wrap-reverse'], 'wrap'),
            'justify_content' => $this->enumToken((string) ($layout['structure_justify_content'] ?? 'flex-start'), ['flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'], 'flex-start'),
            'align_items' => $this->enumToken((string) ($layout['structure_align_items'] ?? 'stretch'), ['stretch', 'flex-start', 'center', 'flex-end', 'baseline'], 'stretch'),
            'align_content' => $this->enumToken((string) ($layout['structure_align_content'] ?? 'stretch'), ['stretch', 'flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'], 'stretch'),
        ];
    }

    private function globalContainerMaxWidthCss(): string
    {
        $tokens = $this->globalDesignTokens();
        $layout = is_array($tokens['layout'] ?? null) ? $tokens['layout'] : [];
        $raw = (string) ($layout['container_max_width'] ?? '1200');
        $unit = (string) ($layout['container_max_width_unit'] ?? 'px');
        return $this->cssDimension($raw, $unit, '1200px');
    }

    private function globalDesignStyle(): string
    {
        $tokens = $this->globalDesignTokens();
        $colors = is_array($tokens['colors'] ?? null) ? $tokens['colors'] : [];
        $fonts = is_array($tokens['fonts'] ?? null) ? $tokens['fonts'] : [];
        $typography = is_array($tokens['typography'] ?? null) ? $tokens['typography'] : [];
        $spacing = is_array($tokens['spacing'] ?? null) ? $tokens['spacing'] : [];
        $shape = is_array($tokens['shape'] ?? null) ? $tokens['shape'] : [];
        $components = is_array($tokens['components'] ?? null) ? $tokens['components'] : [];
        $layout = is_array($tokens['layout'] ?? null) ? $tokens['layout'] : [];

        $styles = [];
        foreach ($colors as $key => $value) {
            $safeKey = sanitize_key((string) $key);
            $safeValue = sanitize_hex_color((string) $value);
            if ($safeKey !== '' && is_string($safeValue) && $safeValue !== '') {
                $styles[] = '--h4bs-color-' . str_replace('_', '-', $safeKey) . ':' . $safeValue;
            }
        }

        foreach (['display', 'heading', 'body', 'ui'] as $key) {
            $family = $this->cssToken((string) ($fonts[$key] ?? ''));
            if ($family !== '') {
                $styles[] = '--h4bs-font-' . $key . ':\'' . str_replace("'", '', $family) . '\',sans-serif';
            }
        }

        $dimensionVariables = [
            'base-size' => [(string) ($fonts['base_size'] ?? '16'), 'px'],
            'h1-size' => [(string) ($typography['h1_size'] ?? '56'), 'px'],
            'h2-size' => [(string) ($typography['h2_size'] ?? '42'), 'px'],
            'h3-size' => [(string) ($typography['h3_size'] ?? '32'), 'px'],
            'h4-size' => [(string) ($typography['h4_size'] ?? '25'), 'px'],
            'h5-size' => [(string) ($typography['h5_size'] ?? '20'), 'px'],
            'h6-size' => [(string) ($typography['h6_size'] ?? '17'), 'px'],
            'page-gap' => [(string) ($spacing['page_gap'] ?? '16'), 'px'],
            'section-x' => [(string) ($spacing['section_x'] ?? '24'), 'px'],
            'section-y' => [(string) ($spacing['section_y'] ?? '64'), 'px'],
            'card-gap' => [(string) ($spacing['card_gap'] ?? '20'), 'px'],
            'radius-sm' => [(string) ($shape['radius_sm'] ?? '4'), 'px'],
            'radius-md' => [(string) ($shape['radius_md'] ?? '8'), 'px'],
            'radius-lg' => [(string) ($shape['radius_lg'] ?? '16'), 'px'],
            'button-padding-x' => [(string) ($components['button_padding_x'] ?? '20'), 'px'],
            'button-padding-y' => [(string) ($components['button_padding_y'] ?? '12'), 'px'],
            'button-radius' => [(string) ($components['button_radius'] ?? '8'), 'px'],
            'card-padding' => [(string) ($components['card_padding'] ?? '28'), 'px'],
            'field-height' => [(string) ($components['field_height'] ?? '46'), 'px'],
            'field-padding-x' => [(string) ($components['field_padding_x'] ?? '14'), 'px'],
            'field-radius' => [(string) ($components['field_radius'] ?? '6'), 'px'],
        ];
        foreach ($dimensionVariables as $key => $parts) {
            $dimension = $this->cssDimension($parts[0], $parts[1]);
            if ($dimension !== '') {
                $styles[] = '--h4bs-' . $key . ':' . $dimension;
            }
        }

        foreach (
            [
                'heading-weight' => (string) ($typography['heading_weight'] ?? '700'),
                'body-weight' => (string) ($typography['body_weight'] ?? '400'),
                'heading-line-height' => (string) ($typography['heading_line_height'] ?? '1.12'),
                'body-line-height' => (string) ($typography['body_line_height'] ?? '1.65'),
                'button-weight' => (string) ($components['button_weight'] ?? '700'),
            ] as $key => $value
        ) {
            $safeValue = $this->cssToken($value);
            if ($safeValue !== '') {
                $styles[] = '--h4bs-' . $key . ':' . $safeValue;
            }
        }

        foreach (['shadow_sm' => 'shadow-sm', 'shadow_md' => 'shadow-md'] as $sourceKey => $cssKey) {
            $shadow = $this->cssToken((string) ($shape[$sourceKey] ?? ''));
            if ($shadow !== '') {
                $styles[] = '--h4bs-' . $cssKey . ':' . $shadow;
            }
        }

        $containerMaxWidth = $this->cssDimension(
            (string) ($layout['container_max_width'] ?? '1200'),
            (string) ($layout['container_max_width_unit'] ?? 'px'),
            '1200px'
        );
        if ($containerMaxWidth !== '') {
            $styles[] = '--h4bs-container-max-width:' . $containerMaxWidth;
        }

        return implode(';', $styles);
    }

    /**
     * @return array<int, float>
     */
    private function normalizedColumnWidths(string $raw, int $columns): array
    {
        $count = max(1, min(self::MAX_COLUMNS, $columns));
        if (trim($raw) === '') {
            return [];
        }
        $parts = preg_split('/\s*[|,]\s*/', trim($raw)) ?: [];
        $values = [];
        foreach ($parts as $part) {
            $token = trim((string) $part);
            if ($token === '' || ! is_numeric($token)) {
                continue;
            }
            $value = (float) $token;
            if ($value > 0) {
                $values[] = $value;
            }
        }
        if (! $values) {
            return [];
        }
        $values = array_slice($values, 0, $count);
        while (count($values) < $count) {
            $values[] = 1.0;
        }
        $total = array_sum($values);
        if ($total <= 0) {
            return [];
        }
        $normalized = [];
        foreach ($values as $value) {
            $normalized[] = ($value / $total) * 100;
        }
        return $normalized;
    }

    private function percentValue(float $value): string
    {
        if (! is_finite($value) || $value <= 0) {
            return '';
        }
        $clamped = max(0.01, min(100, $value));
        $formatted = rtrim(rtrim(number_format($clamped, 2, '.', ''), '0'), '.');
        return $formatted === '' ? '' : ($formatted . '%');
    }

    private function fractionValue(float $value): string
    {
        if (! is_finite($value) || $value <= 0) {
            return '';
        }
        $formatted = rtrim(rtrim(number_format($value, 4, '.', ''), '0'), '.');
        return $formatted === '' ? '' : ($formatted . 'fr');
    }

    /**
     * @param array<int, float> $columnWidths
     */
    private function gridTrackTemplate(array $columnWidths, int $columns): string
    {
        if ($columnWidths && count($columnWidths) === max(1, min(self::MAX_COLUMNS, $columns))) {
            $tracks = [];
            foreach ($columnWidths as $columnWidth) {
                $value = $this->fractionValue((float) $columnWidth);
                if ($value !== '') {
                    $tracks[] = 'minmax(0,' . $value . ')';
                }
            }
            if (count($tracks) === count($columnWidths)) {
                return implode(' ', $tracks);
            }
        }
        return 'repeat(' . (string) max(1, min(self::MAX_COLUMNS, $columns)) . ',minmax(0,1fr))';
    }

    /**
     * @param array<int, float> $columnWidths
     * @param array<int, float> $tabletColumnWidths
     * @param array<int, float> $mobileColumnWidths
     */
    private function sectionColumnStyle(
        string $layoutMode,
        array $columnWidths,
        int $slot,
        array $tabletColumnWidths = [],
        array $mobileColumnWidths = []
    ): string
    {
        $styles = [];
        foreach (
            [
                ['values' => $columnWidths, 'variable' => '--h4bs-column-width'],
                ['values' => $tabletColumnWidths, 'variable' => '--h4bs-column-width-tablet'],
                ['values' => $mobileColumnWidths, 'variable' => '--h4bs-column-width-mobile'],
            ] as $entry
        ) {
            $values = is_array($entry['values']) ? $entry['values'] : [];
            if (! $values) {
                continue;
            }
            $index = max(0, min(count($values) - 1, $slot));
            $width = isset($values[$index]) ? $this->percentValue((float) $values[$index]) : '';
            if ($width !== '') {
                $styles[] = (string) $entry['variable'] . ':' . $width;
            }
        }
        if ($layoutMode === 'flex' && $columnWidths) {
            $styles[] = 'flex:0 0 var(--h4bs-column-width,auto)';
            $styles[] = 'max-width:var(--h4bs-column-width,100%)';
        }
        return implode(';', $styles);
    }

    /**
     * @param array<int, string> $allowed
     */
    private function enumToken(string $raw, array $allowed, string $fallback): string
    {
        $candidate = sanitize_key(str_replace('-', '_', strtolower(trim($raw))));
        $normalizedAllowed = array_map(
            static fn (string $item): string => sanitize_key(str_replace('-', '_', strtolower(trim($item)))),
            $allowed
        );
        $index = array_search($candidate, $normalizedAllowed, true);
        if ($index !== false && isset($allowed[(int) $index])) {
            return $allowed[(int) $index];
        }
        return $fallback;
    }

    private function viewportClassForBlock(array $block): string
    {
        $visibility = is_array($block['visibility'] ?? null) ? $block['visibility'] : [];
        $rule = sanitize_key((string) ($visibility['viewport'] ?? 'all'));
        return match ($rule) {
            'monitor_only' => 'h4bs-vp-only-monitor',
            'tablet_only' => 'h4bs-vp-only-tablet',
            'mobile_only' => 'h4bs-vp-only-mobile',
            'tv_only' => 'h4bs-vp-only-tv',
            'hide_monitor' => 'h4bs-vp-hide-monitor',
            'hide_tablet' => 'h4bs-vp-hide-tablet',
            'hide_mobile' => 'h4bs-vp-hide-mobile',
            'hide_tv' => 'h4bs-vp-hide-tv',
            default => '',
        };
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function buttonWrapperDesignSettings(array $settings): array
    {
        $keys = [
            'design_margin_unit',
            'design_margin_top',
            'design_margin_right',
            'design_margin_bottom',
            'design_margin_left',
            'design_text_align',
            'design_align_self',
        ];

        return array_intersect_key($settings, array_fill_keys($keys, true));
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function buttonElementDesignSettings(array $settings): array
    {
        foreach (
            [
                'design_margin_unit',
                'design_margin_top',
                'design_margin_right',
                'design_margin_bottom',
                'design_margin_left',
                'design_align_self',
            ] as $key
        ) {
            unset($settings[$key]);
        }

        return $settings;
    }

    /**
     * @param array<int, array<string, mixed>> $layout
     */
    private function buildLayoutDesignVariantStyles(array $layout): string
    {
        $tabletRules = [];
        $mobileRules = [];
        $hoverRules = [];
        $tabletHoverRules = [];
        $mobileHoverRules = [];

        foreach ($layout as $block) {
            if (! is_array($block)) {
                continue;
            }
            $id = sanitize_html_class((string) ($block['id'] ?? ''));
            if ($id === '') {
                continue;
            }
            $type = $this->normalizeBlockType((string) ($block['type'] ?? 'text'));
            $settings = is_array($block['settings'] ?? null) ? $block['settings'] : [];
            $this->appendDesignVariantRules($tabletRules, $id, $type, $settings, 'tablet', false);
            $this->appendDesignVariantRules($mobileRules, $id, $type, $settings, 'mobile', false);
            $this->appendDesignVariantRules($hoverRules, $id, $type, $settings, 'hover', true);
            $this->appendDesignVariantRules($tabletHoverRules, $id, $type, $settings, 'tablet_hover', true);
            $this->appendDesignVariantRules($mobileHoverRules, $id, $type, $settings, 'mobile_hover', true);
        }

        $css = '';
        if ($tabletRules) {
            $css .= '@media (max-width:1024px){' . implode('', $tabletRules) . '}';
        }
        if ($mobileRules) {
            $css .= '@media (max-width:767px){' . implode('', $mobileRules) . '}';
        }
        if ($hoverRules) {
            $css .= implode('', $hoverRules);
        }
        if ($tabletHoverRules) {
            $css .= '@media (max-width:1024px){' . implode('', $tabletHoverRules) . '}';
        }
        if ($mobileHoverRules) {
            $css .= '@media (max-width:767px){' . implode('', $mobileHoverRules) . '}';
        }
        if ($css === '') {
            return '';
        }

        return '<style class="h4bs-design-variant-style">' . $css . '</style>';
    }

    /**
     * @param array<int, string> $rules
     * @param array<string, mixed> $settings
     */
    private function appendDesignVariantRules(
        array &$rules,
        string $id,
        string $type,
        array $settings,
        string $variant,
        bool $hover
    ): void {
        $variantSettings = $this->designSettingsForVariant($settings, $variant);
        if (! $variantSettings) {
            return;
        }

        $selector = '#' . $id;
        if ($type === 'button') {
            if (! $hover) {
                $wrapperStyle = $this->importantDesignStyle(
                    $this->buttonWrapperDesignSettings($variantSettings)
                );
                if ($wrapperStyle !== '') {
                    $rules[] = $selector . '{' . $wrapperStyle . '}';
                }
            }
            $buttonStyle = $this->importantDesignStyle(
                $this->buttonElementDesignSettings($variantSettings)
            );
            if ($buttonStyle !== '') {
                $rules[] = $selector . '>.h4bs-builder-button' . ($hover ? ':hover' : '') . '{' . $buttonStyle . '}';
            }
            return;
        }

        $style = $this->importantDesignStyle($variantSettings);
        if ($style !== '') {
            $rules[] = $selector . ($hover ? ':hover' : '') . '{' . $style . '}';
        }
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function renderBackgroundVideo(array $settings): string
    {
        if (! rest_sanitize_boolean($settings['background_video_enabled'] ?? false)) {
            return '';
        }
        $mediaValue = static function (array $source, string $urlKey, string $mediaKey): string {
            $url = trim((string) ($source[$urlKey] ?? ''));
            return $url !== '' ? $url : trim((string) ($source[$mediaKey] ?? ''));
        };

        return VideoPlayer::renderBackground([
            'hls' => (string) ($settings['background_video_hls'] ?? ''),
            'primary' => $mediaValue($settings, 'background_video_src', 'background_video_media_src'),
            'fallback_video' => $mediaValue($settings, 'background_video_fallback_src', 'background_video_fallback_media_src'),
            'poster' => $mediaValue($settings, 'background_video_poster', 'background_video_poster_media_src'),
            'fallback_image' => $mediaValue($settings, 'background_video_failure_image', 'background_video_failure_image_media_src'),
            'autoplay' => rest_sanitize_boolean($settings['background_video_autoplay'] ?? true),
            'muted' => rest_sanitize_boolean($settings['background_video_muted'] ?? true),
            'loop' => rest_sanitize_boolean($settings['background_video_loop'] ?? true),
            'controls' => rest_sanitize_boolean($settings['background_video_controls'] ?? false),
            'preload' => (string) ($settings['background_video_preload'] ?? 'metadata'),
            'fit' => (string) ($settings['background_video_fit'] ?? 'cover'),
            'position' => (string) ($settings['background_video_position'] ?? 'center center'),
            'position_tablet' => (string) ($settings['background_video_position_tablet'] ?? ''),
            'position_mobile' => (string) ($settings['background_video_position_mobile'] ?? ''),
            'overflow' => (string) ($settings['background_video_overflow'] ?? 'hidden'),
            'overlay_color' => (string) ($settings['background_video_overlay_color'] ?? '#000000'),
            'overlay_gradient' => (string) ($settings['background_video_overlay_gradient'] ?? ''),
            'overlay_opacity' => (string) ($settings['background_video_overlay_opacity'] ?? '0.35'),
            'decorative' => rest_sanitize_boolean($settings['background_video_decorative'] ?? true),
            'title' => (string) ($settings['background_video_title'] ?? __('Background video', 'help4-builder-suite')),
        ]);
    }

    /**
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    private function designSettingsForVariant(array $settings, string $variant): array
    {
        $suffix = '_' . sanitize_key($variant);
        $output = [];
        foreach ($settings as $key => $value) {
            $fieldKey = sanitize_key((string) $key);
            if (! str_starts_with($fieldKey, 'design_') || ! str_ends_with($fieldKey, $suffix)) {
                continue;
            }
            $baseKey = substr($fieldKey, 0, -strlen($suffix));
            if ($baseKey === '' || preg_match('/_(tablet|mobile|hover)$/', $baseKey) === 1) {
                continue;
            }
            if ($value === null || trim((string) $value) === '') {
                continue;
            }
            $output[$baseKey] = $value;
        }
        return $output;
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function importantDesignStyle(array $settings): string
    {
        $style = $this->buildDesignStyle($settings);
        if ($style === '') {
            return '';
        }
        $declarations = [];
        foreach (explode(';', $style) as $declaration) {
            $declaration = trim($declaration);
            if ($declaration !== '') {
                $declarations[] = $declaration . '!important';
            }
        }
        return implode(';', $declarations);
    }

    /**
     * @param array<string, mixed> $settings
     * @return array<int, string>
     */
    private function globalStylePresetDeclarations(array $settings): array
    {
        $preset = $this->enumToken(
            (string) ($settings['design_style_preset'] ?? ''),
            ['button_primary', 'button_secondary', 'button_outline', 'button_ghost', 'card', 'card_elevated', 'card_soft', 'callout'],
            ''
        );
        if ($preset === '') {
            return [];
        }

        $tokens = $this->globalDesignTokens();
        $colors = is_array($tokens['colors'] ?? null) ? $tokens['colors'] : [];
        $shape = is_array($tokens['shape'] ?? null) ? $tokens['shape'] : [];
        $components = is_array($tokens['components'] ?? null) ? $tokens['components'] : [];
        $color = static function (array $source, string $key, string $fallback): string {
            $candidate = sanitize_hex_color((string) ($source[$key] ?? ''));
            return is_string($candidate) && $candidate !== '' ? $candidate : $fallback;
        };
        $dimension = function (array $source, string $key, string $fallback): string {
            return $this->cssDimension((string) ($source[$key] ?? $fallback), 'px', $fallback . 'px');
        };

        $primary = $color($colors, 'primary', '#0f6cbf');
        $secondary = $color($colors, 'secondary', '#0a9b7a');
        $onPrimary = $color($colors, 'on_primary', '#ffffff');
        $onSecondary = $color($colors, 'on_secondary', '#ffffff');
        $text = $color($colors, 'text', '#1f2937');
        $surface = $color($colors, 'surface', '#ffffff');
        $surfaceAlt = $color($colors, 'surface_alt', '#f4f7fb');
        $border = $color($colors, 'border', '#d8e2ec');
        $accent = $color($colors, 'accent', '#ff7a33');
        $buttonPaddingX = $dimension($components, 'button_padding_x', '20');
        $buttonPaddingY = $dimension($components, 'button_padding_y', '12');
        $buttonRadius = $dimension($components, 'button_radius', '8');
        $buttonWeight = max(100, min(900, (int) ($components['button_weight'] ?? 700)));
        $cardPadding = $dimension($components, 'card_padding', '28');
        $cardRadius = $dimension($shape, 'radius_md', '8');
        $shadowSmall = $this->cssToken((string) ($shape['shadow_sm'] ?? '0 2px 8px rgba(21,34,56,.08)'));
        $shadowMedium = $this->cssToken((string) ($shape['shadow_md'] ?? '0 16px 40px rgba(21,34,56,.14)'));

        if ($preset === 'button_primary' || $preset === 'button_secondary') {
            return [
                'background-color:' . ($preset === 'button_primary' ? $primary : $secondary),
                'color:' . ($preset === 'button_primary' ? $onPrimary : $onSecondary),
                'padding:' . $buttonPaddingY . ' ' . $buttonPaddingX,
                'border:1px solid transparent',
                'border-radius:' . $buttonRadius,
                'font-weight:' . $buttonWeight,
                'transition:background-color .16s ease,color .16s ease,border-color .16s ease,transform .16s ease',
            ];
        }
        if ($preset === 'button_outline' || $preset === 'button_ghost') {
            return [
                'background-color:' . ($preset === 'button_ghost' ? $surfaceAlt : 'transparent'),
                'color:' . $primary,
                'padding:' . $buttonPaddingY . ' ' . $buttonPaddingX,
                'border:' . ($preset === 'button_outline' ? '2px' : '1px') . ' solid ' . ($preset === 'button_outline' ? $primary : $border),
                'border-radius:' . $buttonRadius,
                'font-weight:' . $buttonWeight,
                'transition:background-color .16s ease,color .16s ease,border-color .16s ease,transform .16s ease',
            ];
        }
        if ($preset === 'callout') {
            return [
                'background-color:' . $surfaceAlt,
                'color:' . $text,
                'padding:' . $cardPadding,
                'border-left:4px solid ' . $accent,
                'border-radius:' . $cardRadius,
            ];
        }

        return [
            'background-color:' . ($preset === 'card_soft' ? $surfaceAlt : $surface),
            'color:' . $text,
            'padding:' . $cardPadding,
            'border:1px solid ' . $border,
            'border-radius:' . $cardRadius,
            'box-shadow:' . ($preset === 'card_elevated' ? $shadowMedium : ($preset === 'card' ? $shadowSmall : 'none')),
        ];
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function buildDesignStyle(array $settings): string
    {
        $styles = $this->globalStylePresetDeclarations($settings);
        $globalTokens = $this->globalDesignTokens();
        $globalColors = is_array($globalTokens['colors'] ?? null) ? $globalTokens['colors'] : [];
        $globalFonts = is_array($globalTokens['fonts'] ?? null) ? $globalTokens['fonts'] : [];

        $textColorKey = sanitize_key((string) ($settings['design_text_color_global'] ?? ''));
        $textColorRaw = $textColorKey !== '' && isset($globalColors[$textColorKey])
            ? (string) $globalColors[$textColorKey]
            : (string) ($settings['design_text_color'] ?? '');
        $textColor = $this->cssToken($textColorRaw);
        if ($textColor !== '') {
            $styles[] = 'color:' . $textColor;
        }

        $bgColorKey = sanitize_key((string) ($settings['design_background_color_global'] ?? ''));
        $bgColorRaw = $bgColorKey !== '' && isset($globalColors[$bgColorKey])
            ? (string) $globalColors[$bgColorKey]
            : (string) ($settings['design_background_color'] ?? '');
        $bgColor = $this->cssToken($bgColorRaw);
        if ($bgColor !== '') {
            $styles[] = 'background-color:' . $bgColor;
        }

        $bgGradient = $this->cssToken((string) ($settings['design_background_gradient'] ?? ''));
        $overlayLayer = $this->overlayColorLayer(
            (string) ($settings['design_overlay_color'] ?? ''),
            (string) ($settings['design_overlay_opacity'] ?? '0')
        );
        $bgImage = esc_url_raw((string) ($settings['design_background_image'] ?? ''));

        $layers = [];
        if ($bgGradient !== '') {
            $layers[] = $bgGradient;
        }
        if ($overlayLayer !== '') {
            $layers[] = $overlayLayer;
        }
        if ($bgImage !== '') {
            $layers[] = "url('" . str_replace("'", '%27', $bgImage) . "')";
        }
        if ($layers) {
            $styles[] = 'background-image:' . implode(',', $layers);
        }

        $hasVisualBackground = count($layers) > 0;
        $bgSize = $this->cssToken((string) ($settings['design_background_size'] ?? ''));
        if ($bgSize === '' && $hasVisualBackground) {
            $bgSize = 'cover';
        }
        if ($bgSize !== '') {
            $styles[] = 'background-size:' . $this->expandBackgroundValue($bgSize, count($layers));
        }
        $bgPosition = $this->cssToken((string) ($settings['design_background_position'] ?? ''));
        if ($bgPosition === '' && $hasVisualBackground) {
            $bgPosition = 'center center';
        }
        if ($bgPosition !== '') {
            $styles[] = 'background-position:' . $this->expandBackgroundValue($bgPosition, count($layers));
        }
        $bgRepeat = $this->normalizeBackgroundRepeatValue((string) ($settings['design_background_repeat'] ?? ''), 1);
        if ($bgRepeat === '' && $hasVisualBackground) {
            $bgRepeat = $this->normalizeBackgroundRepeatValue('no-repeat', count($layers));
        }
        if ($bgRepeat !== '') {
            $styles[] = 'background-repeat:' . $this->normalizeBackgroundRepeatValue($bgRepeat, count($layers));
        }

        $paddingUnit = $this->cssUnit((string) ($settings['design_padding_unit'] ?? 'px'), 'px');
        $paddingTop = $this->cssDimension((string) ($settings['design_padding_top'] ?? ''), $paddingUnit);
        $paddingRight = $this->cssDimension((string) ($settings['design_padding_right'] ?? ''), $paddingUnit);
        $paddingBottom = $this->cssDimension((string) ($settings['design_padding_bottom'] ?? ''), $paddingUnit);
        $paddingLeft = $this->cssDimension((string) ($settings['design_padding_left'] ?? ''), $paddingUnit);
        if ($paddingTop !== '' || $paddingRight !== '' || $paddingBottom !== '' || $paddingLeft !== '') {
            $styles[] = 'padding:' . ($paddingTop !== '' ? $paddingTop : '0px')
                . ' ' . ($paddingRight !== '' ? $paddingRight : '0px')
                . ' ' . ($paddingBottom !== '' ? $paddingBottom : '0px')
                . ' ' . ($paddingLeft !== '' ? $paddingLeft : '0px');
        }

        $marginUnit = $this->cssUnit((string) ($settings['design_margin_unit'] ?? 'px'), 'px');
        $marginTop = $this->cssDimension((string) ($settings['design_margin_top'] ?? ''), $marginUnit);
        $marginRight = $this->cssDimension((string) ($settings['design_margin_right'] ?? ''), $marginUnit);
        $marginBottom = $this->cssDimension((string) ($settings['design_margin_bottom'] ?? ''), $marginUnit);
        $marginLeft = $this->cssDimension((string) ($settings['design_margin_left'] ?? ''), $marginUnit);
        if ($marginTop !== '' || $marginRight !== '' || $marginBottom !== '' || $marginLeft !== '') {
            $styles[] = 'margin:' . ($marginTop !== '' ? $marginTop : '0px')
                . ' ' . ($marginRight !== '' ? $marginRight : '0px')
                . ' ' . ($marginBottom !== '' ? $marginBottom : '0px')
                . ' ' . ($marginLeft !== '' ? $marginLeft : '0px');
        }

        $borderWidthUnit = $this->cssUnit((string) ($settings['design_border_width_unit'] ?? 'px'), 'px');
        $borderWidth = $this->cssDimension((string) ($settings['design_border_width'] ?? ''), $borderWidthUnit);
        if ($borderWidth !== '') {
            $borderStyle = $this->cssToken((string) ($settings['design_border_style'] ?? 'solid'));
            if ($borderStyle === '') {
                $borderStyle = 'solid';
            }
            $borderColor = $this->cssToken((string) ($settings['design_border_color'] ?? '#dbe7f3'));
            if ($borderColor === '') {
                $borderColor = '#dbe7f3';
            }
            $styles[] = 'border:' . $borderWidth . ' ' . $borderStyle . ' ' . $borderColor;
        }

        $borderRadiusUnit = $this->cssUnit((string) ($settings['design_border_radius_unit'] ?? 'px'), 'px');
        $borderRadius = $this->cssDimension((string) ($settings['design_border_radius'] ?? ''), $borderRadiusUnit);
        if ($borderRadius !== '') {
            $styles[] = 'border-radius:' . $borderRadius;
        }

        $boxShadow = $this->cssToken((string) ($settings['design_box_shadow'] ?? ''));
        if ($boxShadow !== '') {
            $styles[] = 'box-shadow:' . $boxShadow;
        }

        $textShadow = $this->cssToken((string) ($settings['design_text_shadow'] ?? ''));
        if ($textShadow !== '') {
            $styles[] = 'text-shadow:' . $textShadow;
        }

        $opacity = $this->cssToken((string) ($settings['design_opacity'] ?? ''));
        if ($opacity !== '') {
            $styles[] = 'opacity:' . $opacity;
        }

        $filter = $this->cssToken((string) ($settings['design_filter'] ?? ''));
        if ($filter !== '') {
            $styles[] = 'filter:' . $filter;
        }

        $backdrop = $this->cssToken((string) ($settings['design_backdrop_filter'] ?? ''));
        if ($backdrop !== '') {
            $styles[] = 'backdrop-filter:' . $backdrop;
        }

        $transform = $this->cssToken((string) ($settings['design_transform'] ?? ''));
        if ($transform !== '') {
            $styles[] = 'transform:' . $transform;
        }

        $transformOrigin = $this->cssToken((string) ($settings['design_transform_origin'] ?? ''));
        if ($transformOrigin !== '') {
            $styles[] = 'transform-origin:' . $transformOrigin;
        }

        $transition = $this->cssToken((string) ($settings['design_transition'] ?? ''));
        if ($transition !== '') {
            $styles[] = 'transition:' . $transition;
        }

        $motionDuration = max(80, min(60000, (int) ($settings['design_motion_duration'] ?? 600)));
        $motionDelay = max(0, min(60000, (int) ($settings['design_motion_delay'] ?? 0)));
        $motionEasing = $this->enumToken(
            (string) ($settings['design_motion_easing'] ?? 'ease-out'),
            ['ease', 'linear', 'ease-in', 'ease-out', 'ease-in-out'],
            'ease-out'
        );
        $motionIteration = $this->enumToken(
            (string) ($settings['design_motion_iteration'] ?? '1'),
            ['1', '2', '3', 'infinite'],
            '1'
        );
        if (
            trim((string) ($settings['design_motion_entrance'] ?? '')) !== ''
            || trim((string) ($settings['design_motion_background'] ?? '')) !== ''
            || trim((string) ($settings['design_motion_hover'] ?? '')) !== ''
        ) {
            $styles[] = '--h4bs-motion-duration:' . (string) $motionDuration . 'ms';
            $styles[] = '--h4bs-motion-delay:' . (string) $motionDelay . 'ms';
            $styles[] = '--h4bs-motion-easing:' . $motionEasing;
            $styles[] = '--h4bs-motion-iteration:' . $motionIteration;
        }

        $cursor = $this->cssToken((string) ($settings['design_cursor'] ?? ''));
        if ($cursor !== '') {
            $styles[] = 'cursor:' . $cursor;
        }

        $textAlign = $this->cssToken((string) ($settings['design_text_align'] ?? ''));
        if ($textAlign !== '') {
            $styles[] = 'text-align:' . $textAlign;
        }

        $fontKey = sanitize_key((string) ($settings['design_font_family_global'] ?? ''));
        $fontFamilyRaw = $fontKey !== '' && isset($globalFonts[$fontKey])
            ? (string) $globalFonts[$fontKey]
            : (string) ($settings['design_font_family'] ?? '');
        $fontFamily = $this->cssToken($fontFamilyRaw);
        if ($fontFamily !== '') {
            $styles[] = "font-family:'" . str_replace("'", '', $fontFamily) . "', sans-serif";
        }

        $fontSizeUnit = $this->cssUnit((string) ($settings['design_font_size_unit'] ?? 'px'), 'px');
        $fontSize = $this->cssDimension((string) ($settings['design_font_size'] ?? ''), $fontSizeUnit);
        if ($fontSize !== '') {
            $styles[] = '--h4bs-font-size:' . $fontSize;
            $styles[] = 'font-size:' . $fontSize;
        }

        $fontWeight = $this->cssToken((string) ($settings['design_font_weight'] ?? ''));
        if ($fontWeight !== '') {
            $styles[] = 'font-weight:' . $fontWeight;
        }

        $lineHeightUnit = $this->cssUnit((string) ($settings['design_line_height_unit'] ?? ''), '');
        $lineHeight = $this->cssDimension((string) ($settings['design_line_height'] ?? ''), $lineHeightUnit, '');
        if ($lineHeight !== '') {
            $styles[] = 'line-height:' . $lineHeight;
        }

        $letterSpacingUnit = $this->cssUnit((string) ($settings['design_letter_spacing_unit'] ?? 'px'), 'px');
        $letterSpacing = $this->cssDimension((string) ($settings['design_letter_spacing'] ?? ''), $letterSpacingUnit);
        if ($letterSpacing !== '') {
            $styles[] = 'letter-spacing:' . $letterSpacing;
        }
        $textTransform = $this->cssToken((string) ($settings['design_text_transform'] ?? ''));
        if ($textTransform !== '') {
            $styles[] = 'text-transform:' . $textTransform;
        }

        $widthUnit = $this->cssUnit((string) ($settings['design_width_unit'] ?? '%'), '%');
        $width = $this->cssDimension((string) ($settings['design_width'] ?? ''), $widthUnit);
        if ($width !== '') {
            $styles[] = 'width:' . $width;
        }

        $maxWidthUnit = $this->cssUnit((string) ($settings['design_max_width_unit'] ?? 'px'), 'px');
        $maxWidth = $this->cssDimension((string) ($settings['design_max_width'] ?? ''), $maxWidthUnit);
        if ($maxWidth !== '') {
            $styles[] = 'max-width:' . $maxWidth;
        }

        $minHeightUnit = $this->cssUnit((string) ($settings['design_min_height_unit'] ?? 'px'), 'px');
        $minHeight = $this->cssDimension((string) ($settings['design_min_height'] ?? ''), $minHeightUnit);
        if ($minHeight !== '') {
            $styles[] = 'min-height:' . $minHeight;
        }

        $heightUnit = $this->cssUnit((string) ($settings['design_height_unit'] ?? 'px'), 'px');
        $height = $this->cssDimension((string) ($settings['design_height'] ?? ''), $heightUnit);
        if ($height !== '') {
            $styles[] = 'height:' . $height;
        }

        $maxHeightUnit = $this->cssUnit((string) ($settings['design_max_height_unit'] ?? 'px'), 'px');
        $maxHeight = $this->cssDimension((string) ($settings['design_max_height'] ?? ''), $maxHeightUnit);
        if ($maxHeight !== '') {
            $styles[] = 'max-height:' . $maxHeight;
        }

        $display = $this->cssToken((string) ($settings['design_display'] ?? ''));
        if ($display !== '') {
            $styles[] = 'display:' . $display;
        }

        $position = $this->cssToken((string) ($settings['design_position'] ?? ''));
        if ($position !== '') {
            $styles[] = 'position:' . $position;
        }

        $zIndex = $this->cssToken((string) ($settings['design_z_index'] ?? ''));
        if ($zIndex !== '') {
            $styles[] = 'z-index:' . $zIndex;
        }

        $overflow = $this->cssToken((string) ($settings['design_overflow'] ?? ''));
        if ($overflow !== '') {
            $styles[] = 'overflow:' . $overflow;
        }

        $topUnit = $this->cssUnit((string) ($settings['design_top_unit'] ?? 'px'), 'px');
        $top = $this->cssDimension((string) ($settings['design_top'] ?? ''), $topUnit);
        if ($top !== '') {
            $styles[] = 'top:' . $top;
        }

        $rightUnit = $this->cssUnit((string) ($settings['design_right_unit'] ?? 'px'), 'px');
        $right = $this->cssDimension((string) ($settings['design_right'] ?? ''), $rightUnit);
        if ($right !== '') {
            $styles[] = 'right:' . $right;
        }

        $bottomUnit = $this->cssUnit((string) ($settings['design_bottom_unit'] ?? 'px'), 'px');
        $bottom = $this->cssDimension((string) ($settings['design_bottom'] ?? ''), $bottomUnit);
        if ($bottom !== '') {
            $styles[] = 'bottom:' . $bottom;
        }

        $leftUnit = $this->cssUnit((string) ($settings['design_left_unit'] ?? 'px'), 'px');
        $left = $this->cssDimension((string) ($settings['design_left'] ?? ''), $leftUnit);
        if ($left !== '') {
            $styles[] = 'left:' . $left;
        }

        $flexDirection = $this->cssToken((string) ($settings['design_flex_direction'] ?? ''));
        if ($flexDirection !== '') {
            $styles[] = 'flex-direction:' . $flexDirection;
        }

        $justifyContent = $this->cssToken((string) ($settings['design_justify_content'] ?? ''));
        if ($justifyContent !== '') {
            $styles[] = 'justify-content:' . $justifyContent;
        }

        $alignItems = $this->cssToken((string) ($settings['design_align_items'] ?? ''));
        if ($alignItems !== '') {
            $styles[] = 'align-items:' . $alignItems;
        }

        $alignSelf = $this->cssToken((string) ($settings['design_align_self'] ?? ''));
        if ($alignSelf !== '') {
            $styles[] = 'align-self:' . $alignSelf;
        }

        return implode(';', $styles);
    }

    private function cssToken(string $raw): string
    {
        $raw = trim($raw);
        if ($raw === '') {
            return '';
        }
        $raw = preg_replace('/[{}<>;]/', '', $raw) ?: '';
        return trim($raw);
    }

    private function cssLength(string $raw): string
    {
        $raw = trim($raw);
        if ($raw === '') {
            return '';
        }
        if (preg_match('/^-?\d+(\.\d+)?$/', $raw)) {
            return $raw . 'px';
        }
        if (preg_match('/^-?\d+(\.\d+)?(px|rem|em|%|vh|vw|vmin|vmax|ch|ex|pt|pc)$/', $raw)) {
            return $raw;
        }
        return '';
    }

    private function cssUnit(string $raw, string $fallback = 'px'): string
    {
        $unit = strtolower(trim($raw));
        $allowed = [
            '',
            'px',
            'rem',
            'em',
            '%',
            'vh',
            'vw',
            'vmin',
            'vmax',
            'ch',
            'ex',
            'pt',
            'pc',
        ];
        if (in_array($unit, $allowed, true)) {
            return $unit;
        }
        $fallbackUnit = strtolower(trim($fallback));
        if (in_array($fallbackUnit, $allowed, true)) {
            return $fallbackUnit;
        }
        return 'px';
    }

    private function cssDimension(string $raw, string $unit, string $fallback = ''): string
    {
        $value = trim($raw);
        if ($value === '') {
            return $fallback;
        }

        if (preg_match('/^-?\d+(\.\d+)?$/', $value)) {
            $safeUnit = $this->cssUnit($unit, 'px');
            return $safeUnit === '' ? $value : $value . $safeUnit;
        }

        $normalized = $this->cssLength($value);
        return $normalized !== '' ? $normalized : $fallback;
    }

    private function expandBackgroundValue(string $raw, int $layersCount): string
    {
        $value = trim($raw);
        if ($value === '' || $layersCount <= 1) {
            return $value;
        }

        $parts = array_values(
            array_filter(
                array_map(
                    static fn (string $part): string => trim($part),
                    explode(',', $value)
                ),
                static fn (string $part): bool => $part !== ''
            )
        );

        if (! $parts) {
            return $value;
        }

        if (count($parts) >= $layersCount) {
            return implode(',', array_slice($parts, 0, $layersCount));
        }

        $expanded = [];
        for ($i = 0; $i < $layersCount; $i++) {
            $expanded[] = $parts[min($i, count($parts) - 1)];
        }

        return implode(',', $expanded);
    }

    private function normalizeBackgroundRepeatKeyword(string $raw): string
    {
        $value = strtolower(trim(preg_replace('/\s+/', ' ', $raw) ?: ''));
        if ($value === '') {
            return '';
        }
        if ($value === 'none' || $value === 'no repeat') {
            return 'no-repeat';
        }
        $single = ['repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space', 'round'];
        if (in_array($value, $single, true)) {
            return $value;
        }
        $parts = preg_split('/\s+/', $value) ?: [];
        $pair = ['repeat', 'no-repeat', 'space', 'round'];
        if (
            count($parts) === 2
            && in_array($parts[0], $pair, true)
            && in_array($parts[1], $pair, true)
        ) {
            return $parts[0] . ' ' . $parts[1];
        }
        return '';
    }

    private function normalizeBackgroundRepeatValue(string $raw, int $layersCount): string
    {
        $value = trim($raw);
        if ($value === '') {
            return '';
        }
        $parts = array_values(
            array_filter(
                array_map(
                    fn (string $part): string => $this->normalizeBackgroundRepeatKeyword($part),
                    explode(',', $value)
                ),
                static fn (string $part): bool => $part !== ''
            )
        );
        if (! $parts) {
            return '';
        }
        if ($layersCount <= 1) {
            return $parts[0];
        }
        if (count($parts) >= $layersCount) {
            return implode(',', array_slice($parts, 0, $layersCount));
        }
        $expanded = [];
        for ($i = 0; $i < $layersCount; $i++) {
            $expanded[] = $parts[min($i, count($parts) - 1)];
        }
        return implode(',', $expanded);
    }

    private function overlayColorLayer(string $rawColor, string $rawOpacity): string
    {
        $color = trim($rawColor);
        if (! preg_match('/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $color)) {
            return '';
        }
        $opacity = (float) $rawOpacity;
        $opacity = max(0, min(1, $opacity));
        if ($opacity <= 0) {
            return '';
        }

        $rgb = $this->hexToRgb($color);
        if (! $rgb) {
            return '';
        }

        $alpha = rtrim(rtrim(number_format($opacity, 3, '.', ''), '0'), '.');
        if ($alpha === '') {
            $alpha = '0';
        }

        return 'linear-gradient(rgba(' . $rgb['r'] . ',' . $rgb['g'] . ',' . $rgb['b'] . ',' . $alpha . '),rgba(' . $rgb['r'] . ',' . $rgb['g'] . ',' . $rgb['b'] . ',' . $alpha . '))';
    }

    private function youtubeEmbedUrl(string $raw, bool $autoplay, bool $controls, bool $muted, bool $loop): string
    {
        $videoId = $this->youtubeVideoId($raw);
        if ($videoId === '') {
            return '';
        }

        $params = [
            'autoplay' => $autoplay ? '1' : '0',
            'controls' => $controls ? '1' : '0',
            'mute' => $muted ? '1' : '0',
            'loop' => $loop ? '1' : '0',
            'playsinline' => '1',
            'rel' => '0',
        ];
        if ($loop) {
            $params['playlist'] = $videoId;
        }

        return 'https://www.youtube.com/embed/' . rawurlencode($videoId) . '?' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
    }

    private function vimeoEmbedUrl(string $raw, bool $autoplay, bool $controls, bool $muted, bool $loop): string
    {
        $videoId = $this->vimeoVideoId($raw);
        if ($videoId === '') {
            return '';
        }

        $params = [
            'autoplay' => $autoplay ? '1' : '0',
            'muted' => $muted ? '1' : '0',
            'loop' => $loop ? '1' : '0',
            'title' => '0',
            'byline' => '0',
            'portrait' => '0',
        ];
        if (! $controls) {
            $params['controls'] = '0';
        }

        return 'https://player.vimeo.com/video/' . rawurlencode($videoId) . '?' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
    }

    private function youtubeVideoId(string $raw): string
    {
        $value = trim($raw);
        if ($value === '') {
            return '';
        }

        $patterns = [
            '/[?&]v=([A-Za-z0-9_-]{6,})/i',
            '#youtu\.be/([A-Za-z0-9_-]{6,})#i',
            '#youtube\.com/embed/([A-Za-z0-9_-]{6,})#i',
        ];
        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $value, $matches) && ! empty($matches[1])) {
                return sanitize_text_field((string) $matches[1]);
            }
        }

        if (preg_match('/^[A-Za-z0-9_-]{6,}$/', $value)) {
            return sanitize_text_field($value);
        }

        return '';
    }

    private function vimeoVideoId(string $raw): string
    {
        $value = trim($raw);
        if ($value === '') {
            return '';
        }

        if (preg_match('#vimeo\.com/(?:video/)?(\d{5,})#i', $value, $matches) && ! empty($matches[1])) {
            return sanitize_text_field((string) $matches[1]);
        }
        if (preg_match('/^\d{5,}$/', $value)) {
            return sanitize_text_field($value);
        }
        return '';
    }

    /**
     * @return array{r:int,g:int,b:int}|null
     */
    private function hexToRgb(string $hex): ?array
    {
        $hex = ltrim(trim($hex), '#');
        if (! preg_match('/^[0-9a-fA-F]{3}$|^[0-9a-fA-F]{6}$/', $hex)) {
            return null;
        }

        if (strlen($hex) === 3) {
            $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
        }

        return [
            'r' => (int) hexdec(substr($hex, 0, 2)),
            'g' => (int) hexdec(substr($hex, 2, 2)),
            'b' => (int) hexdec(substr($hex, 4, 2)),
        ];
    }

    /**
     * @param array<int, array<string, mixed>> $layout
     */
    private function enqueueTypographyAssets(array $layout, bool $admin): void
    {
        $fonts = $this->googleFontsForLayout($layout);
        if ($fonts) {
            wp_enqueue_style(
                'h4bs-google-fonts',
                $this->googleFontsUrl($fonts),
                [],
                H4BS_VERSION
            );
        }

        if ($this->layoutUsesFontAwesome($layout)) {
            wp_enqueue_style(
                $admin ? 'h4bs-fontawesome-free' : 'h4bs-fontawesome-free-frontend',
                H4BS_URL . 'assets/vendor/fontawesome/css/help4-fa-local.css',
                [],
                H4BS_VERSION
            );
        }

        if ($admin && ! wp_style_is('dashicons', 'enqueued')) {
            wp_enqueue_style('dashicons');
        }
    }

    /**
     * @param array<int, array<string, mixed>> $layout
     * @return array<int, string>
     */
    private function googleFontsForLayout(array $layout): array
    {
        $allowed = $this->googleFontCatalog();
        $globalTokens = $this->globalDesignTokens();
        $globalFonts = is_array($globalTokens['fonts'] ?? null) ? $globalTokens['fonts'] : [];
        $fonts = [];
        foreach ($layout as $block) {
            if (! is_array($block)) {
                continue;
            }
            $settings = is_array($block['settings'] ?? null) ? $block['settings'] : [];
            $fontKey = sanitize_key((string) ($settings['design_font_family_global'] ?? ''));
            $font = $fontKey !== '' && isset($globalFonts[$fontKey])
                ? (string) $globalFonts[$fontKey]
                : (string) ($settings['design_font_family'] ?? '');
            if ($font !== '' && isset($allowed[$font])) {
                $fonts[] = $font;
            }
        }

        return array_values(array_unique($fonts));
    }

    /**
     * @return array<string, bool>
     */
    private function googleFontCatalog(): array
    {
        return [
            'Roboto' => true,
            'Open Sans' => true,
            'Lato' => true,
            'Montserrat' => true,
            'Poppins' => true,
            'Inter' => true,
            'Nunito' => true,
            'Oswald' => true,
            'Merriweather' => true,
            'Playfair Display' => true,
            'Raleway' => true,
            'Rubik' => true,
            'Source Sans 3' => true,
            'Work Sans' => true,
            'DM Sans' => true,
        ];
    }

    /**
     * @param array<int, string> $fonts
     */
    private function googleFontsUrl(array $fonts): string
    {
        return '';
    }

    /**
     * @param array<int, array<string, mixed>> $layout
     */
    private function layoutUsesFontAwesome(array $layout): bool
    {
        foreach ($layout as $block) {
            if (! is_array($block)) {
                continue;
            }
            if ((string) ($block['type'] ?? '') !== 'icon') {
                continue;
            }
            $settings = is_array($block['settings'] ?? null) ? $block['settings'] : [];
            if (sanitize_key((string) ($settings['icon_set'] ?? 'dashicons')) === 'fontawesome') {
                return true;
            }
        }
        return false;
    }

    /**
     * @param array<int, array<int, array<string, mixed>>> $layouts
     */
    private function layoutsUseDashicons(array $layouts): bool
    {
        foreach ($layouts as $layout) {
            if (! is_array($layout)) {
                continue;
            }
            foreach ($layout as $block) {
                if (! is_array($block) || (string) ($block['type'] ?? '') !== 'icon') {
                    continue;
                }
                $settings = is_array($block['settings'] ?? null) ? $block['settings'] : [];
                if (sanitize_key((string) ($settings['icon_set'] ?? 'dashicons')) !== 'fontawesome') {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * @param mixed $raw
     * @return array<string, mixed>
     */
    private function sanitizeVisibilityRules($raw): array
    {
        $raw = is_array($raw) ? $raw : [];

        $scope = sanitize_key((string) ($raw['scope'] ?? 'all'));
        $allowedScopes = [
            'all',
            'front_page',
            'home',
            'singular',
            'archive',
            'search',
            '404',
            'product_archive',
            'product_single',
        ];
        if (! in_array($scope, $allowedScopes, true)) {
            $scope = 'all';
        }

        $viewport = sanitize_key((string) ($raw['viewport'] ?? 'all'));
        $allowedViewports = [
            'all',
            'monitor_only',
            'tablet_only',
            'mobile_only',
            'tv_only',
            'hide_monitor',
            'hide_tablet',
            'hide_mobile',
            'hide_tv',
        ];
        if (! in_array($viewport, $allowedViewports, true)) {
            $viewport = 'all';
        }

        $includeSource = $raw['include_ids'] ?? '';
        if (is_array($includeSource)) {
            $includeSource = implode(',', array_map('strval', $includeSource));
        }
        $excludeSource = $raw['exclude_ids'] ?? '';
        if (is_array($excludeSource)) {
            $excludeSource = implode(',', array_map('strval', $excludeSource));
        }

        $includeIds = $this->parseIdList((string) $includeSource);
        $excludeIds = $this->parseIdList((string) $excludeSource);

        $rolesRaw = $raw['user_roles'] ?? [];
        if (is_string($rolesRaw)) {
            $rolesRaw = preg_split('/[\s,]+/', $rolesRaw) ?: [];
        }
        $roles = [];
        if (is_array($rolesRaw)) {
            foreach ($rolesRaw as $role) {
                $clean = sanitize_key((string) $role);
                if ($clean !== '') {
                    $roles[] = $clean;
                }
            }
        }

        return [
            'scope'       => $scope,
            'include_ids' => $includeIds,
            'exclude_ids' => $excludeIds,
            'user_roles'  => array_values(array_unique($roles)),
            'viewport'    => $viewport,
        ];
    }

    /**
     * @param array<string, mixed> $block
     */
    private function blockVisibleForCurrentRequest(array $block): bool
    {
        $visibility = $block['visibility'] ?? [];
        if (! is_array($visibility)) {
            return true;
        }

        $scope = sanitize_key((string) ($visibility['scope'] ?? 'all'));
        $queriedId = $this->previewContextActive
            ? ($this->previewRepresentativePostId > 0 ? $this->previewRepresentativePostId : $this->previewContextPostId)
            : get_queried_object_id();

        if ($this->previewContextActive) {
            $scopeMatches = $this->previewScopeMatches(
                $scope,
                $queriedId,
                $this->previewRepresentativePostType !== ''
                    ? $this->previewRepresentativePostType
                    : $this->previewContextPostType
            );
        } else {
            $scopeMatches = match ($scope) {
                'all' => true,
                'front_page' => is_front_page(),
                'home' => is_home(),
                'singular' => is_singular(),
                'archive' => is_archive(),
                'search' => is_search(),
                '404' => is_404(),
                'product_archive' => is_post_type_archive('product')
                    || (function_exists('is_shop') && is_shop()),
                'product_single' => is_singular('product'),
                default => true,
            };
        }

        if (! $scopeMatches) {
            return false;
        }

        $includeValue = $visibility['include_ids'] ?? [];
        if (is_array($includeValue)) {
            $includeIds = array_values(array_filter(array_map('absint', $includeValue)));
        } else {
            $includeIds = $this->parseIdList((string) $includeValue);
        }
        if ($includeIds && $queriedId > 0 && ! in_array($queriedId, $includeIds, true)) {
            return false;
        }

        $excludeValue = $visibility['exclude_ids'] ?? [];
        if (is_array($excludeValue)) {
            $excludeIds = array_values(array_filter(array_map('absint', $excludeValue)));
        } else {
            $excludeIds = $this->parseIdList((string) $excludeValue);
        }
        if ($excludeIds && $queriedId > 0 && in_array($queriedId, $excludeIds, true)) {
            return false;
        }

        $requiredRoles = $visibility['user_roles'] ?? [];
        if (is_string($requiredRoles)) {
            $requiredRoles = preg_split('/[\s,]+/', trim($requiredRoles)) ?: [];
        }
        $requiredRoles = (array) $requiredRoles;
        $requiredRoles = array_values(array_filter(array_map('sanitize_key', $requiredRoles)));
        if ($requiredRoles) {
            if (! is_user_logged_in()) {
                return in_array('guest', $requiredRoles, true);
            }

            $user = wp_get_current_user();
            if (! $user || ! $user->exists()) {
                return false;
            }

            $userRoles = array_map('sanitize_key', (array) $user->roles);
            if (! array_intersect($requiredRoles, $userRoles) && ! in_array('logged_in', $requiredRoles, true)) {
                return false;
            }
        }

        return true;
    }

    private function enablePreviewContext(int $postId, string $postType): void
    {
        $this->previewContextActive = true;
        $this->previewContextPostId = absint($postId);
        $this->previewContextPostType = sanitize_key($postType);
        $this->previewRepresentativePostId = 0;
        $this->previewRepresentativePostType = '';
        if ($this->previewContextPostId > 0 && $this->previewContextPostType === '') {
            $resolvedPostType = get_post_type($this->previewContextPostId);
            if (is_string($resolvedPostType)) {
                $this->previewContextPostType = sanitize_key($resolvedPostType);
            }
        }
        if ($this->previewContextPostType === 'h4_template') {
            $representative = $this->representativePostForTemplate($this->previewContextPostId);
            if ($representative instanceof WP_Post) {
                $this->previewRepresentativePostId = (int) $representative->ID;
                $this->previewRepresentativePostType = sanitize_key((string) $representative->post_type);
            }
        }
    }

    private function disablePreviewContext(): void
    {
        $this->previewContextActive = false;
        $this->previewContextPostId = 0;
        $this->previewContextPostType = '';
        $this->previewRepresentativePostId = 0;
        $this->previewRepresentativePostType = '';
    }

    private function previewRenderPost(): ?WP_Post
    {
        $postId = $this->previewRepresentativePostId > 0
            ? $this->previewRepresentativePostId
            : $this->previewContextPostId;
        if ($postId <= 0) {
            return null;
        }
        $post = get_post($postId);
        return $post instanceof WP_Post ? $post : null;
    }

    private function activateTemplatePreviewQueryContext(): void
    {
        if (
            ! $this->previewContextActive
            || $this->previewContextPostType !== 'h4_template'
            || $this->previewContextPostId <= 0
        ) {
            return;
        }

        $template = get_post($this->previewContextPostId);
        if (! $template instanceof WP_Post || $template->post_type !== 'h4_template') {
            return;
        }

        $representative = $this->previewRepresentativePostId > 0
            ? get_post($this->previewRepresentativePostId)
            : null;
        if ($representative instanceof WP_Post && $representative->post_type === 'h4_template') {
            $representative = null;
        }

        $query = new \WP_Query();
        $query->posts = [];
        $query->post_count = 0;
        $query->found_posts = 0;
        $query->max_num_pages = 0;
        $query->current_post = -1;
        $query->in_the_loop = false;
        $query->is_home = false;
        $query->is_page = false;
        $query->is_single = false;
        $query->is_attachment = false;
        $query->is_singular = false;
        $query->is_archive = false;
        $query->is_post_type_archive = false;
        $query->is_search = false;
        $query->is_404 = false;

        if (! $representative instanceof WP_Post) {
            $role = sanitize_key((string) get_post_meta($template->ID, '_h4_template_role', true));
            $scope = sanitize_key((string) get_post_meta($template->ID, '_h4_template_scope', true));
            $semantics = array_values(array_unique(array_filter([$role, $scope])));
            $includeTypes = $this->parseSlugList((string) get_post_meta($template->ID, '_h4_template_include_types', true));
            $archivePostType = $includeTypes[0] ?? '';
            if (in_array('404', $semantics, true)) {
                $query->is_404 = true;
            } elseif (in_array('search', $semantics, true)) {
                $query->query = ['s' => ''];
                $query->query_vars = $query->query;
                $query->is_search = true;
            } elseif (in_array('product_archive', $semantics, true)) {
                $archivePostType = $archivePostType !== '' ? $archivePostType : 'product';
                $query->query = ['post_type' => $archivePostType];
                $query->query_vars = $query->query;
                $query->is_archive = true;
                $archiveObject = get_post_type_object($archivePostType);
                $query->is_post_type_archive = $archiveObject instanceof \WP_Post_Type;
                $query->queried_object = $query->is_post_type_archive ? $archiveObject : null;
            } elseif (in_array('archive', $semantics, true)) {
                $query->query = $archivePostType !== '' ? ['post_type' => $archivePostType] : [];
                $query->query_vars = $query->query;
                $query->is_archive = true;
                $archiveObject = $archivePostType !== '' ? get_post_type_object($archivePostType) : null;
                $query->is_post_type_archive = $archiveObject instanceof \WP_Post_Type;
                $query->queried_object = $query->is_post_type_archive ? $archiveObject : null;
            } elseif (in_array('home', $semantics, true)) {
                $query->is_home = true;
            } elseif (in_array('front_page', $semantics, true) && get_option('show_on_front') === 'posts') {
                $query->is_home = true;
            }

            $GLOBALS['wp_query'] = $query;
            $GLOBALS['wp_the_query'] = $query;
            return;
        }

        $query->query = [
            'p' => (int) $representative->ID,
            'post_type' => (string) $representative->post_type,
        ];
        $query->query_vars = $query->query;
        $query->queried_object = $representative;
        $query->queried_object_id = (int) $representative->ID;
        $query->post = $representative;
        $query->posts = [$representative];
        $query->post_count = 1;
        $query->found_posts = 1;
        $query->max_num_pages = 1;
        $query->is_singular = true;
        $query->is_page = $representative->post_type === 'page';
        $query->is_attachment = $representative->post_type === 'attachment';
        $query->is_single = ! $query->is_page && ! $query->is_attachment;

        $GLOBALS['wp_query'] = $query;
        $GLOBALS['wp_the_query'] = $query;
        $GLOBALS['post'] = $representative;
        setup_postdata($representative);
    }

    private function representativePostForTemplate(int $templateId): ?WP_Post
    {
        $templateId = absint($templateId);
        if ($templateId <= 0 || get_post_type($templateId) !== 'h4_template') {
            return null;
        }

        $role = sanitize_key((string) get_post_meta($templateId, '_h4_template_role', true));
        $scope = sanitize_key((string) get_post_meta($templateId, '_h4_template_scope', true));
        $semantics = array_values(array_unique(array_filter([$role, $scope])));
        if (array_intersect($semantics, ['archive', 'search', '404', 'product_archive'])) {
            return null;
        }

        if (in_array('front_page', $semantics, true)) {
            $frontPageId = absint(get_option('page_on_front'));
            $frontPage = $frontPageId > 0 ? get_post($frontPageId) : null;
            if ($frontPage instanceof WP_Post) {
                return $frontPage;
            }
        }
        if (in_array('home', $semantics, true)) {
            $postsPageId = absint(get_option('page_for_posts'));
            $postsPage = $postsPageId > 0 ? get_post($postsPageId) : null;
            if ($postsPage instanceof WP_Post) {
                return $postsPage;
            }
        }

        $includeIds = $this->parseIdList((string) get_post_meta($templateId, '_h4_template_include_ids', true));
        foreach ($includeIds as $includeId) {
            $candidate = get_post($includeId);
            if ($candidate instanceof WP_Post && $candidate->post_type !== 'h4_template') {
                return $candidate;
            }
        }

        $postTypes = $this->parseSlugList((string) get_post_meta($templateId, '_h4_template_include_types', true));
        if (! $postTypes) {
            if (in_array($role, ['page', 'canvas_page'], true) || in_array($scope, ['page', 'canvas_page'], true)) {
                $postTypes[] = 'page';
            } elseif ($role === 'post' || $scope === 'post') {
                $postTypes[] = 'post';
            } elseif ($role === 'product_single' || $scope === 'product_single') {
                $postTypes = ['product', 'h4_product'];
            }
        }

        $postTypes = array_values(
            array_filter(
                array_unique(array_map('sanitize_key', $postTypes)),
                static fn (string $postType): bool => $postType !== '' && $postType !== 'h4_template'
            )
        );
        if (! $postTypes) {
            return null;
        }

        $candidateIds = get_posts(
            [
                'post_type' => $postTypes,
                'post_status' => ['publish', 'private'],
                'posts_per_page' => 1,
                'orderby' => 'modified',
                'order' => 'DESC',
                'fields' => 'ids',
                'no_found_rows' => true,
                'suppress_filters' => false,
            ]
        );
        $candidateId = isset($candidateIds[0]) ? absint($candidateIds[0]) : 0;
        $candidate = $candidateId > 0 ? get_post($candidateId) : null;
        return $candidate instanceof WP_Post ? $candidate : null;
    }

    private function previewScopeMatches(string $scope, int $postId, string $postType): bool
    {
        $postId = absint($postId);
        $postType = sanitize_key($postType);

        if ($this->previewContextPostType === 'h4_template' && $this->previewContextPostId > 0) {
            $role = sanitize_key((string) get_post_meta($this->previewContextPostId, '_h4_template_role', true));
            $templateScope = sanitize_key((string) get_post_meta($this->previewContextPostId, '_h4_template_scope', true));
            $semantic = array_values(array_unique(array_filter([$role, $templateScope])));
            if ($scope === 'front_page' || $scope === 'home' || $scope === 'archive' || $scope === 'search' || $scope === '404') {
                return in_array($scope, $semantic, true);
            }
            if ($scope === 'product_archive') {
                return in_array('product_archive', $semantic, true);
            }
        }

        return match ($scope) {
            'all' => true,
            'front_page' => $postId > 0 && absint(get_option('page_on_front')) === $postId,
            'home' => $postId > 0 && absint(get_option('page_for_posts')) === $postId,
            'singular' => $postId > 0,
            'archive' => false,
            'search' => false,
            '404' => false,
            'product_archive' => false,
            'product_single' => in_array($postType, ['product', 'h4_product'], true),
            default => true,
        };
    }

    /**
     * @return array<int, int>
     */
    private function parseIdList(string $raw): array
    {
        $parts = preg_split('/[\s,]+/', trim($raw)) ?: [];
        $ids = [];
        foreach ($parts as $part) {
            $id = absint($part);
            if ($id > 0) {
                $ids[] = $id;
            }
        }

        return array_values(array_unique($ids));
    }

    /**
     * @return array<int, string>
     */
    private function parseSlugList(string $raw): array
    {
        $parts = preg_split('/[\s,]+/', trim($raw)) ?: [];
        $slugs = [];
        foreach ($parts as $part) {
            $slug = sanitize_key((string) $part);
            if ($slug !== '') {
                $slugs[] = $slug;
            }
        }
        return array_values(array_unique($slugs));
    }

    private function sanitizeHtmlBlockContent(string $raw): string
    {
        $raw = trim($raw);
        if ($raw === '') {
            return '';
        }

        $raw = (string) preg_replace(
            '~<(script|style|template|noscript)\b[^>]*>.*?</\1\s*>~is',
            '',
            $raw
        );
        $raw = (string) preg_replace(
            '~<(script|style|template|noscript)\b[^>]*>.*$~is',
            '',
            $raw
        );

        $supportsResponsiveImages = class_exists('WP_HTML_Tag_Processor');
        if ($supportsResponsiveImages) {
            $raw = $this->sanitizeHtmlBlockImageAttributes($raw);
        }

        $allowed = wp_kses_allowed_html('post');
        if (! is_array($allowed)) {
            $allowed = [];
        }

        if ($supportsResponsiveImages) {
            $allowed['img'] = is_array($allowed['img'] ?? null) ? $allowed['img'] : [];
            $allowed['img']['srcset'] = true;
            $allowed['img']['sizes'] = true;
            $allowed['img']['decoding'] = [
                'values' => ['async', 'sync', 'auto'],
            ];
        }

        $allowed['iframe'] = [
            'src' => true,
            'title' => true,
            'width' => true,
            'height' => true,
            'loading' => true,
            'allow' => true,
            'allowfullscreen' => true,
            'frameborder' => true,
            'referrerpolicy' => true,
            'sandbox' => true,
            'class' => true,
            'id' => true,
            'style' => true,
        ];
        $allowed['video'] = $allowed['video'] ?? [
            'src' => true,
            'controls' => true,
            'autoplay' => true,
            'muted' => true,
            'loop' => true,
            'playsinline' => true,
            'poster' => true,
            'preload' => true,
            'width' => true,
            'height' => true,
            'class' => true,
            'style' => true,
        ];
        $allowed['source'] = $allowed['source'] ?? [
            'src' => true,
            'type' => true,
            'media' => true,
        ];

        return wp_kses($raw, $allowed);
    }

    private function sanitizeHtmlBlockImageAttributes(string $html): string
    {
        $processor = new \WP_HTML_Tag_Processor($html);
        while ($processor->next_tag(['tag_name' => 'IMG'])) {
            $srcset = $processor->get_attribute('srcset');
            if (is_string($srcset)) {
                $cleanSrcset = $this->sanitizeHtmlBlockSrcset($srcset);
                if ($cleanSrcset === '') {
                    $processor->remove_attribute('srcset');
                } else {
                    $processor->set_attribute('srcset', $cleanSrcset);
                }
            }

            $sizes = $processor->get_attribute('sizes');
            if (is_string($sizes)) {
                $cleanSizes = $this->sanitizeHtmlBlockSizes($sizes);
                if ($cleanSizes === '') {
                    $processor->remove_attribute('sizes');
                } else {
                    $processor->set_attribute('sizes', $cleanSizes);
                }
            }

            $decoding = $processor->get_attribute('decoding');
            if (is_string($decoding)) {
                $cleanDecoding = strtolower(trim($decoding));
                if (! in_array($cleanDecoding, ['async', 'sync', 'auto'], true)) {
                    $processor->remove_attribute('decoding');
                } else {
                    $processor->set_attribute('decoding', $cleanDecoding);
                }
            }
        }

        return $processor->get_updated_html();
    }

    private function sanitizeHtmlBlockSrcset(string $raw): string
    {
        $raw = trim(wp_kses_no_null(html_entity_decode($raw, ENT_QUOTES | ENT_HTML5, 'UTF-8')));
        if (
            $raw === ''
            || strlen($raw) > 8192
            || preg_match('/(?:javascript|vbscript|data|file)\s*:/i', $raw) === 1
        ) {
            return '';
        }

        $candidates = preg_split('/\s*,\s*/', $raw) ?: [];
        $clean = [];
        foreach (array_slice($candidates, 0, 100) as $candidate) {
            $candidate = trim((string) $candidate);
            if (
                $candidate === ''
                || preg_match('/^(\S+?)(?:\s+((?:[1-9]\d*)w|(?:(?:[1-9]\d*)(?:\.\d+)?|0\.\d*[1-9]\d*)x))?$/i', $candidate, $matches) !== 1
            ) {
                continue;
            }

            $url = (string) ($matches[1] ?? '');
            if ($url === '' || wp_kses_bad_protocol($url, wp_allowed_protocols()) !== $url) {
                continue;
            }
            $url = esc_url_raw($url, wp_allowed_protocols());
            if ($url === '') {
                continue;
            }

            $descriptor = strtolower((string) ($matches[2] ?? ''));
            $clean[] = $descriptor === '' ? $url : $url . ' ' . $descriptor;
        }

        return implode(', ', array_values(array_unique($clean)));
    }

    private function sanitizeHtmlBlockSizes(string $raw): string
    {
        $sizes = trim(wp_kses_no_null(html_entity_decode($raw, ENT_QUOTES | ENT_HTML5, 'UTF-8')));
        if ($sizes === '' || strlen($sizes) > 2048) {
            return '';
        }
        if (
            preg_match('/(?:url|expression)\s*\(|@|[;{}\\\\`"\']/i', $sizes) === 1
            || preg_match('/^[a-z0-9\s.,%()\/:+*<>=-]+$/i', $sizes) !== 1
        ) {
            return '';
        }

        return trim((string) preg_replace('/\s+/', ' ', $sizes));
    }

}
