self::VERSION, 'tokens' => [ 'primary' => (string) ($design['primary'] ?? '#2563eb'), 'primary_dark' => (string) ($design['primary_dark'] ?? '#1d4ed8'), 'accent' => (string) ($design['accent'] ?? '#10b981'), 'ink' => (string) ($design['ink'] ?? '#111827'), 'muted' => (string) ($design['muted'] ?? '#4b5563'), 'border' => (string) ($design['border'] ?? '#e5e7eb'), 'surface' => (string) ($design['surface'] ?? '#ffffff'), 'soft' => (string) ($design['soft'] ?? '#f8fafc'), 'soft_blue' => (string) ($design['soft_blue'] ?? '#eff6ff'), 'radius' => (string) ($design['radius'] ?? '22px'), 'button_radius' => (string) ($design['button_radius'] ?? '999px'), 'shadow' => (string) ($design['shadow'] ?? '0 18px 50px rgba(15,23,42,.08)'), 'max_width' => (string) ($design['max_width'] ?? '1180px'), ], 'layout' => [ 'template' => sanitize_key((string) ($design['layout_template'] ?? 'standard_landing')), 'section_count' => (string) ($design['section_count'] ?? '4'), 'card_count' => (string) ($design['card_count'] ?? '3'), 'faq_policy' => sanitize_key((string) ($design['faq_policy'] ?? 'optional')), 'cta_policy' => sanitize_key((string) ($design['cta_policy'] ?? 'always')), 'related_policy' => sanitize_key((string) ($design['related_policy'] ?? 'optional')), 'mode' => $layout_mode, ], 'media' => [ 'source' => sanitize_key((string) ($design['media_source'] ?? 'ai_or_library')), 'position' => sanitize_key((string) ($design['media_position'] ?? 'right_column')), 'size' => sanitize_key((string) ($design['media_size'] ?? 'medium')), 'url' => esc_url_raw((string) ($design['media_url'] ?? '')), 'video_url' => esc_url_raw((string) ($design['video_url'] ?? '')), ], 'canvas' => [ 'background_url' => esc_url_raw((string) get_option('aiwest_canvas_bg_url', '')), 'background_opacity' => self::opacity(get_option('aiwest_canvas_bg_opacity', 0.16), 0.16), 'header_background_url' => esc_url_raw((string) get_option('aiwest_header_bg_url', '')), 'header_background_opacity' => self::opacity(get_option('aiwest_header_bg_opacity', 0.18), 0.18), ], 'brand_style' => sanitize_textarea_field($brand_style), 'imported_reference' => sanitize_textarea_field($reference), ]; $contract['hash'] = self::fingerprint($contract); return $contract; } public static function planner_context($contract = null) { $contract = is_array($contract) ? $contract : self::resolve(); $layout = $contract['layout'] ?? []; $media = $contract['media'] ?? []; return "SITE-WIDE DESIGN CONTRACT (mandatory):\n" . "- Use the global WordPress design tokens exposed as --aiwest-* CSS variables. Never invent a page-specific palette.\n" . "- Layout template: " . ($layout['template'] ?? 'standard_landing') . ".\n" . "- Layout mode: " . ($layout['mode'] ?? 'ai_independent') . ".\n" . "- Target sections: " . ($layout['section_count'] ?? '4') . "; cards per card section: " . ($layout['card_count'] ?? '3') . ".\n" . "- FAQ: " . ($layout['faq_policy'] ?? 'optional') . "; CTA: " . ($layout['cta_policy'] ?? 'always') . "; related content: " . ($layout['related_policy'] ?? 'optional') . ".\n" . "- Media: " . ($media['source'] ?? 'ai_or_library') . ", " . ($media['position'] ?? 'right_column') . ", " . ($media['size'] ?? 'medium') . ".\n" . "- Brand direction: " . (($contract['brand_style'] ?? '') ?: 'No additional brand direction.') . "\n" . "- Imported design reference: " . (($contract['imported_reference'] ?? '') ?: 'No imported design reference.') . "\n" . "- Contract fingerprint: " . ($contract['hash'] ?? '') . "."; } public static function default_profile($contract = null) { $contract = is_array($contract) ? $contract : self::resolve(); $template = sanitize_key((string) ($contract['layout']['template'] ?? 'standard_landing')); $map = [ 'standard_landing' => 'landing', 'guide' => 'documentation', 'comparison' => 'cards', 'directory' => 'cards', 'product' => 'landing', 'minimal' => 'editorial', ]; return $map[$template] ?? 'editorial'; } public static function fingerprint($contract) { if (!is_array($contract)) return ''; unset($contract['hash']); $json = wp_json_encode($contract, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); return substr(hash('sha256', (string) $json), 0, 16); } protected static function opacity($value, $fallback) { return is_numeric($value) ? max(0, min(1, (float) $value)) : (float) $fallback; } protected static function normalize_legacy_reference($reference) { if ($reference === '') return ''; return strtr($reference, [ 'Zaimportowany wzorzec projektu.' => 'Imported project design reference.', 'Data importu:' => 'Import date:', 'Pliki:' => 'Files:', 'Komponenty:' => 'Components:', 'Strony/widoki:' => 'Pages/views:', 'Pliki danych:' => 'Data files:', 'Wykryte ścieżki/menu/linki:' => 'Detected routes/menu/links:', 'Ważne pliki stron:' => 'Important page files:', 'Ważne komponenty:' => 'Important components:', 'Pliki danych/treści:' => 'Data/content files:', 'Fragmenty treści i klas:' => 'Content and class excerpts:', ]); } protected static function normalize_legacy_brand($brand_style) { $legacy = [ 'Nowoczesny, jasny, ekspercki portal budowlany: hero, karty, kroki, FAQ, CTA, dużo przestrzeni, profesjonalny ton.', 'Nowoczesny, jasny, ekspercki serwis: hero, karty, kroki, FAQ, CTA, dużo przestrzeni, profesjonalny ton.', ]; return in_array($brand_style, $legacy, true) ? 'Modern, clear, expert website: hero, cards, steps, FAQ, CTA, generous spacing and professional tone.' : $brand_style; } protected static function fallback_design() { return [ 'primary' => '#2563eb', 'primary_dark' => '#1d4ed8', 'accent' => '#10b981', 'ink' => '#111827', 'muted' => '#4b5563', 'border' => '#e5e7eb', 'surface' => '#ffffff', 'soft' => '#f8fafc', 'soft_blue' => '#eff6ff', 'radius' => '22px', 'button_radius' => '999px', 'shadow' => '0 18px 50px rgba(15,23,42,.08)', 'max_width' => '1180px', 'layout_template' => 'standard_landing', 'section_count' => '4', 'card_count' => '3', 'faq_policy' => 'optional', 'cta_policy' => 'always', 'related_policy' => 'optional', 'media_source' => 'ai_or_library', 'media_position' => 'right_column', 'media_size' => 'medium', ]; } }