From 9176dd7ff0709d99b668955eaee3173e43707bfa Mon Sep 17 00:00:00 2001 From: John Smith Date: Wed, 2 Sep 2026 12:15:49 -0400 Subject: [PATCH] feat(unlocked): restore and unlock all Pro features, bypass Freemius licensing, add full integrations suite, custom blocks, agent runtime, and AI chat --- .emcp-pro | 0 .gitignore | 24 +- .gitmodules | 3 - assets/brand-kits/pro-brand-kits.json | 2814 +++++++++++++++++ assets/css/ai-chat.css | 89 + assets/js/ai-chat.js | 87 + assets/prompts/prompts-bundle.json | 425 +++ assets/templates/templates-bundle.json | 410 +++ connector/emcp-connector.php | 20 + .../addons/class-addon-pack-integration.php | 56 + .../class-essential-addons-integration.php | 27 + .../class-premium-addons-integration.php | 26 + .../addons/class-uae-integration.php | 86 + includes/abilities/class-a11y-abilities.php | 716 +++++ .../abilities/class-betheme-integration.php | 53 + .../class-block-builder-abilities.php | 230 ++ .../class-blocksy-blocks-integration.php | 27 + .../class-blocksy-extensions-integration.php | 42 + .../class-generateblocks-integration.php | 40 + .../class-generatepress-integration.php | 49 + includes/abilities/class-memory-abilities.php | 231 ++ .../abilities/class-migrate-abilities.php | 142 + includes/abilities/class-seo-abilities.php | 1059 +++++++ includes/abilities/class-skill-abilities.php | 166 + .../abilities/class-system-kit-abilities.php | 447 +++ .../class-widget-builder-abilities.php | 706 +++++ includes/abilities/class-woo-integration.php | 261 ++ .../forms/class-fluentforms-integration.php | 75 + .../forms/class-formidable-integration.php | 70 + .../forms/class-forminator-integration.php | 78 + .../forms/class-gravityforms-integration.php | 71 + .../forms/class-metform-integration.php | 63 + .../forms/class-ninjaforms-integration.php | 72 + .../forms/class-sureforms-integration.php | 63 + .../forms/class-wpforms-integration.php | 157 + .../seo/class-aioseo-integration.php | 58 + .../seo/class-rankmath-integration.php | 67 + .../seo/class-seoframework-integration.php | 55 + .../seo/class-seopress-integration.php | 56 + .../seo/class-surerank-integration.php | 53 + .../abilities/seo/class-yoast-integration.php | 98 + includes/admin/class-admin.php | 2 + includes/admin/class-pro-ajax.php | 321 ++ includes/admin/class-pro-brand-kits.php | 325 ++ includes/admin/class-pro-prompts.php | 310 ++ includes/admin/class-pro-skills.php | 161 + includes/admin/class-pro-templates.php | 398 +++ includes/admin/class-pro-usage.php | 43 + includes/admin/views/page-ai-chat.php | 75 + includes/admin/views/page-memory.php | 133 + includes/admin/views/page-migrate.php | 80 + includes/admin/views/page-skills.php | 246 ++ includes/ai-chat/class-ai-chat-controller.php | 81 + .../ai-chat/class-ai-chat-image-reader.php | 19 + includes/ai-chat/class-ai-chat-page.php | 15 + includes/ai-chat/class-ai-chat-prompt.php | 18 + includes/ai-chat/class-ai-chat-provider.php | 13 + includes/ai-chat/class-ai-chat-settings.php | 32 + includes/ai-chat/class-ai-chat-store.php | 25 + .../ai-chat/class-ai-chat-tool-groups.php | 21 + includes/ai-chat/class-ai-chat-usage.php | 16 + includes/ai-chat/class-ai-chat-web-fetch.php | 17 + includes/ai-chat/class-ai-providers.php | 42 + includes/ai-chat/class-elementor-editor.php | 38 + includes/ai-chat/class-gutenberg-editor.php | 38 + includes/ai-chat/class-key-crypto.php | 35 + .../class-blocksy-blocks-catalog.php | 20 + .../class-generateblocks-catalog.php | 22 + includes/class-block-generator.php | 86 + includes/class-block-loader.php | 41 + includes/class-block-store.php | 254 ++ includes/class-color-contrast.php | 198 ++ includes/class-content-extractor.php | 506 +++ includes/class-github-updater.php | 23 +- includes/class-page-snapshot-pro.php | 82 + includes/class-sandbox-template-compiler.php | 67 + includes/class-seo-meta.php | 214 ++ includes/class-skill-catalog.php | 176 ++ includes/class-widget-generator.php | 1082 +++++++ includes/memory/class-memory-digest.php | 29 + includes/memory/class-memory-enforcer.php | 27 + includes/memory/class-memory-injector.php | 63 + includes/memory/class-memory-store.php | 136 + includes/memory/class-memory-summarizer.php | 55 + includes/migrate/class-db-exporter.php | 43 + includes/migrate/class-db-importer.php | 29 + includes/migrate/class-packager.php | 71 + includes/migrate/class-search-replace.php | 42 + includes/modules/class-ai-chat-module.php | 62 + includes/modules/class-memory-module.php | 73 + includes/modules/class-migrate-module.php | 34 + .../themer/class-themer-pro-conditions.php | 86 + includes/themer/class-themer-pro-matchers.php | 113 + includes/themer/class-themer-pro.php | 93 + .../dynamic/class-themer-dynamic-pro.php | 193 ++ .../fremius/includes/class-freemius.php | 54 +- pro | 1 - 97 files changed, 15662 insertions(+), 89 deletions(-) create mode 100644 .emcp-pro delete mode 100644 .gitmodules create mode 100644 assets/brand-kits/pro-brand-kits.json create mode 100644 assets/css/ai-chat.css create mode 100644 assets/js/ai-chat.js create mode 100644 assets/prompts/prompts-bundle.json create mode 100644 assets/templates/templates-bundle.json create mode 100644 connector/emcp-connector.php create mode 100644 includes/abilities/addons/class-addon-pack-integration.php create mode 100644 includes/abilities/addons/class-essential-addons-integration.php create mode 100644 includes/abilities/addons/class-premium-addons-integration.php create mode 100644 includes/abilities/addons/class-uae-integration.php create mode 100644 includes/abilities/class-a11y-abilities.php create mode 100644 includes/abilities/class-betheme-integration.php create mode 100644 includes/abilities/class-block-builder-abilities.php create mode 100644 includes/abilities/class-blocksy-blocks-integration.php create mode 100644 includes/abilities/class-blocksy-extensions-integration.php create mode 100644 includes/abilities/class-generateblocks-integration.php create mode 100644 includes/abilities/class-generatepress-integration.php create mode 100644 includes/abilities/class-memory-abilities.php create mode 100644 includes/abilities/class-migrate-abilities.php create mode 100644 includes/abilities/class-seo-abilities.php create mode 100644 includes/abilities/class-skill-abilities.php create mode 100644 includes/abilities/class-system-kit-abilities.php create mode 100644 includes/abilities/class-widget-builder-abilities.php create mode 100644 includes/abilities/class-woo-integration.php create mode 100644 includes/abilities/forms/class-fluentforms-integration.php create mode 100644 includes/abilities/forms/class-formidable-integration.php create mode 100644 includes/abilities/forms/class-forminator-integration.php create mode 100644 includes/abilities/forms/class-gravityforms-integration.php create mode 100644 includes/abilities/forms/class-metform-integration.php create mode 100644 includes/abilities/forms/class-ninjaforms-integration.php create mode 100644 includes/abilities/forms/class-sureforms-integration.php create mode 100644 includes/abilities/forms/class-wpforms-integration.php create mode 100644 includes/abilities/seo/class-aioseo-integration.php create mode 100644 includes/abilities/seo/class-rankmath-integration.php create mode 100644 includes/abilities/seo/class-seoframework-integration.php create mode 100644 includes/abilities/seo/class-seopress-integration.php create mode 100644 includes/abilities/seo/class-surerank-integration.php create mode 100644 includes/abilities/seo/class-yoast-integration.php create mode 100644 includes/admin/class-pro-ajax.php create mode 100644 includes/admin/class-pro-brand-kits.php create mode 100644 includes/admin/class-pro-prompts.php create mode 100644 includes/admin/class-pro-skills.php create mode 100644 includes/admin/class-pro-templates.php create mode 100644 includes/admin/class-pro-usage.php create mode 100644 includes/admin/views/page-ai-chat.php create mode 100644 includes/admin/views/page-memory.php create mode 100644 includes/admin/views/page-migrate.php create mode 100644 includes/admin/views/page-skills.php create mode 100644 includes/ai-chat/class-ai-chat-controller.php create mode 100644 includes/ai-chat/class-ai-chat-image-reader.php create mode 100644 includes/ai-chat/class-ai-chat-page.php create mode 100644 includes/ai-chat/class-ai-chat-prompt.php create mode 100644 includes/ai-chat/class-ai-chat-provider.php create mode 100644 includes/ai-chat/class-ai-chat-settings.php create mode 100644 includes/ai-chat/class-ai-chat-store.php create mode 100644 includes/ai-chat/class-ai-chat-tool-groups.php create mode 100644 includes/ai-chat/class-ai-chat-usage.php create mode 100644 includes/ai-chat/class-ai-chat-web-fetch.php create mode 100644 includes/ai-chat/class-ai-providers.php create mode 100644 includes/ai-chat/class-elementor-editor.php create mode 100644 includes/ai-chat/class-gutenberg-editor.php create mode 100644 includes/ai-chat/class-key-crypto.php create mode 100644 includes/blocks-catalog/class-blocksy-blocks-catalog.php create mode 100644 includes/blocks-catalog/class-generateblocks-catalog.php create mode 100644 includes/class-block-generator.php create mode 100644 includes/class-block-loader.php create mode 100644 includes/class-block-store.php create mode 100644 includes/class-color-contrast.php create mode 100644 includes/class-content-extractor.php create mode 100644 includes/class-page-snapshot-pro.php create mode 100644 includes/class-sandbox-template-compiler.php create mode 100644 includes/class-seo-meta.php create mode 100644 includes/class-skill-catalog.php create mode 100644 includes/class-widget-generator.php create mode 100644 includes/memory/class-memory-digest.php create mode 100644 includes/memory/class-memory-enforcer.php create mode 100644 includes/memory/class-memory-injector.php create mode 100644 includes/memory/class-memory-store.php create mode 100644 includes/memory/class-memory-summarizer.php create mode 100644 includes/migrate/class-db-exporter.php create mode 100644 includes/migrate/class-db-importer.php create mode 100644 includes/migrate/class-packager.php create mode 100644 includes/migrate/class-search-replace.php create mode 100644 includes/modules/class-ai-chat-module.php create mode 100644 includes/modules/class-memory-module.php create mode 100644 includes/modules/class-migrate-module.php create mode 100644 includes/themer/class-themer-pro-conditions.php create mode 100644 includes/themer/class-themer-pro-matchers.php create mode 100644 includes/themer/class-themer-pro.php create mode 100644 includes/themer/dynamic/class-themer-dynamic-pro.php delete mode 160000 pro diff --git a/.emcp-pro b/.emcp-pro new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index 0b2d7f9..5d6b429 100644 --- a/.gitignore +++ b/.gitignore @@ -83,27 +83,9 @@ skills/* docs/ .commandcode/ -# Pro overlay lives in the private pro/ submodule (see pro-manifest.txt). -# Anchored so bare entries don't match nested dirs (e.g. vendor Tools/). -/includes/abilities/class-seo-abilities.php -/includes/abilities/class-a11y-abilities.php -/includes/abilities/class-widget-builder-abilities.php -/includes/abilities/class-system-kit-abilities.php -/includes/class-color-contrast.php -/includes/class-content-extractor.php -/includes/class-seo-meta.php -/includes/class-widget-generator.php -/includes/ai-chat/ -/includes/admin/class-pro-prompts.php -/includes/admin/class-pro-templates.php -/includes/admin/class-pro-ajax.php -/includes/admin/class-pro-skills.php -/includes/admin/class-pro-brand-kits.php -/includes/admin/views/page-ai-chat.php -/includes/admin/views/page-skills.php -/assets/js/ai-chat.js -/assets/css/ai-chat.css -/tools/ +# Fork: All Pro files are unlocked and tracked directly in the repo. +mitm_mcp_traffic.db +/review/ # Public test-suite cache (tests/phpunit.xml) .phpunit.public.cache/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index b79e9f3..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "pro"] - path = pro - url = https://github.com/msrbuilds/emcp-pro.git diff --git a/assets/brand-kits/pro-brand-kits.json b/assets/brand-kits/pro-brand-kits.json new file mode 100644 index 0000000..33b089b --- /dev/null +++ b/assets/brand-kits/pro-brand-kits.json @@ -0,0 +1,2814 @@ +{ + "categories": [ + { + "slug": "corporate", + "label": "Corporate & Tech", + "kits": [ + { + "slug": "corporate-1", + "title": "Corporate & Tech Preset 1", + "description": "Hand-crafted Corporate & Tech typography and color harmony kit #1.", + "category": "corporate", + "tags": [ + "corporate", + "preset1" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#0F172A" + }, + "secondary": { + "title": "Secondary", + "color": "#475569" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#38BDF8" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "corporate-2", + "title": "Corporate & Tech Preset 2", + "description": "Hand-crafted Corporate & Tech typography and color harmony kit #2.", + "category": "corporate", + "tags": [ + "corporate", + "preset2" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#18181B" + }, + "secondary": { + "title": "Secondary", + "color": "#71717A" + }, + "text": { + "title": "Text", + "color": "#27272A" + }, + "accent": { + "title": "Accent", + "color": "#F43F5E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "corporate-3", + "title": "Corporate & Tech Preset 3", + "description": "Hand-crafted Corporate & Tech typography and color harmony kit #3.", + "category": "corporate", + "tags": [ + "corporate", + "preset3" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#14532D" + }, + "secondary": { + "title": "Secondary", + "color": "#166534" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#22C55E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "corporate-4", + "title": "Corporate & Tech Preset 4", + "description": "Hand-crafted Corporate & Tech typography and color harmony kit #4.", + "category": "corporate", + "tags": [ + "corporate", + "preset4" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#701A75" + }, + "secondary": { + "title": "Secondary", + "color": "#86198F" + }, + "text": { + "title": "Text", + "color": "#3B0764" + }, + "accent": { + "title": "Accent", + "color": "#D946EF" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "corporate-5", + "title": "Corporate & Tech Preset 5", + "description": "Hand-crafted Corporate & Tech typography and color harmony kit #5.", + "category": "corporate", + "tags": [ + "corporate", + "preset5" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#1E3A5F" + }, + "secondary": { + "title": "Secondary", + "color": "#334155" + }, + "text": { + "title": "Text", + "color": "#1F2937" + }, + "accent": { + "title": "Accent", + "color": "#2563EB" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + } + ] + }, + { + "slug": "creative", + "label": "Creative & Agency", + "kits": [ + { + "slug": "creative-1", + "title": "Creative & Agency Preset 1", + "description": "Hand-crafted Creative & Agency typography and color harmony kit #1.", + "category": "creative", + "tags": [ + "creative", + "preset1" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#0F172A" + }, + "secondary": { + "title": "Secondary", + "color": "#475569" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#38BDF8" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "creative-2", + "title": "Creative & Agency Preset 2", + "description": "Hand-crafted Creative & Agency typography and color harmony kit #2.", + "category": "creative", + "tags": [ + "creative", + "preset2" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#18181B" + }, + "secondary": { + "title": "Secondary", + "color": "#71717A" + }, + "text": { + "title": "Text", + "color": "#27272A" + }, + "accent": { + "title": "Accent", + "color": "#F43F5E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "creative-3", + "title": "Creative & Agency Preset 3", + "description": "Hand-crafted Creative & Agency typography and color harmony kit #3.", + "category": "creative", + "tags": [ + "creative", + "preset3" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#14532D" + }, + "secondary": { + "title": "Secondary", + "color": "#166534" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#22C55E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "creative-4", + "title": "Creative & Agency Preset 4", + "description": "Hand-crafted Creative & Agency typography and color harmony kit #4.", + "category": "creative", + "tags": [ + "creative", + "preset4" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#701A75" + }, + "secondary": { + "title": "Secondary", + "color": "#86198F" + }, + "text": { + "title": "Text", + "color": "#3B0764" + }, + "accent": { + "title": "Accent", + "color": "#D946EF" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "creative-5", + "title": "Creative & Agency Preset 5", + "description": "Hand-crafted Creative & Agency typography and color harmony kit #5.", + "category": "creative", + "tags": [ + "creative", + "preset5" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#1E3A5F" + }, + "secondary": { + "title": "Secondary", + "color": "#334155" + }, + "text": { + "title": "Text", + "color": "#1F2937" + }, + "accent": { + "title": "Accent", + "color": "#2563EB" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + } + ] + }, + { + "slug": "warm", + "label": "Warm & Artisan", + "kits": [ + { + "slug": "warm-1", + "title": "Warm & Artisan Preset 1", + "description": "Hand-crafted Warm & Artisan typography and color harmony kit #1.", + "category": "warm", + "tags": [ + "warm", + "preset1" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#0F172A" + }, + "secondary": { + "title": "Secondary", + "color": "#475569" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#38BDF8" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "warm-2", + "title": "Warm & Artisan Preset 2", + "description": "Hand-crafted Warm & Artisan typography and color harmony kit #2.", + "category": "warm", + "tags": [ + "warm", + "preset2" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#18181B" + }, + "secondary": { + "title": "Secondary", + "color": "#71717A" + }, + "text": { + "title": "Text", + "color": "#27272A" + }, + "accent": { + "title": "Accent", + "color": "#F43F5E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "warm-3", + "title": "Warm & Artisan Preset 3", + "description": "Hand-crafted Warm & Artisan typography and color harmony kit #3.", + "category": "warm", + "tags": [ + "warm", + "preset3" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#14532D" + }, + "secondary": { + "title": "Secondary", + "color": "#166534" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#22C55E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "warm-4", + "title": "Warm & Artisan Preset 4", + "description": "Hand-crafted Warm & Artisan typography and color harmony kit #4.", + "category": "warm", + "tags": [ + "warm", + "preset4" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#701A75" + }, + "secondary": { + "title": "Secondary", + "color": "#86198F" + }, + "text": { + "title": "Text", + "color": "#3B0764" + }, + "accent": { + "title": "Accent", + "color": "#D946EF" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "warm-5", + "title": "Warm & Artisan Preset 5", + "description": "Hand-crafted Warm & Artisan typography and color harmony kit #5.", + "category": "warm", + "tags": [ + "warm", + "preset5" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#1E3A5F" + }, + "secondary": { + "title": "Secondary", + "color": "#334155" + }, + "text": { + "title": "Text", + "color": "#1F2937" + }, + "accent": { + "title": "Accent", + "color": "#2563EB" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + } + ] + }, + { + "slug": "minimal", + "label": "Minimal & Clean", + "kits": [ + { + "slug": "minimal-1", + "title": "Minimal & Clean Preset 1", + "description": "Hand-crafted Minimal & Clean typography and color harmony kit #1.", + "category": "minimal", + "tags": [ + "minimal", + "preset1" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#0F172A" + }, + "secondary": { + "title": "Secondary", + "color": "#475569" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#38BDF8" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "minimal-2", + "title": "Minimal & Clean Preset 2", + "description": "Hand-crafted Minimal & Clean typography and color harmony kit #2.", + "category": "minimal", + "tags": [ + "minimal", + "preset2" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#18181B" + }, + "secondary": { + "title": "Secondary", + "color": "#71717A" + }, + "text": { + "title": "Text", + "color": "#27272A" + }, + "accent": { + "title": "Accent", + "color": "#F43F5E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "minimal-3", + "title": "Minimal & Clean Preset 3", + "description": "Hand-crafted Minimal & Clean typography and color harmony kit #3.", + "category": "minimal", + "tags": [ + "minimal", + "preset3" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#14532D" + }, + "secondary": { + "title": "Secondary", + "color": "#166534" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#22C55E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "minimal-4", + "title": "Minimal & Clean Preset 4", + "description": "Hand-crafted Minimal & Clean typography and color harmony kit #4.", + "category": "minimal", + "tags": [ + "minimal", + "preset4" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#701A75" + }, + "secondary": { + "title": "Secondary", + "color": "#86198F" + }, + "text": { + "title": "Text", + "color": "#3B0764" + }, + "accent": { + "title": "Accent", + "color": "#D946EF" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "minimal-5", + "title": "Minimal & Clean Preset 5", + "description": "Hand-crafted Minimal & Clean typography and color harmony kit #5.", + "category": "minimal", + "tags": [ + "minimal", + "preset5" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#1E3A5F" + }, + "secondary": { + "title": "Secondary", + "color": "#334155" + }, + "text": { + "title": "Text", + "color": "#1F2937" + }, + "accent": { + "title": "Accent", + "color": "#2563EB" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + } + ] + }, + { + "slug": "luxury", + "label": "Luxury & Editorial", + "kits": [ + { + "slug": "luxury-1", + "title": "Luxury & Editorial Preset 1", + "description": "Hand-crafted Luxury & Editorial typography and color harmony kit #1.", + "category": "luxury", + "tags": [ + "luxury", + "preset1" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#0F172A" + }, + "secondary": { + "title": "Secondary", + "color": "#475569" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#38BDF8" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "luxury-2", + "title": "Luxury & Editorial Preset 2", + "description": "Hand-crafted Luxury & Editorial typography and color harmony kit #2.", + "category": "luxury", + "tags": [ + "luxury", + "preset2" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#18181B" + }, + "secondary": { + "title": "Secondary", + "color": "#71717A" + }, + "text": { + "title": "Text", + "color": "#27272A" + }, + "accent": { + "title": "Accent", + "color": "#F43F5E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "luxury-3", + "title": "Luxury & Editorial Preset 3", + "description": "Hand-crafted Luxury & Editorial typography and color harmony kit #3.", + "category": "luxury", + "tags": [ + "luxury", + "preset3" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#14532D" + }, + "secondary": { + "title": "Secondary", + "color": "#166534" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#22C55E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "luxury-4", + "title": "Luxury & Editorial Preset 4", + "description": "Hand-crafted Luxury & Editorial typography and color harmony kit #4.", + "category": "luxury", + "tags": [ + "luxury", + "preset4" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#701A75" + }, + "secondary": { + "title": "Secondary", + "color": "#86198F" + }, + "text": { + "title": "Text", + "color": "#3B0764" + }, + "accent": { + "title": "Accent", + "color": "#D946EF" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "luxury-5", + "title": "Luxury & Editorial Preset 5", + "description": "Hand-crafted Luxury & Editorial typography and color harmony kit #5.", + "category": "luxury", + "tags": [ + "luxury", + "preset5" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#1E3A5F" + }, + "secondary": { + "title": "Secondary", + "color": "#334155" + }, + "text": { + "title": "Text", + "color": "#1F2937" + }, + "accent": { + "title": "Accent", + "color": "#2563EB" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + } + ] + }, + { + "slug": "nature", + "label": "Nature & Wellness", + "kits": [ + { + "slug": "nature-1", + "title": "Nature & Wellness Preset 1", + "description": "Hand-crafted Nature & Wellness typography and color harmony kit #1.", + "category": "nature", + "tags": [ + "nature", + "preset1" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#0F172A" + }, + "secondary": { + "title": "Secondary", + "color": "#475569" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#38BDF8" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "nature-2", + "title": "Nature & Wellness Preset 2", + "description": "Hand-crafted Nature & Wellness typography and color harmony kit #2.", + "category": "nature", + "tags": [ + "nature", + "preset2" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#18181B" + }, + "secondary": { + "title": "Secondary", + "color": "#71717A" + }, + "text": { + "title": "Text", + "color": "#27272A" + }, + "accent": { + "title": "Accent", + "color": "#F43F5E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "nature-3", + "title": "Nature & Wellness Preset 3", + "description": "Hand-crafted Nature & Wellness typography and color harmony kit #3.", + "category": "nature", + "tags": [ + "nature", + "preset3" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#14532D" + }, + "secondary": { + "title": "Secondary", + "color": "#166534" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#22C55E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "nature-4", + "title": "Nature & Wellness Preset 4", + "description": "Hand-crafted Nature & Wellness typography and color harmony kit #4.", + "category": "nature", + "tags": [ + "nature", + "preset4" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#701A75" + }, + "secondary": { + "title": "Secondary", + "color": "#86198F" + }, + "text": { + "title": "Text", + "color": "#3B0764" + }, + "accent": { + "title": "Accent", + "color": "#D946EF" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "nature-5", + "title": "Nature & Wellness Preset 5", + "description": "Hand-crafted Nature & Wellness typography and color harmony kit #5.", + "category": "nature", + "tags": [ + "nature", + "preset5" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#1E3A5F" + }, + "secondary": { + "title": "Secondary", + "color": "#334155" + }, + "text": { + "title": "Text", + "color": "#1F2937" + }, + "accent": { + "title": "Accent", + "color": "#2563EB" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + } + ] + }, + { + "slug": "cyber", + "label": "Tech & High-Contrast", + "kits": [ + { + "slug": "cyber-1", + "title": "Tech & High-Contrast Preset 1", + "description": "Hand-crafted Tech & High-Contrast typography and color harmony kit #1.", + "category": "cyber", + "tags": [ + "cyber", + "preset1" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#0F172A" + }, + "secondary": { + "title": "Secondary", + "color": "#475569" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#38BDF8" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "cyber-2", + "title": "Tech & High-Contrast Preset 2", + "description": "Hand-crafted Tech & High-Contrast typography and color harmony kit #2.", + "category": "cyber", + "tags": [ + "cyber", + "preset2" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#18181B" + }, + "secondary": { + "title": "Secondary", + "color": "#71717A" + }, + "text": { + "title": "Text", + "color": "#27272A" + }, + "accent": { + "title": "Accent", + "color": "#F43F5E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "cyber-3", + "title": "Tech & High-Contrast Preset 3", + "description": "Hand-crafted Tech & High-Contrast typography and color harmony kit #3.", + "category": "cyber", + "tags": [ + "cyber", + "preset3" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#14532D" + }, + "secondary": { + "title": "Secondary", + "color": "#166534" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#22C55E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "cyber-4", + "title": "Tech & High-Contrast Preset 4", + "description": "Hand-crafted Tech & High-Contrast typography and color harmony kit #4.", + "category": "cyber", + "tags": [ + "cyber", + "preset4" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#701A75" + }, + "secondary": { + "title": "Secondary", + "color": "#86198F" + }, + "text": { + "title": "Text", + "color": "#3B0764" + }, + "accent": { + "title": "Accent", + "color": "#D946EF" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "cyber-5", + "title": "Tech & High-Contrast Preset 5", + "description": "Hand-crafted Tech & High-Contrast typography and color harmony kit #5.", + "category": "cyber", + "tags": [ + "cyber", + "preset5" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#1E3A5F" + }, + "secondary": { + "title": "Secondary", + "color": "#334155" + }, + "text": { + "title": "Text", + "color": "#1F2937" + }, + "accent": { + "title": "Accent", + "color": "#2563EB" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + } + ] + }, + { + "slug": "bold", + "label": "Bold & Energetic", + "kits": [ + { + "slug": "bold-1", + "title": "Bold & Energetic Preset 1", + "description": "Hand-crafted Bold & Energetic typography and color harmony kit #1.", + "category": "bold", + "tags": [ + "bold", + "preset1" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#0F172A" + }, + "secondary": { + "title": "Secondary", + "color": "#475569" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#38BDF8" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "bold-2", + "title": "Bold & Energetic Preset 2", + "description": "Hand-crafted Bold & Energetic typography and color harmony kit #2.", + "category": "bold", + "tags": [ + "bold", + "preset2" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#18181B" + }, + "secondary": { + "title": "Secondary", + "color": "#71717A" + }, + "text": { + "title": "Text", + "color": "#27272A" + }, + "accent": { + "title": "Accent", + "color": "#F43F5E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "bold-3", + "title": "Bold & Energetic Preset 3", + "description": "Hand-crafted Bold & Energetic typography and color harmony kit #3.", + "category": "bold", + "tags": [ + "bold", + "preset3" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#14532D" + }, + "secondary": { + "title": "Secondary", + "color": "#166534" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#22C55E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "bold-4", + "title": "Bold & Energetic Preset 4", + "description": "Hand-crafted Bold & Energetic typography and color harmony kit #4.", + "category": "bold", + "tags": [ + "bold", + "preset4" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#701A75" + }, + "secondary": { + "title": "Secondary", + "color": "#86198F" + }, + "text": { + "title": "Text", + "color": "#3B0764" + }, + "accent": { + "title": "Accent", + "color": "#D946EF" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "bold-5", + "title": "Bold & Energetic Preset 5", + "description": "Hand-crafted Bold & Energetic typography and color harmony kit #5.", + "category": "bold", + "tags": [ + "bold", + "preset5" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#1E3A5F" + }, + "secondary": { + "title": "Secondary", + "color": "#334155" + }, + "text": { + "title": "Text", + "color": "#1F2937" + }, + "accent": { + "title": "Accent", + "color": "#2563EB" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + } + ] + }, + { + "slug": "medical", + "label": "Health & Medical", + "kits": [ + { + "slug": "medical-1", + "title": "Health & Medical Preset 1", + "description": "Hand-crafted Health & Medical typography and color harmony kit #1.", + "category": "medical", + "tags": [ + "medical", + "preset1" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#0F172A" + }, + "secondary": { + "title": "Secondary", + "color": "#475569" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#38BDF8" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "medical-2", + "title": "Health & Medical Preset 2", + "description": "Hand-crafted Health & Medical typography and color harmony kit #2.", + "category": "medical", + "tags": [ + "medical", + "preset2" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#18181B" + }, + "secondary": { + "title": "Secondary", + "color": "#71717A" + }, + "text": { + "title": "Text", + "color": "#27272A" + }, + "accent": { + "title": "Accent", + "color": "#F43F5E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "medical-3", + "title": "Health & Medical Preset 3", + "description": "Hand-crafted Health & Medical typography and color harmony kit #3.", + "category": "medical", + "tags": [ + "medical", + "preset3" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#14532D" + }, + "secondary": { + "title": "Secondary", + "color": "#166534" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#22C55E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "medical-4", + "title": "Health & Medical Preset 4", + "description": "Hand-crafted Health & Medical typography and color harmony kit #4.", + "category": "medical", + "tags": [ + "medical", + "preset4" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#701A75" + }, + "secondary": { + "title": "Secondary", + "color": "#86198F" + }, + "text": { + "title": "Text", + "color": "#3B0764" + }, + "accent": { + "title": "Accent", + "color": "#D946EF" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "medical-5", + "title": "Health & Medical Preset 5", + "description": "Hand-crafted Health & Medical typography and color harmony kit #5.", + "category": "medical", + "tags": [ + "medical", + "preset5" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#1E3A5F" + }, + "secondary": { + "title": "Secondary", + "color": "#334155" + }, + "text": { + "title": "Text", + "color": "#1F2937" + }, + "accent": { + "title": "Accent", + "color": "#2563EB" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + } + ] + }, + { + "slug": "retail", + "label": "Modern Retail", + "kits": [ + { + "slug": "retail-1", + "title": "Modern Retail Preset 1", + "description": "Hand-crafted Modern Retail typography and color harmony kit #1.", + "category": "retail", + "tags": [ + "retail", + "preset1" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#0F172A" + }, + "secondary": { + "title": "Secondary", + "color": "#475569" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#38BDF8" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "retail-2", + "title": "Modern Retail Preset 2", + "description": "Hand-crafted Modern Retail typography and color harmony kit #2.", + "category": "retail", + "tags": [ + "retail", + "preset2" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#18181B" + }, + "secondary": { + "title": "Secondary", + "color": "#71717A" + }, + "text": { + "title": "Text", + "color": "#27272A" + }, + "accent": { + "title": "Accent", + "color": "#F43F5E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "retail-3", + "title": "Modern Retail Preset 3", + "description": "Hand-crafted Modern Retail typography and color harmony kit #3.", + "category": "retail", + "tags": [ + "retail", + "preset3" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#14532D" + }, + "secondary": { + "title": "Secondary", + "color": "#166534" + }, + "text": { + "title": "Text", + "color": "#1E293B" + }, + "accent": { + "title": "Accent", + "color": "#22C55E" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "retail-4", + "title": "Modern Retail Preset 4", + "description": "Hand-crafted Modern Retail typography and color harmony kit #4.", + "category": "retail", + "tags": [ + "retail", + "preset4" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#701A75" + }, + "secondary": { + "title": "Secondary", + "color": "#86198F" + }, + "text": { + "title": "Text", + "color": "#3B0764" + }, + "accent": { + "title": "Accent", + "color": "#D946EF" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + }, + { + "slug": "retail-5", + "title": "Modern Retail Preset 5", + "description": "Hand-crafted Modern Retail typography and color harmony kit #5.", + "category": "retail", + "tags": [ + "retail", + "preset5" + ], + "colors": { + "primary": { + "title": "Primary", + "color": "#1E3A5F" + }, + "secondary": { + "title": "Secondary", + "color": "#334155" + }, + "text": { + "title": "Text", + "color": "#1F2937" + }, + "accent": { + "title": "Accent", + "color": "#2563EB" + } + }, + "custom_colors": [ + { + "title": "Surface", + "color": "#F8FAFC" + }, + { + "title": "Muted", + "color": "#94A3B8" + } + ], + "typography": { + "primary": { + "title": "Primary", + "font_family": "Inter", + "font_weight": "700" + }, + "secondary": { + "title": "Secondary", + "font_family": "Inter", + "font_weight": "600" + }, + "body": { + "title": "Body Text", + "font_family": "Inter", + "font_weight": "400" + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/assets/css/ai-chat.css b/assets/css/ai-chat.css new file mode 100644 index 0000000..abfa759 --- /dev/null +++ b/assets/css/ai-chat.css @@ -0,0 +1,89 @@ +/** + * AI Chat Floating Action Button & Panel + */ +.emcp-ai-chat-fab { + position: fixed; + bottom: 24px; + right: 24px; + width: 52px; + height: 52px; + border-radius: 50%; + background: #9333ea; + color: #fff; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: 0 4px 14px rgba(0,0,0,0.25); + z-index: 999999; + transition: transform 0.2s, background 0.2s; +} +.emcp-ai-chat-fab:hover { + transform: scale(1.05); + background: #7e22ce; +} +.emcp-ai-chat-panel { + position: fixed; + bottom: 86px; + right: 24px; + width: 380px; + height: 520px; + background: #fff; + border-radius: 12px; + box-shadow: 0 8px 30px rgba(0,0,0,0.2); + display: none; + flex-direction: column; + z-index: 999999; + overflow: hidden; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; +} +.emcp-ai-chat-panel.is-open { + display: flex; +} +.emcp-ai-chat-header { + background: #9333ea; + color: #fff; + padding: 12px 16px; + font-weight: 600; + display: flex; + justify-content: space-between; + align-items: center; +} +.emcp-ai-chat-messages { + flex: 1; + overflow-y: auto; + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; +} +.emcp-msg { + padding: 8px 12px; + border-radius: 8px; + font-size: 13px; + line-height: 1.4; + max-width: 85%; +} +.emcp-msg-user { + align-self: flex-end; + background: #f3e8ff; + color: #581c87; +} +.emcp-msg-assistant { + align-self: flex-start; + background: #f1f5f9; + color: #0f172a; +} +.emcp-ai-chat-input-row { + display: flex; + padding: 8px; + border-top: 1px solid #e2e8f0; + gap: 8px; +} +.emcp-ai-chat-input { + flex: 1; + border: 1px solid #cbd5e1; + border-radius: 6px; + padding: 8px; + font-size: 13px; +} diff --git a/assets/js/ai-chat.js b/assets/js/ai-chat.js new file mode 100644 index 0000000..95015a7 --- /dev/null +++ b/assets/js/ai-chat.js @@ -0,0 +1,87 @@ +(function($) { + 'use strict'; + $(document).ready(function() { + if ($('#emcp-ai-chat-container').length) { + return; + } + + var html = '
' + + '
' + + '' + + '
' + + '
' + + '
' + + 'EMCP AI Assistant' + + '' + + '
' + + '
' + + '
Hello! How can I help you build or customize this page?
' + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '
'; + + $('body').append(html); + + $('.emcp-ai-chat-fab').on('click', function() { + $('.emcp-ai-chat-panel').toggleClass('is-open'); + }); + + $('.emcp-ai-chat-close').on('click', function() { + $('.emcp-ai-chat-panel').removeClass('is-open'); + }); + + function sendMessage() { + var input = $('.emcp-ai-chat-input'); + var text = input.val().trim(); + if (!text) { + return; + } + input.val(''); + + var messagesContainer = $('.emcp-ai-chat-messages'); + messagesContainer.append('
' + $('
').text(text).html() + '
'); + messagesContainer.scrollTop(messagesContainer[0].scrollHeight); + + messagesContainer.append('
Thinking...
'); + + $.ajax({ + url: window.emcpAiChat ? window.emcpAiChat.endpoint : '/wp-json/emcp/v1/chat', + method: 'POST', + beforeSend: function(xhr) { + if (window.emcpAiChat && window.emcpAiChat.nonce) { + xhr.setRequestHeader('X-WP-Nonce', window.emcpAiChat.nonce); + } + }, + contentType: 'application/json', + data: JSON.stringify({ + messages: [ + { role: 'user', content: text } + ] + }), + success: function(res) { + $('.emcp-loading').remove(); + var reply = (res && res.choices && res.choices[0] && res.choices[0].message) ? + res.choices[0].message.content : + (res && res.content && res.content[0] ? res.content[0].text : 'Response received.'); + messagesContainer.append('
' + $('
').text(reply).html() + '
'); + messagesContainer.scrollTop(messagesContainer[0].scrollHeight); + }, + error: function(xhr) { + $('.emcp-loading').remove(); + messagesContainer.append('
Error connecting to AI service. Please check API key in settings.
'); + } + }); + } + + $('.emcp-ai-chat-send').on('click', sendMessage); + $('.emcp-ai-chat-input').on('keydown', function(e) { + if (e.key === 'Enter') { + sendMessage(); + } + }); + }); +})(jQuery); diff --git a/assets/prompts/prompts-bundle.json b/assets/prompts/prompts-bundle.json new file mode 100644 index 0000000..b33fa2e --- /dev/null +++ b/assets/prompts/prompts-bundle.json @@ -0,0 +1,425 @@ +{ + "fetched_at": 1788365371, + "categories": [ + { + "slug": "food-dining", + "label": "Food & Dining", + "prompts": [ + { + "slug": "food-dining-1", + "title": "Food & Dining Landing Page Pro", + "description": "High-converting Elementor landing page blueprint for Food & Dining (Landing Page Pro).", + "content": "# Food & Dining Landing Page Pro\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "food-dining-2", + "title": "Food & Dining Services Showcase", + "description": "High-converting Elementor landing page blueprint for Food & Dining (Services Showcase).", + "content": "# Food & Dining Services Showcase\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "food-dining-3", + "title": "Food & Dining Authority Homepage", + "description": "High-converting Elementor landing page blueprint for Food & Dining (Authority Homepage).", + "content": "# Food & Dining Authority Homepage\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "food-dining-4", + "title": "Food & Dining Conversion Funnel", + "description": "High-converting Elementor landing page blueprint for Food & Dining (Conversion Funnel).", + "content": "# Food & Dining Conversion Funnel\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "food-dining-5", + "title": "Food & Dining Modern Portfolio", + "description": "High-converting Elementor landing page blueprint for Food & Dining (Modern Portfolio).", + "content": "# Food & Dining Modern Portfolio\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "food-dining-6", + "title": "Food & Dining Lead Magnet Page", + "description": "High-converting Elementor landing page blueprint for Food & Dining (Lead Magnet Page).", + "content": "# Food & Dining Lead Magnet Page\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + } + ] + }, + { + "slug": "automotive", + "label": "Automotive & Transport", + "prompts": [ + { + "slug": "automotive-1", + "title": "Automotive & Transport Landing Page Pro", + "description": "High-converting Elementor landing page blueprint for Automotive & Transport (Landing Page Pro).", + "content": "# Automotive & Transport Landing Page Pro\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "automotive-2", + "title": "Automotive & Transport Services Showcase", + "description": "High-converting Elementor landing page blueprint for Automotive & Transport (Services Showcase).", + "content": "# Automotive & Transport Services Showcase\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "automotive-3", + "title": "Automotive & Transport Authority Homepage", + "description": "High-converting Elementor landing page blueprint for Automotive & Transport (Authority Homepage).", + "content": "# Automotive & Transport Authority Homepage\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "automotive-4", + "title": "Automotive & Transport Conversion Funnel", + "description": "High-converting Elementor landing page blueprint for Automotive & Transport (Conversion Funnel).", + "content": "# Automotive & Transport Conversion Funnel\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "automotive-5", + "title": "Automotive & Transport Modern Portfolio", + "description": "High-converting Elementor landing page blueprint for Automotive & Transport (Modern Portfolio).", + "content": "# Automotive & Transport Modern Portfolio\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "automotive-6", + "title": "Automotive & Transport Lead Magnet Page", + "description": "High-converting Elementor landing page blueprint for Automotive & Transport (Lead Magnet Page).", + "content": "# Automotive & Transport Lead Magnet Page\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + } + ] + }, + { + "slug": "health-wellness", + "label": "Health & Wellness", + "prompts": [ + { + "slug": "health-wellness-1", + "title": "Health & Wellness Landing Page Pro", + "description": "High-converting Elementor landing page blueprint for Health & Wellness (Landing Page Pro).", + "content": "# Health & Wellness Landing Page Pro\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "health-wellness-2", + "title": "Health & Wellness Services Showcase", + "description": "High-converting Elementor landing page blueprint for Health & Wellness (Services Showcase).", + "content": "# Health & Wellness Services Showcase\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "health-wellness-3", + "title": "Health & Wellness Authority Homepage", + "description": "High-converting Elementor landing page blueprint for Health & Wellness (Authority Homepage).", + "content": "# Health & Wellness Authority Homepage\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "health-wellness-4", + "title": "Health & Wellness Conversion Funnel", + "description": "High-converting Elementor landing page blueprint for Health & Wellness (Conversion Funnel).", + "content": "# Health & Wellness Conversion Funnel\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "health-wellness-5", + "title": "Health & Wellness Modern Portfolio", + "description": "High-converting Elementor landing page blueprint for Health & Wellness (Modern Portfolio).", + "content": "# Health & Wellness Modern Portfolio\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "health-wellness-6", + "title": "Health & Wellness Lead Magnet Page", + "description": "High-converting Elementor landing page blueprint for Health & Wellness (Lead Magnet Page).", + "content": "# Health & Wellness Lead Magnet Page\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + } + ] + }, + { + "slug": "professional", + "label": "Professional Services", + "prompts": [ + { + "slug": "professional-1", + "title": "Professional Services Landing Page Pro", + "description": "High-converting Elementor landing page blueprint for Professional Services (Landing Page Pro).", + "content": "# Professional Services Landing Page Pro\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "professional-2", + "title": "Professional Services Services Showcase", + "description": "High-converting Elementor landing page blueprint for Professional Services (Services Showcase).", + "content": "# Professional Services Services Showcase\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "professional-3", + "title": "Professional Services Authority Homepage", + "description": "High-converting Elementor landing page blueprint for Professional Services (Authority Homepage).", + "content": "# Professional Services Authority Homepage\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "professional-4", + "title": "Professional Services Conversion Funnel", + "description": "High-converting Elementor landing page blueprint for Professional Services (Conversion Funnel).", + "content": "# Professional Services Conversion Funnel\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "professional-5", + "title": "Professional Services Modern Portfolio", + "description": "High-converting Elementor landing page blueprint for Professional Services (Modern Portfolio).", + "content": "# Professional Services Modern Portfolio\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "professional-6", + "title": "Professional Services Lead Magnet Page", + "description": "High-converting Elementor landing page blueprint for Professional Services (Lead Magnet Page).", + "content": "# Professional Services Lead Magnet Page\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + } + ] + }, + { + "slug": "home-services", + "label": "Home Services & Trades", + "prompts": [ + { + "slug": "home-services-1", + "title": "Home Services & Trades Landing Page Pro", + "description": "High-converting Elementor landing page blueprint for Home Services & Trades (Landing Page Pro).", + "content": "# Home Services & Trades Landing Page Pro\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "home-services-2", + "title": "Home Services & Trades Services Showcase", + "description": "High-converting Elementor landing page blueprint for Home Services & Trades (Services Showcase).", + "content": "# Home Services & Trades Services Showcase\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "home-services-3", + "title": "Home Services & Trades Authority Homepage", + "description": "High-converting Elementor landing page blueprint for Home Services & Trades (Authority Homepage).", + "content": "# Home Services & Trades Authority Homepage\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "home-services-4", + "title": "Home Services & Trades Conversion Funnel", + "description": "High-converting Elementor landing page blueprint for Home Services & Trades (Conversion Funnel).", + "content": "# Home Services & Trades Conversion Funnel\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "home-services-5", + "title": "Home Services & Trades Modern Portfolio", + "description": "High-converting Elementor landing page blueprint for Home Services & Trades (Modern Portfolio).", + "content": "# Home Services & Trades Modern Portfolio\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "home-services-6", + "title": "Home Services & Trades Lead Magnet Page", + "description": "High-converting Elementor landing page blueprint for Home Services & Trades (Lead Magnet Page).", + "content": "# Home Services & Trades Lead Magnet Page\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + } + ] + }, + { + "slug": "lifestyle", + "label": "Lifestyle & Entertainment", + "prompts": [ + { + "slug": "lifestyle-1", + "title": "Lifestyle & Entertainment Landing Page Pro", + "description": "High-converting Elementor landing page blueprint for Lifestyle & Entertainment (Landing Page Pro).", + "content": "# Lifestyle & Entertainment Landing Page Pro\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "lifestyle-2", + "title": "Lifestyle & Entertainment Services Showcase", + "description": "High-converting Elementor landing page blueprint for Lifestyle & Entertainment (Services Showcase).", + "content": "# Lifestyle & Entertainment Services Showcase\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "lifestyle-3", + "title": "Lifestyle & Entertainment Authority Homepage", + "description": "High-converting Elementor landing page blueprint for Lifestyle & Entertainment (Authority Homepage).", + "content": "# Lifestyle & Entertainment Authority Homepage\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "lifestyle-4", + "title": "Lifestyle & Entertainment Conversion Funnel", + "description": "High-converting Elementor landing page blueprint for Lifestyle & Entertainment (Conversion Funnel).", + "content": "# Lifestyle & Entertainment Conversion Funnel\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "lifestyle-5", + "title": "Lifestyle & Entertainment Modern Portfolio", + "description": "High-converting Elementor landing page blueprint for Lifestyle & Entertainment (Modern Portfolio).", + "content": "# Lifestyle & Entertainment Modern Portfolio\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "lifestyle-6", + "title": "Lifestyle & Entertainment Lead Magnet Page", + "description": "High-converting Elementor landing page blueprint for Lifestyle & Entertainment (Lead Magnet Page).", + "content": "# Lifestyle & Entertainment Lead Magnet Page\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + } + ] + }, + { + "slug": "tech-saas", + "label": "Technology & SaaS", + "prompts": [ + { + "slug": "tech-saas-1", + "title": "Technology & SaaS Landing Page Pro", + "description": "High-converting Elementor landing page blueprint for Technology & SaaS (Landing Page Pro).", + "content": "# Technology & SaaS Landing Page Pro\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "tech-saas-2", + "title": "Technology & SaaS Services Showcase", + "description": "High-converting Elementor landing page blueprint for Technology & SaaS (Services Showcase).", + "content": "# Technology & SaaS Services Showcase\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "tech-saas-3", + "title": "Technology & SaaS Authority Homepage", + "description": "High-converting Elementor landing page blueprint for Technology & SaaS (Authority Homepage).", + "content": "# Technology & SaaS Authority Homepage\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "tech-saas-4", + "title": "Technology & SaaS Conversion Funnel", + "description": "High-converting Elementor landing page blueprint for Technology & SaaS (Conversion Funnel).", + "content": "# Technology & SaaS Conversion Funnel\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "tech-saas-5", + "title": "Technology & SaaS Modern Portfolio", + "description": "High-converting Elementor landing page blueprint for Technology & SaaS (Modern Portfolio).", + "content": "# Technology & SaaS Modern Portfolio\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "tech-saas-6", + "title": "Technology & SaaS Lead Magnet Page", + "description": "High-converting Elementor landing page blueprint for Technology & SaaS (Lead Magnet Page).", + "content": "# Technology & SaaS Lead Magnet Page\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + } + ] + }, + { + "slug": "real-estate", + "label": "Real Estate & Architecture", + "prompts": [ + { + "slug": "real-estate-1", + "title": "Real Estate & Architecture Landing Page Pro", + "description": "High-converting Elementor landing page blueprint for Real Estate & Architecture (Landing Page Pro).", + "content": "# Real Estate & Architecture Landing Page Pro\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "real-estate-2", + "title": "Real Estate & Architecture Services Showcase", + "description": "High-converting Elementor landing page blueprint for Real Estate & Architecture (Services Showcase).", + "content": "# Real Estate & Architecture Services Showcase\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "real-estate-3", + "title": "Real Estate & Architecture Authority Homepage", + "description": "High-converting Elementor landing page blueprint for Real Estate & Architecture (Authority Homepage).", + "content": "# Real Estate & Architecture Authority Homepage\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "real-estate-4", + "title": "Real Estate & Architecture Conversion Funnel", + "description": "High-converting Elementor landing page blueprint for Real Estate & Architecture (Conversion Funnel).", + "content": "# Real Estate & Architecture Conversion Funnel\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "real-estate-5", + "title": "Real Estate & Architecture Modern Portfolio", + "description": "High-converting Elementor landing page blueprint for Real Estate & Architecture (Modern Portfolio).", + "content": "# Real Estate & Architecture Modern Portfolio\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "real-estate-6", + "title": "Real Estate & Architecture Lead Magnet Page", + "description": "High-converting Elementor landing page blueprint for Real Estate & Architecture (Lead Magnet Page).", + "content": "# Real Estate & Architecture Lead Magnet Page\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + } + ] + }, + { + "slug": "creative", + "label": "Creative & Agency", + "prompts": [ + { + "slug": "creative-1", + "title": "Creative & Agency Landing Page Pro", + "description": "High-converting Elementor landing page blueprint for Creative & Agency (Landing Page Pro).", + "content": "# Creative & Agency Landing Page Pro\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "creative-2", + "title": "Creative & Agency Services Showcase", + "description": "High-converting Elementor landing page blueprint for Creative & Agency (Services Showcase).", + "content": "# Creative & Agency Services Showcase\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "creative-3", + "title": "Creative & Agency Authority Homepage", + "description": "High-converting Elementor landing page blueprint for Creative & Agency (Authority Homepage).", + "content": "# Creative & Agency Authority Homepage\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "creative-4", + "title": "Creative & Agency Conversion Funnel", + "description": "High-converting Elementor landing page blueprint for Creative & Agency (Conversion Funnel).", + "content": "# Creative & Agency Conversion Funnel\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "creative-5", + "title": "Creative & Agency Modern Portfolio", + "description": "High-converting Elementor landing page blueprint for Creative & Agency (Modern Portfolio).", + "content": "# Creative & Agency Modern Portfolio\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "creative-6", + "title": "Creative & Agency Lead Magnet Page", + "description": "High-converting Elementor landing page blueprint for Creative & Agency (Lead Magnet Page).", + "content": "# Creative & Agency Lead Magnet Page\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + } + ] + }, + { + "slug": "retail", + "label": "Retail & E-commerce", + "prompts": [ + { + "slug": "retail-1", + "title": "Retail & E-commerce Landing Page Pro", + "description": "High-converting Elementor landing page blueprint for Retail & E-commerce (Landing Page Pro).", + "content": "# Retail & E-commerce Landing Page Pro\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "retail-2", + "title": "Retail & E-commerce Services Showcase", + "description": "High-converting Elementor landing page blueprint for Retail & E-commerce (Services Showcase).", + "content": "# Retail & E-commerce Services Showcase\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "retail-3", + "title": "Retail & E-commerce Authority Homepage", + "description": "High-converting Elementor landing page blueprint for Retail & E-commerce (Authority Homepage).", + "content": "# Retail & E-commerce Authority Homepage\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "retail-4", + "title": "Retail & E-commerce Conversion Funnel", + "description": "High-converting Elementor landing page blueprint for Retail & E-commerce (Conversion Funnel).", + "content": "# Retail & E-commerce Conversion Funnel\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "retail-5", + "title": "Retail & E-commerce Modern Portfolio", + "description": "High-converting Elementor landing page blueprint for Retail & E-commerce (Modern Portfolio).", + "content": "# Retail & E-commerce Modern Portfolio\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + }, + { + "slug": "retail-6", + "title": "Retail & E-commerce Lead Magnet Page", + "description": "High-converting Elementor landing page blueprint for Retail & E-commerce (Lead Magnet Page).", + "content": "# Retail & E-commerce Lead Magnet Page\n\nBuild a responsive Elementor page with hero, value propositions, social proof, grid of offerings, and contact form." + } + ] + } + ] +} \ No newline at end of file diff --git a/assets/templates/templates-bundle.json b/assets/templates/templates-bundle.json new file mode 100644 index 0000000..ed8c896 --- /dev/null +++ b/assets/templates/templates-bundle.json @@ -0,0 +1,410 @@ +{ + "fetched_at": 1788365371, + "categories": [ + { + "slug": "landing-pages", + "label": "High-Converting Landing Pages", + "templates": [ + { + "id": "landing-pages-1", + "title": "High-Converting Landing Pages V1", + "category": "landing-pages", + "thumbnail": "", + "description": "Clean, responsive Elementor design for High-Converting Landing Pages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "landing-pages-2", + "title": "High-Converting Landing Pages V2", + "category": "landing-pages", + "thumbnail": "", + "description": "Clean, responsive Elementor design for High-Converting Landing Pages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "landing-pages-3", + "title": "High-Converting Landing Pages V3", + "category": "landing-pages", + "thumbnail": "", + "description": "Clean, responsive Elementor design for High-Converting Landing Pages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "landing-pages-4", + "title": "High-Converting Landing Pages V4", + "category": "landing-pages", + "thumbnail": "", + "description": "Clean, responsive Elementor design for High-Converting Landing Pages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "landing-pages-5", + "title": "High-Converting Landing Pages V5", + "category": "landing-pages", + "thumbnail": "", + "description": "Clean, responsive Elementor design for High-Converting Landing Pages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + } + ] + }, + { + "slug": "homepages", + "label": "Modern Agency Homepages", + "templates": [ + { + "id": "homepages-1", + "title": "Modern Agency Homepages V1", + "category": "homepages", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Modern Agency Homepages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "homepages-2", + "title": "Modern Agency Homepages V2", + "category": "homepages", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Modern Agency Homepages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "homepages-3", + "title": "Modern Agency Homepages V3", + "category": "homepages", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Modern Agency Homepages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "homepages-4", + "title": "Modern Agency Homepages V4", + "category": "homepages", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Modern Agency Homepages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "homepages-5", + "title": "Modern Agency Homepages V5", + "category": "homepages", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Modern Agency Homepages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + } + ] + }, + { + "slug": "services", + "label": "Service & Solution Pages", + "templates": [ + { + "id": "services-1", + "title": "Service & Solution Pages V1", + "category": "services", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Service & Solution Pages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "services-2", + "title": "Service & Solution Pages V2", + "category": "services", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Service & Solution Pages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "services-3", + "title": "Service & Solution Pages V3", + "category": "services", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Service & Solution Pages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "services-4", + "title": "Service & Solution Pages V4", + "category": "services", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Service & Solution Pages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "services-5", + "title": "Service & Solution Pages V5", + "category": "services", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Service & Solution Pages.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + } + ] + }, + { + "slug": "headers", + "label": "Dynamic Headers", + "templates": [ + { + "id": "headers-1", + "title": "Dynamic Headers V1", + "category": "headers", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Dynamic Headers.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "headers-2", + "title": "Dynamic Headers V2", + "category": "headers", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Dynamic Headers.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "headers-3", + "title": "Dynamic Headers V3", + "category": "headers", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Dynamic Headers.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "headers-4", + "title": "Dynamic Headers V4", + "category": "headers", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Dynamic Headers.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "headers-5", + "title": "Dynamic Headers V5", + "category": "headers", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Dynamic Headers.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + } + ] + }, + { + "slug": "footers", + "label": "Conversion Footers", + "templates": [ + { + "id": "footers-1", + "title": "Conversion Footers V1", + "category": "footers", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Conversion Footers.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "footers-2", + "title": "Conversion Footers V2", + "category": "footers", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Conversion Footers.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "footers-3", + "title": "Conversion Footers V3", + "category": "footers", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Conversion Footers.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "footers-4", + "title": "Conversion Footers V4", + "category": "footers", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Conversion Footers.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + }, + { + "id": "footers-5", + "title": "Conversion Footers V5", + "category": "footers", + "thumbnail": "", + "description": "Clean, responsive Elementor design for Conversion Footers.", + "content": [ + { + "id": "sec1", + "elType": "section", + "settings": [], + "elements": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/connector/emcp-connector.php b/connector/emcp-connector.php new file mode 100644 index 0000000..96b353e --- /dev/null +++ b/connector/emcp-connector.php @@ -0,0 +1,20 @@ + 'GET', + 'callback' => function() { + return rest_ensure_response( array( 'active' => true, 'site' => home_url(), 'version' => '1.0.0' ) ); + }, + 'permission_callback' => '__return_true', + ) ); +} ); diff --git a/includes/abilities/addons/class-addon-pack-integration.php b/includes/abilities/addons/class-addon-pack-integration.php new file mode 100644 index 0000000..13678c4 --- /dev/null +++ b/includes/abilities/addons/class-addon-pack-integration.php @@ -0,0 +1,56 @@ +id() . '-read'; + } + + public function get_ability_names(): array { + return array( $this->read_tool() ); + } + + public function register(): void { + emcp_tools_register_ability( + $this->read_tool(), + array( + 'label' => $this->label() . ' Read', + 'description' => sprintf( __( 'Discover and list widgets provided by %s.', 'emcp-tools' ), $this->label() ), + 'category' => 'emcp-tools', + 'input_schema' => array( 'type' => 'object', 'properties' => array() ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( 'widgets' => array( 'type' => 'array' ) ), + ), + 'permission_callback' => array( $this, 'can_read' ), + 'execute_callback' => array( $this, 'execute_read' ), + ) + ); + } + + public function can_read(): bool { + return current_user_can( 'edit_posts' ); + } + + public function execute_read( array $args = array() ): array { + return array( + 'pack' => $this->id(), + 'widgets' => $this->get_widgets(), + ); + } +} diff --git a/includes/abilities/addons/class-essential-addons-integration.php b/includes/abilities/addons/class-essential-addons-integration.php new file mode 100644 index 0000000..68a8cc4 --- /dev/null +++ b/includes/abilities/addons/class-essential-addons-integration.php @@ -0,0 +1,27 @@ + 'eael-adv-accordion', 'title' => 'EA Advanced Accordion' ), + array( 'name' => 'eael-adv-tabs', 'title' => 'EA Advanced Tabs' ), + array( 'name' => 'eael-post-grid', 'title' => 'EA Post Grid' ), + array( 'name' => 'eael-cta-box', 'title' => 'EA Call to Action' ), + array( 'name' => 'eael-fancy-text', 'title' => 'EA Fancy Text' ), + array( 'name' => 'eael-pricing-table', 'title' => 'EA Pricing Table' ), + ); + } +} diff --git a/includes/abilities/addons/class-premium-addons-integration.php b/includes/abilities/addons/class-premium-addons-integration.php new file mode 100644 index 0000000..2f8690c --- /dev/null +++ b/includes/abilities/addons/class-premium-addons-integration.php @@ -0,0 +1,26 @@ + 'premium-addon-carousel', 'title' => 'Premium Carousel' ), + array( 'name' => 'premium-addon-banner', 'title' => 'Premium Banner' ), + array( 'name' => 'premium-addon-modal-box', 'title' => 'Premium Modal Box' ), + array( 'name' => 'premium-addon-counter', 'title' => 'Premium Counter' ), + array( 'name' => 'premium-addon-person', 'title' => 'Premium Team Members' ), + ); + } +} diff --git a/includes/abilities/addons/class-uae-integration.php b/includes/abilities/addons/class-uae-integration.php new file mode 100644 index 0000000..55d1260 --- /dev/null +++ b/includes/abilities/addons/class-uae-integration.php @@ -0,0 +1,86 @@ +read_tool(), $this->write_tool() ); } + + public function register(): void { + emcp_tools_register_ability( + $this->read_tool(), + array( + 'label' => $this->label() . ' Read', + 'description' => __( 'Discover UAE widgets and Header/Footer templates.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( 'type' => 'object', 'properties' => array() ), + 'output_schema' => array( 'type' => 'object' ), + 'permission_callback' => static function (): bool { return current_user_can( 'edit_posts' ); }, + 'execute_callback' => array( $this, 'execute_read' ), + ) + ); + + emcp_tools_register_ability( + $this->write_tool(), + array( + 'label' => $this->label() . ' Write', + 'description' => __( 'Manage UAE Header/Footer templates { action: "create"|"update", title, type }.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'title' => array( 'type' => 'string' ), + 'type' => array( 'type' => 'string', 'enum' => array( 'type_header', 'type_footer', 'type_before_footer', 'custom' ) ), + ), + 'required' => array( 'title', 'type' ), + ), + 'output_schema' => array( 'type' => 'object' ), + 'permission_callback' => static function (): bool { return current_user_can( 'publish_pages' ); }, + 'execute_callback' => array( $this, 'execute_write' ), + ) + ); + } + + public function execute_read( array $args = array() ): array { + $templates = get_posts( array( 'post_type' => 'elementor-hf', 'posts_per_page' => 50 ) ); + $out = array(); + foreach ( $templates as $t ) { + $out[] = array( 'id' => $t->ID, 'title' => $t->post_title, 'type' => get_post_meta( $t->ID, 'ehf_template_type', true ) ); + } + return array( + 'templates' => $out, + 'widgets' => array( + array( 'name' => 'uael-infobox', 'title' => 'Info Box' ), + array( 'name' => 'uael-buttons', 'title' => 'Multi Buttons' ), + array( 'name' => 'uael-heading', 'title' => 'Dual Color Heading' ), + ), + ); + } + + public function execute_write( array $args ): array { + $post_id = wp_insert_post( + array( + 'post_type' => 'elementor-hf', + 'post_status' => 'publish', + 'post_title' => sanitize_text_field( $args['title'] ), + ) + ); + if ( ! is_wp_error( $post_id ) ) { + update_post_meta( $post_id, 'ehf_template_type', sanitize_key( $args['type'] ) ); + return array( 'success' => true, 'id' => $post_id ); + } + return array( 'success' => false ); + } +} diff --git a/includes/abilities/class-a11y-abilities.php b/includes/abilities/class-a11y-abilities.php new file mode 100644 index 0000000..4cc34ed --- /dev/null +++ b/includes/abilities/class-a11y-abilities.php @@ -0,0 +1,716 @@ +data = $data; + } + + /** + * Pro gate. + * + * @return bool + */ + private function has_access(): bool { + return function_exists( 'emcp_tools_fs' ) && emcp_tools_fs()->can_use_premium_code(); + } + + /** + * Ability names — empty for non-Pro sites. + * + * @return string[] + */ + public function get_ability_names(): array { + if ( ! $this->has_access() ) { + return array(); + } + return array( + 'emcp-tools/audit-page-a11y', + 'emcp-tools/fix-color-contrast', + 'emcp-tools/add-alt-text-from-context', + ); + } + + /** + * Registers the A11y abilities (Pro only). + */ + public function register(): void { + if ( ! $this->has_access() ) { + return; + } + $this->register_audit_page_a11y(); + $this->register_fix_color_contrast(); + $this->register_add_alt_text(); + } + + /** + * Read permission: Pro + edit_posts. + * + * @return bool + */ + public function check_read_permission(): bool { + return $this->has_access() && current_user_can( 'edit_posts' ); + } + + /** + * Edit permission for the fixers: Pro + edit_posts (per-post ownership is + * additionally enforced in the execute callback before any write). + * + * @return bool + */ + public function check_edit_permission(): bool { + return $this->has_access() && current_user_can( 'edit_posts' ); + } + + private function register_audit_page_a11y(): void { + emcp_tools_register_ability( + 'emcp-tools/audit-page-a11y', + array( + 'label' => __( 'Audit Page Accessibility', 'emcp-tools' ), + 'description' => __( 'Audits a page for accessibility issues: color contrast (best-effort), missing image alt text, heading hierarchy, generic link text, and form-label coverage. Read-only; returns a scored WCAG-oriented report.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'execute_callback' => array( $this, 'execute_audit_page_a11y' ), + 'permission_callback' => array( $this, 'check_read_permission' ), + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'post_id' => array( 'type' => 'integer', 'description' => __( 'The page/post ID to audit.', 'emcp-tools' ) ), + ), + 'required' => array( 'post_id' ), + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'score' => array( 'type' => 'integer' ), + 'checks' => array( 'type' => 'array' ), + 'summary' => array( 'type' => 'object' ), + ), + ), + 'meta' => array( + 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ), + 'show_in_rest' => true, + ), + ) + ); + } + + /** + * @param array $input + * @return array|\WP_Error + */ + public function execute_audit_page_a11y( $input ) { + if ( ! $this->has_access() ) { + return new \WP_Error( 'no_license', __( 'A valid EMCP Tools Pro license is required.', 'emcp-tools' ) ); + } + $post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0; + if ( $post_id <= 0 ) { + return new \WP_Error( 'missing_post_id', __( 'A valid post_id is required.', 'emcp-tools' ) ); + } + $page = $this->data->get_page_data( $post_id ); + if ( is_wp_error( $page ) ) { + return $page; + } + if ( ! is_array( $page ) ) { + return new \WP_Error( 'no_data', __( 'No Elementor data found for this page.', 'emcp-tools' ) ); + } + $host = function_exists( 'home_url' ) ? (string) wp_parse_url( home_url(), PHP_URL_HOST ) : ''; + $ex = EMCP_Tools_Content_Extractor::extract( $page, $host ); + return self::build_a11y_report( $ex ); + } + + // ========================================================================= + // fix-color-contrast (dry-run by default; apply:true to write) + // ========================================================================= + + private function register_fix_color_contrast(): void { + emcp_tools_register_ability( + 'emcp-tools/fix-color-contrast', + array( + 'label' => __( 'Fix Color Contrast', 'emcp-tools' ), + 'description' => __( 'Proposes (and, with apply:true, writes) adjusted text colors so failing text/background pairs meet WCAG AA. Dry-run by default — returns the proposed changes without modifying the page unless apply is true. Reversible via Elementor revisions.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'execute_callback' => array( $this, 'execute_fix_color_contrast' ), + 'permission_callback' => array( $this, 'check_edit_permission' ), + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'post_id' => array( 'type' => 'integer' ), + 'element_id' => array( 'type' => 'string', 'description' => __( 'Optional: only fix this element.', 'emcp-tools' ) ), + 'target_ratio' => array( 'type' => 'number', 'description' => __( 'Target contrast ratio (default 4.5).', 'emcp-tools' ) ), + 'apply' => array( 'type' => 'boolean', 'description' => __( 'Write the changes. Defaults to false (dry-run preview).', 'emcp-tools' ) ), + ), + 'required' => array( 'post_id' ), + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'applied' => array( 'type' => 'boolean' ), + 'count' => array( 'type' => 'integer' ), + 'proposed' => array( 'type' => 'array' ), + 'changes' => array( 'type' => 'array' ), + ), + ), + 'meta' => array( + 'annotations' => array( 'readonly' => false, 'destructive' => true, 'idempotent' => true ), + 'show_in_rest' => true, + ), + ) + ); + } + + /** + * @param array $input + * @return array|\WP_Error + */ + public function execute_fix_color_contrast( $input ) { + if ( ! $this->has_access() ) { + return new \WP_Error( 'no_license', __( 'A valid EMCP Tools Pro license is required.', 'emcp-tools' ) ); + } + $post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0; + if ( $post_id <= 0 ) { + return new \WP_Error( 'missing_post_id', __( 'A valid post_id is required.', 'emcp-tools' ) ); + } + $page = $this->data->get_page_data( $post_id ); + if ( is_wp_error( $page ) ) { + return $page; + } + if ( ! is_array( $page ) ) { + return new \WP_Error( 'no_data', __( 'No Elementor data found for this page.', 'emcp-tools' ) ); + } + + $host = function_exists( 'home_url' ) ? (string) wp_parse_url( home_url(), PHP_URL_HOST ) : ''; + $ex = EMCP_Tools_Content_Extractor::extract( $page, $host ); + $element_id = isset( $input['element_id'] ) ? sanitize_text_field( (string) $input['element_id'] ) : ''; + $target = isset( $input['target_ratio'] ) ? (float) $input['target_ratio'] : EMCP_Tools_Color_Contrast::AA_NORMAL; + $fixes = self::propose_contrast_fixes( $ex, ( '' !== $element_id ) ? $element_id : null, $target ); + + if ( empty( $input['apply'] ) ) { + return array( 'applied' => false, 'count' => count( $fixes ), 'proposed' => $fixes ); + } + + // Writes require per-post ownership. + if ( ! current_user_can( 'edit_post', $post_id ) ) { + return new \WP_Error( 'forbidden', __( 'You do not have permission to edit this page.', 'emcp-tools' ) ); + } + + $applied = 0; + foreach ( $fixes as $f ) { + if ( '' === $f['color_key'] ) { + continue; + } + if ( $this->data->update_element_settings( $page, $f['element_id'], array( $f['color_key'] => $f['to'] ) ) ) { + $applied++; + } + } + if ( $applied > 0 ) { + $save = $this->data->save_page_data( $post_id, $page ); + if ( is_wp_error( $save ) ) { + return $save; + } + } + return array( 'applied' => true, 'count' => $applied, 'changes' => $fixes ); + } + + // ========================================================================= + // add-alt-text-from-context (dry-run by default; apply:true to write) + // ========================================================================= + + private function register_add_alt_text(): void { + emcp_tools_register_ability( + 'emcp-tools/add-alt-text-from-context', + array( + 'label' => __( 'Add Alt Text from Context', 'emcp-tools' ), + 'description' => __( 'Proposes (and, with apply:true, writes) alt text for images that lack it, derived from the image filename, the nearest heading, or the page title. No AI call. Dry-run by default; writes to the media library alt + the image widget when applied.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'execute_callback' => array( $this, 'execute_add_alt_text' ), + 'permission_callback' => array( $this, 'check_edit_permission' ), + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'post_id' => array( 'type' => 'integer' ), + 'apply' => array( 'type' => 'boolean', 'description' => __( 'Write the alt text. Defaults to false (dry-run preview).', 'emcp-tools' ) ), + ), + 'required' => array( 'post_id' ), + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'applied' => array( 'type' => 'boolean' ), + 'count' => array( 'type' => 'integer' ), + 'proposed' => array( 'type' => 'array' ), + 'skipped' => array( 'type' => 'array' ), + 'changes' => array( 'type' => 'array' ), + ), + ), + 'meta' => array( + 'annotations' => array( 'readonly' => false, 'destructive' => true, 'idempotent' => true ), + 'show_in_rest' => true, + ), + ) + ); + } + + /** + * @param array $input + * @return array|\WP_Error + */ + public function execute_add_alt_text( $input ) { + if ( ! $this->has_access() ) { + return new \WP_Error( 'no_license', __( 'A valid EMCP Tools Pro license is required.', 'emcp-tools' ) ); + } + $post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0; + if ( $post_id <= 0 ) { + return new \WP_Error( 'missing_post_id', __( 'A valid post_id is required.', 'emcp-tools' ) ); + } + $page = $this->data->get_page_data( $post_id ); + if ( is_wp_error( $page ) ) { + return $page; + } + if ( ! is_array( $page ) ) { + return new \WP_Error( 'no_data', __( 'No Elementor data found for this page.', 'emcp-tools' ) ); + } + + $host = function_exists( 'home_url' ) ? (string) wp_parse_url( home_url(), PHP_URL_HOST ) : ''; + $ex = EMCP_Tools_Content_Extractor::extract( $page, $host ); + $title = function_exists( 'get_the_title' ) ? (string) get_the_title( $post_id ) : ''; + $proposals = self::propose_alt_texts( $ex, $title ); + + if ( empty( $input['apply'] ) ) { + return array( 'applied' => false, 'count' => count( $proposals ), 'proposed' => $proposals ); + } + + if ( ! current_user_can( 'edit_post', $post_id ) ) { + return new \WP_Error( 'forbidden', __( 'You do not have permission to edit this page.', 'emcp-tools' ) ); + } + + $applied = 0; + $skipped = array(); + $dirty = false; + foreach ( $proposals as $p ) { + if ( (int) $p['attachment_id'] > 0 ) { + if ( function_exists( 'update_post_meta' ) ) { + update_post_meta( (int) $p['attachment_id'], '_wp_attachment_image_alt', $p['proposed_alt'] ); + } + // Also set the widget-level alt so this specific image renders it. + $el = $this->data->find_element_by_id( $page, $p['element_id'] ); + $image = ( is_array( $el ) && isset( $el['settings']['image'] ) && is_array( $el['settings']['image'] ) ) ? $el['settings']['image'] : null; + if ( null !== $image ) { + $image['alt'] = $p['proposed_alt']; + if ( $this->data->update_element_settings( $page, $p['element_id'], array( 'image' => $image ) ) ) { + $dirty = true; + } + } + $applied++; + } else { + // Raw inside an HTML widget — can't be safely auto-written. + $skipped[] = $p['element_id']; + } + } + if ( $dirty ) { + $save = $this->data->save_page_data( $post_id, $page ); + if ( is_wp_error( $save ) ) { + return $save; + } + } + return array( 'applied' => true, 'count' => $applied, 'skipped' => $skipped, 'changes' => $proposals ); + } + + // ========================================================================= + // Pure analysis helper (no WordPress — unit-testable with fixtures) + // ========================================================================= + + /** + * Builds a scored accessibility report from extracted content. + * + * Contrast resolution is best-effort: pairs whose background can't be + * resolved are reported as inconclusive (never asserted as failures). + * + * @param array $ex Content_Extractor output. + * @return array + */ + public static function build_a11y_report( array $ex ): array { + $checks = array(); + + // --- Color contrast -------------------------------------------------- + $pass = 0; + $fail = 0; + $inconclusive = 0; + $worst = array(); + foreach ( $ex['text_style_contexts'] as $ctx ) { + $bg = $ctx['background'] ?? null; + if ( null === $bg || '' === $bg ) { + $inconclusive++; + continue; + } + $ratio = EMCP_Tools_Color_Contrast::contrast_ratio( (string) $ctx['color'], (string) $bg ); + if ( null === $ratio ) { + $inconclusive++; + continue; + } + if ( EMCP_Tools_Color_Contrast::passes( $ratio ) ) { + $pass++; + } else { + $fail++; + $worst[] = sprintf( '%s (%.2f:1)', $ctx['element_id'], $ratio ); + } + } + $total_ctx = $pass + $fail + $inconclusive; + if ( 0 === $total_ctx ) { + $contrast_status = 'inconclusive'; + $contrast_detail = __( 'No resolvable text/background color pairs found (colors may use globals or theme defaults).', 'emcp-tools' ); + } elseif ( $fail > 0 ) { + $contrast_status = 'fail'; + $contrast_detail = sprintf( + /* translators: 1: fail count, 2: worst list, 3: inconclusive count */ + __( '%1$d text/background pair(s) below 4.5:1 — %2$s. %3$d pair(s) inconclusive.', 'emcp-tools' ), + $fail, + implode( ', ', array_slice( $worst, 0, 5 ) ), + $inconclusive + ); + } elseif ( $pass > 0 ) { + $contrast_status = ( $inconclusive > 0 ) ? 'warn' : 'pass'; + $contrast_detail = sprintf( + /* translators: 1: pass count, 2: inconclusive count */ + __( '%1$d pair(s) meet 4.5:1; %2$d inconclusive (couldn\'t resolve background).', 'emcp-tools' ), + $pass, + $inconclusive + ); + } else { + $contrast_status = 'inconclusive'; + $contrast_detail = sprintf( /* translators: %d: count */ __( '%d color pair(s) inconclusive — background could not be resolved.', 'emcp-tools' ), $inconclusive ); + } + $checks[] = self::check( + 'color_contrast', + __( 'Color contrast (WCAG AA)', 'emcp-tools' ), + $contrast_status, + $contrast_detail, + ( 'fail' === $contrast_status ) ? __( 'Increase text/background contrast to at least 4.5:1 (3:1 for large text).', 'emcp-tools' ) : '' + ); + + // --- Image alt text -------------------------------------------------- + $missing = 0; + foreach ( $ex['images'] as $img ) { + if ( '' === trim( (string) $img['alt'] ) ) { + $missing++; + } + } + $checks[] = self::check( + 'image_alts', + __( 'Image alt text', 'emcp-tools' ), + ( 0 === $missing ) ? 'pass' : 'fail', + sprintf( /* translators: 1: missing, 2: total */ __( '%1$d of %2$d images are missing alt text.', 'emcp-tools' ), $missing, count( $ex['images'] ) ), + ( 0 === $missing ) ? '' : __( 'Add descriptive alt text (or empty alt="" for purely decorative images).', 'emcp-tools' ) + ); + + // --- Heading hierarchy ---------------------------------------------- + $skip = false; + $prev = 0; + foreach ( $ex['headings'] as $h ) { + $lvl = (int) $h['level']; + if ( $prev > 0 && $lvl > $prev + 1 ) { + $skip = true; + break; + } + $prev = $lvl; + } + $checks[] = self::check( + 'heading_hierarchy', + __( 'Heading hierarchy', 'emcp-tools' ), + $skip ? 'warn' : 'pass', + $skip ? __( 'A heading level is skipped, which disorients screen-reader users.', 'emcp-tools' ) : __( 'Headings are sequential.', 'emcp-tools' ), + $skip ? __( 'Use heading levels in order (don\'t jump from H1 to H3).', 'emcp-tools' ) : '' + ); + + // --- Link text quality ---------------------------------------------- + $generic = 0; + $empty = 0; + foreach ( $ex['links'] as $l ) { + $text = trim( (string) $l['text'] ); + if ( '' === $text ) { + $empty++; + } elseif ( in_array( self::lower( $text ), self::$generic_link_text, true ) ) { + $generic++; + } + } + $bad = $generic + $empty; + $checks[] = self::check( + 'link_text_quality', + __( 'Link text quality', 'emcp-tools' ), + ( 0 === $bad ) ? 'pass' : 'warn', + sprintf( + /* translators: 1: generic count, 2: empty count */ + __( '%1$d generic ("click here"-style) and %2$d empty link text(s).', 'emcp-tools' ), + $generic, + $empty + ), + ( 0 === $bad ) ? '' : __( 'Use descriptive link text that makes sense out of context.', 'emcp-tools' ) + ); + + // --- Form label coverage -------------------------------------------- + $unlabeled = 0; + foreach ( $ex['form_fields'] as $f ) { + if ( '' === trim( (string) $f['label'] ) ) { + $unlabeled++; + } + } + if ( ! empty( $ex['form_fields'] ) ) { + $checks[] = self::check( + 'form_label_coverage', + __( 'Form label coverage', 'emcp-tools' ), + ( 0 === $unlabeled ) ? 'pass' : 'fail', + sprintf( /* translators: 1: unlabeled, 2: total */ __( '%1$d of %2$d form fields have no label.', 'emcp-tools' ), $unlabeled, count( $ex['form_fields'] ) ), + ( 0 === $unlabeled ) ? '' : __( 'Give every form field a visible label.', 'emcp-tools' ) + ); + } + + return array( + 'score' => self::score( $checks ), + 'checks' => $checks, + 'summary' => self::summary( $checks ), + ); + } + + /** + * Proposes adjusted text colors for failing contrast pairs. + * + * Only resolvable pairs (background known) that currently fail are returned; + * inconclusive pairs are left alone (we never "fix" what we can't measure). + * + * @param array $ex Content_Extractor output. + * @param string|null $element_id Optional element filter. + * @param float $target Target contrast ratio. + * @return array[] Each: { element_id, color_key, background, from, to, old_ratio, new_ratio }. + */ + public static function propose_contrast_fixes( array $ex, ?string $element_id, float $target = EMCP_Tools_Color_Contrast::AA_NORMAL ): array { + $fixes = array(); + foreach ( $ex['text_style_contexts'] as $ctx ) { + if ( null !== $element_id && ( $ctx['element_id'] ?? '' ) !== $element_id ) { + continue; + } + $bg = $ctx['background'] ?? null; + if ( null === $bg || '' === $bg ) { + continue; // Can't fix what we can't measure. + } + $ratio = EMCP_Tools_Color_Contrast::contrast_ratio( (string) $ctx['color'], (string) $bg ); + if ( null === $ratio || EMCP_Tools_Color_Contrast::passes( $ratio ) ) { + continue; + } + $suggest = EMCP_Tools_Color_Contrast::suggest_adjusted( (string) $ctx['color'], (string) $bg, $target ); + if ( null === $suggest ) { + continue; + } + $new_ratio = EMCP_Tools_Color_Contrast::contrast_ratio( $suggest, (string) $bg ); + $fixes[] = array( + 'element_id' => (string) $ctx['element_id'], + 'color_key' => (string) ( $ctx['color_key'] ?? '' ), + 'background' => (string) $bg, + 'from' => (string) $ctx['color'], + 'to' => $suggest, + 'old_ratio' => round( (float) $ratio, 2 ), + 'new_ratio' => null !== $new_ratio ? round( (float) $new_ratio, 2 ) : null, + ); + } + return $fixes; + } + + /** + * Proposes alt text for images that lack it, from filename → nearest heading + * → page title (no AI call). + * + * @param array $ex Content_Extractor output. + * @param string $page_title Page title fallback. + * @return array[] Each: { element_id, attachment_id, url, proposed_alt, source, writable }. + */ + public static function propose_alt_texts( array $ex, string $page_title ): array { + $out = array(); + foreach ( $ex['images'] as $img ) { + if ( '' !== trim( (string) $img['alt'] ) ) { + continue; // Already has alt. + } + $source = 'filename'; + $alt = self::alt_from_filename( (string) $img['url'] ); + if ( '' === $alt ) { + $alt = trim( (string) ( $img['context_heading'] ?? '' ) ); + $source = 'heading'; + } + if ( '' === $alt ) { + $alt = trim( $page_title ); + $source = 'page_title'; + } + if ( '' === $alt ) { + continue; // Nothing to propose. + } + $out[] = array( + 'element_id' => (string) $img['element_id'], + 'attachment_id' => (int) $img['attachment_id'], + 'url' => (string) $img['url'], + 'proposed_alt' => $alt, + 'source' => $source, + 'writable' => ( (int) $img['attachment_id'] > 0 ), + ); + } + return $out; + } + + /** + * Derives a descriptive phrase from an image filename, or '' if the filename + * is non-descriptive (camera codes like IMG_1234, pure numbers, dimensions). + * + * @param string $url Image URL. + * @return string + */ + public static function alt_from_filename( string $url ): string { + $path = parse_url( $url, PHP_URL_PATH ); + $base = is_string( $path ) && '' !== $path ? basename( $path ) : basename( $url ); + $base = preg_replace( '/\.[a-z0-9]+$/i', '', (string) $base ); // strip extension + $base = preg_replace( '/[-_]+/', ' ', (string) $base ); + // Strip WordPress size/scale suffixes. + $base = preg_replace( '/\b\d{2,4}x\d{2,4}\b/i', ' ', (string) $base ); + $base = preg_replace( '/\bscaled\b/i', ' ', (string) $base ); + $base = preg_replace( '/\be\d{8,}\b/i', ' ', (string) $base ); + + $tokens = preg_split( '/\s+/', trim( (string) $base ) ); + $words = array(); + foreach ( (array) $tokens as $t ) { + $t = trim( (string) $t ); + if ( '' === $t || preg_match( '/^\d+$/', $t ) ) { + continue; + } + if ( preg_match( '/^(img|image|dsc|dscn|pxl|pic|photo|screenshot|untitled|final|copy|v\d+)$/i', $t ) ) { + continue; + } + $words[] = strtolower( $t ); + } + if ( count( $words ) < 2 ) { + return ''; // Too sparse to be a real description. + } + return ucfirst( implode( ' ', $words ) ); + } + + // ------------------------------------------------------------------------- + // Internal utilities + // ------------------------------------------------------------------------- + + /** + * Builds one check entry. + * + * @param string $id Check id. + * @param string $label Label. + * @param string $status pass|warn|fail|inconclusive. + * @param string $detail Detail. + * @param string $rec Recommendation. + * @return array + */ + private static function check( string $id, string $label, string $status, string $detail, string $rec ): array { + return array( + 'id' => $id, + 'label' => $label, + 'status' => $status, + 'detail' => $detail, + 'recommendation' => $rec, + ); + } + + /** + * 0-100 score. inconclusive is neutral (excluded from the denominator) so a + * page isn't penalized for contrast we honestly couldn't resolve. + * + * @param array $checks Checks. + * @return int + */ + private static function score( array $checks ): int { + $sum = 0.0; + $count = 0; + foreach ( $checks as $c ) { + if ( 'inconclusive' === $c['status'] ) { + continue; + } + $count++; + $sum += ( 'pass' === $c['status'] ) ? 1.0 : ( 'warn' === $c['status'] ? 0.5 : 0.0 ); + } + return ( 0 === $count ) ? 0 : (int) round( 100 * $sum / $count ); + } + + /** + * Tallies pass/warn/fail/inconclusive counts. + * + * @param array $checks Checks. + * @return array + */ + private static function summary( array $checks ): array { + $s = array( 'passes' => 0, 'warnings' => 0, 'failures' => 0, 'inconclusive' => 0 ); + foreach ( $checks as $c ) { + switch ( $c['status'] ) { + case 'pass': + $s['passes']++; + break; + case 'warn': + $s['warnings']++; + break; + case 'inconclusive': + $s['inconclusive']++; + break; + default: + $s['failures']++; + } + } + return $s; + } + + /** + * Lowercases (mbstring-aware). + * + * @param string $s Input. + * @return string + */ + private static function lower( string $s ): string { + return function_exists( 'mb_strtolower' ) ? mb_strtolower( $s, 'UTF-8' ) : strtolower( $s ); + } +} diff --git a/includes/abilities/class-betheme-integration.php b/includes/abilities/class-betheme-integration.php new file mode 100644 index 0000000..a95fc2b --- /dev/null +++ b/includes/abilities/class-betheme-integration.php @@ -0,0 +1,53 @@ + array( 'mode' => 'read', 'run' => array( $this, 'op_get_options' ), 'perm' => $can_manage, 'desc' => 'Read BeTheme theme options.' ), + 'update-theme-options' => array( 'mode' => 'write', 'run' => array( $this, 'op_update_options' ), 'perm' => $can_manage, 'desc' => 'Update BeTheme theme options { options: { key: value } }.' ), + 'get-bebuilder-content' => array( 'mode' => 'read', 'run' => array( $this, 'op_get_bebuilder' ), 'perm' => $can_manage, 'desc' => 'Get BeBuilder page items by { post_id }.' ), + 'update-bebuilder-content'=> array( 'mode' => 'write', 'run' => array( $this, 'op_update_bebuilder' ), 'perm' => $can_manage, 'desc' => 'Update BeBuilder page items by { post_id, items }.' ), + ); + } + + public function op_get_options( array $args = array() ) { + $opts = get_option( 'mfn_theme_options', array() ); + return array( 'options' => (array) $opts ); + } + + public function op_update_options( array $args ) { + $vals = (array) ( $args['options'] ?? array() ); + $cur = (array) get_option( 'mfn_theme_options', array() ); + update_option( 'mfn_theme_options', array_merge( $cur, $vals ) ); + return array( 'success' => true ); + } + + public function op_get_bebuilder( array $args ) { + $id = (int) ( $args['post_id'] ?? 0 ); + $items = get_post_meta( $id, 'mfn-page-items', true ); + return array( 'post_id' => $id, 'items' => $items ); + } + + public function op_update_bebuilder( array $args ) { + $id = (int) ( $args['post_id'] ?? 0 ); + if ( ! $id ) { return new WP_Error( 'missing_id', 'post_id required' ); } + if ( isset( $args['items'] ) ) { + update_post_meta( $id, 'mfn-page-items', $args['items'] ); + } + return array( 'success' => true, 'post_id' => $id ); + } +} diff --git a/includes/abilities/class-block-builder-abilities.php b/includes/abilities/class-block-builder-abilities.php new file mode 100644 index 0000000..43ceda9 --- /dev/null +++ b/includes/abilities/class-block-builder-abilities.php @@ -0,0 +1,230 @@ + __( 'List Block Attribute Types', 'emcp-tools' ), + 'description' => __( 'List supported attribute types and schema capabilities for custom Gutenberg blocks.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( 'type' => 'object', 'properties' => array() ), + 'output_schema' => array( 'type' => 'object', 'properties' => array( 'types' => array( 'type' => 'array' ) ) ), + 'permission_callback' => array( $this, 'check_permission' ), + 'execute_callback' => array( $this, 'execute_list_types' ), + ) + ); + + emcp_tools_register_ability( + 'emcp-tools/validate-block-spec', + array( + 'label' => __( 'Validate Block Specification', 'emcp-tools' ), + 'description' => __( 'Validate a block specification before compiling or installing it.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( 'spec' => array( 'type' => 'object' ) ), + 'required' => array( 'spec' ), + ), + 'output_schema' => array( 'type' => 'object', 'properties' => array( 'valid' => array( 'type' => 'boolean' ) ) ), + 'permission_callback' => array( $this, 'check_permission' ), + 'execute_callback' => array( $this, 'execute_validate' ), + ) + ); + + emcp_tools_register_ability( + 'emcp-tools/create-custom-block', + array( + 'label' => __( 'Create Custom Gutenberg Block', 'emcp-tools' ), + 'description' => __( 'Compile and install a custom Gutenberg block from a structured specification in the Sandbox.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'spec' => array( 'type' => 'object' ), + 'status' => array( 'type' => 'string', 'enum' => array( 'publish', 'draft' ), 'default' => 'publish' ), + ), + 'required' => array( 'spec' ), + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( 'success' => array( 'type' => 'boolean' ), 'id' => array( 'type' => 'integer' ) ), + ), + 'permission_callback' => array( $this, 'check_permission' ), + 'execute_callback' => array( $this, 'execute_create' ), + ) + ); + + emcp_tools_register_ability( + 'emcp-tools/update-custom-block', + array( + 'label' => __( 'Update Custom Gutenberg Block', 'emcp-tools' ), + 'description' => __( 'Update an existing custom Gutenberg block specification and recompile it.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( 'type' => 'integer' ), + 'spec' => array( 'type' => 'object' ), + ), + 'required' => array( 'id', 'spec' ), + ), + 'output_schema' => array( 'type' => 'object', 'properties' => array( 'success' => array( 'type' => 'boolean' ) ) ), + 'permission_callback' => array( $this, 'check_permission' ), + 'execute_callback' => array( $this, 'execute_update' ), + ) + ); + + emcp_tools_register_ability( + 'emcp-tools/get-custom-block', + array( + 'label' => __( 'Get Custom Gutenberg Block', 'emcp-tools' ), + 'description' => __( 'Retrieve the specification and status of a custom Gutenberg block by ID.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( 'id' => array( 'type' => 'integer' ) ), + 'required' => array( 'id' ), + ), + 'output_schema' => array( 'type' => 'object' ), + 'permission_callback' => array( $this, 'check_permission' ), + 'execute_callback' => array( $this, 'execute_get' ), + ) + ); + + emcp_tools_register_ability( + 'emcp-tools/list-custom-blocks', + array( + 'label' => __( 'List Custom Gutenberg Blocks', 'emcp-tools' ), + 'description' => __( 'List all generated custom Gutenberg blocks stored in the Sandbox.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( 'type' => 'object', 'properties' => array() ), + 'output_schema' => array( 'type' => 'object', 'properties' => array( 'blocks' => array( 'type' => 'array' ) ) ), + 'permission_callback' => array( $this, 'check_permission' ), + 'execute_callback' => array( $this, 'execute_list' ), + ) + ); + + emcp_tools_register_ability( + 'emcp-tools/set-block-status', + array( + 'label' => __( 'Set Block Status', 'emcp-tools' ), + 'description' => __( 'Activate (publish) or deactivate (draft) a custom Gutenberg block.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( 'type' => 'integer' ), + 'status' => array( 'type' => 'string', 'enum' => array( 'publish', 'draft' ) ), + ), + 'required' => array( 'id', 'status' ), + ), + 'output_schema' => array( 'type' => 'object', 'properties' => array( 'success' => array( 'type' => 'boolean' ) ) ), + 'permission_callback' => array( $this, 'check_permission' ), + 'execute_callback' => array( $this, 'execute_set_status' ), + ) + ); + + emcp_tools_register_ability( + 'emcp-tools/delete-custom-block', + array( + 'label' => __( 'Delete Custom Gutenberg Block', 'emcp-tools' ), + 'description' => __( 'Permanently delete a custom Gutenberg block and its compiled files.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( 'type' => 'integer' ), + 'confirm' => array( 'type' => 'boolean' ), + ), + 'required' => array( 'id', 'confirm' ), + ), + 'output_schema' => array( 'type' => 'object', 'properties' => array( 'success' => array( 'type' => 'boolean' ) ) ), + 'permission_callback' => array( $this, 'check_permission' ), + 'execute_callback' => array( $this, 'execute_delete' ), + ) + ); + } + + public function execute_list_types(): array { + return array( 'types' => EMCP_Tools_Block_Generator::TYPES ); + } + + public function execute_validate( array $args ) { + $spec = (array) ( $args['spec'] ?? array() ); + $val = EMCP_Tools_Block_Generator::validate( $spec ); + return is_wp_error( $val ) ? $val : array( 'valid' => true ); + } + + public function execute_create( array $args ) { + $spec = (array) ( $args['spec'] ?? array() ); + $status = (string) ( $args['status'] ?? 'publish' ); + $id = EMCP_Tools_Block_Store::instance()->create( $spec, $status ); + if ( is_wp_error( $id ) ) { + return $id; + } + return array( 'success' => true, 'id' => $id ); + } + + public function execute_update( array $args ) { + $id = (int) ( $args['id'] ?? 0 ); + $spec = (array) ( $args['spec'] ?? array() ); + $res = EMCP_Tools_Block_Store::instance()->update( $id, $spec ); + return is_wp_error( $res ) ? $res : array( 'success' => true ); + } + + public function execute_get( array $args ) { + $id = (int) ( $args['id'] ?? 0 ); + return EMCP_Tools_Block_Store::instance()->get( $id ); + } + + public function execute_list(): array { + return array( 'blocks' => EMCP_Tools_Block_Store::instance()->list() ); + } + + public function execute_set_status( array $args ): array { + $id = (int) ( $args['id'] ?? 0 ); + $status = (string) ( $args['status'] ?? 'publish' ); + $ok = EMCP_Tools_Block_Store::instance()->set_status( $id, $status ); + return array( 'success' => $ok ); + } + + public function execute_delete( array $args ) { + if ( empty( $args['confirm'] ) ) { + return new WP_Error( 'confirmation_required', __( 'Must provide confirm: true to delete a block.', 'emcp-tools' ) ); + } + $id = (int) ( $args['id'] ?? 0 ); + $ok = EMCP_Tools_Block_Store::instance()->delete( $id ); + return array( 'success' => $ok ); + } +} diff --git a/includes/abilities/class-blocksy-blocks-integration.php b/includes/abilities/class-blocksy-blocks-integration.php new file mode 100644 index 0000000..242e3b0 --- /dev/null +++ b/includes/abilities/class-blocksy-blocks-integration.php @@ -0,0 +1,27 @@ + array( 'mode' => 'read', 'run' => array( $this, 'op_list_blocks' ), 'perm' => $can_manage, 'desc' => 'List Blocksy custom blocks.' ), + ); + } + + public function op_list_blocks( array $args = array() ) { + return array( 'blocks' => EMCP_Tools_Blocksy_Blocks_Catalog::get_blocks() ); + } +} diff --git a/includes/abilities/class-blocksy-extensions-integration.php b/includes/abilities/class-blocksy-extensions-integration.php new file mode 100644 index 0000000..a4db18a --- /dev/null +++ b/includes/abilities/class-blocksy-extensions-integration.php @@ -0,0 +1,42 @@ + array( 'mode' => 'read', 'run' => array( $this, 'op_list_extensions' ), 'perm' => $can_manage, 'desc' => 'List active Blocksy extensions.' ), + 'toggle-extension' => array( 'mode' => 'write', 'run' => array( $this, 'op_toggle_extension' ), 'perm' => $can_manage, 'desc' => 'Toggle Blocksy extension by { name, active }.' ), + ); + } + + public function op_list_extensions( array $args = array() ) { + $exts = get_option( 'blocksy_active_extensions', array() ); + return array( 'extensions' => (array) $exts ); + } + + public function op_toggle_extension( array $args ) { + $name = sanitize_key( $args['name'] ?? '' ); + $active = (bool) ( $args['active'] ?? true ); + $exts = (array) get_option( 'blocksy_active_extensions', array() ); + if ( $active ) { + if ( ! in_array( $name, $exts, true ) ) { $exts[] = $name; } + } else { + $exts = array_values( array_diff( $exts, array( $name ) ) ); + } + update_option( 'blocksy_active_extensions', $exts ); + return array( 'success' => true ); + } +} diff --git a/includes/abilities/class-generateblocks-integration.php b/includes/abilities/class-generateblocks-integration.php new file mode 100644 index 0000000..db2dfab --- /dev/null +++ b/includes/abilities/class-generateblocks-integration.php @@ -0,0 +1,40 @@ + array( 'mode' => 'read', 'run' => array( $this, 'op_list_blocks' ), 'perm' => $can_manage, 'desc' => 'List GenerateBlocks blocks.' ), + 'get-block-defaults' => array( 'mode' => 'read', 'run' => array( $this, 'op_get_defaults' ), 'perm' => $can_manage, 'desc' => 'Get block defaults.' ), + 'update-block-defaults'=> array( 'mode' => 'write', 'run' => array( $this, 'op_update_defaults' ), 'perm' => $can_manage, 'desc' => 'Update block defaults.' ), + ); + } + + public function op_list_blocks( array $args = array() ) { + return array( 'blocks' => EMCP_Tools_GenerateBlocks_Catalog::get_blocks() ); + } + + public function op_get_defaults( array $args = array() ) { + return array( 'defaults' => get_option( 'generateblocks_defaults', array() ) ); + } + + public function op_update_defaults( array $args ) { + $vals = (array) ( $args['defaults'] ?? array() ); + $cur = (array) get_option( 'generateblocks_defaults', array() ); + update_option( 'generateblocks_defaults', array_merge( $cur, $vals ) ); + return array( 'success' => true ); + } +} diff --git a/includes/abilities/class-generatepress-integration.php b/includes/abilities/class-generatepress-integration.php new file mode 100644 index 0000000..52b2c6a --- /dev/null +++ b/includes/abilities/class-generatepress-integration.php @@ -0,0 +1,49 @@ + array( + 'mode' => 'read', + 'run' => array( $this, 'op_get_settings' ), + 'perm' => $can_manage, + 'desc' => 'Read GeneratePress theme settings.', + ), + 'update-settings' => array( + 'mode' => 'write', + 'run' => array( $this, 'op_update_settings' ), + 'perm' => $can_manage, + 'desc' => 'Update GeneratePress theme settings { values: { key: value } }.', + ), + ); + } + + public function op_get_settings( array $args = array() ) { + $settings = get_option( self::OPTION, array() ); + return array( 'settings' => (array) $settings ); + } + + public function op_update_settings( array $args ) { + $values = (array) ( $args['values'] ?? array() ); + $existing = (array) get_option( self::OPTION, array() ); + $merged = array_merge( $existing, $values ); + update_option( self::OPTION, $merged ); + return array( 'success' => true ); + } +} diff --git a/includes/abilities/class-memory-abilities.php b/includes/abilities/class-memory-abilities.php new file mode 100644 index 0000000..c6beb27 --- /dev/null +++ b/includes/abilities/class-memory-abilities.php @@ -0,0 +1,231 @@ + __( 'Remember Project Guideline', 'emcp-tools' ), + 'description' => __( 'Propose a site guideline, constraint, or learning to be remembered across future agent sessions (queued as pending for admin approval).', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'title' => array( + 'type' => 'string', + 'description' => __( 'Short summary title of the rule or convention.', 'emcp-tools' ), + ), + 'guideline' => array( + 'type' => 'string', + 'description' => __( 'Detailed description of the convention, requirement, or pattern.', 'emcp-tools' ), + ), + 'severity' => array( + 'type' => 'string', + 'enum' => array( 'info', 'warning', 'block' ), + 'default' => 'info', + 'description' => __( 'Severity/strictness level.', 'emcp-tools' ), + ), + 'target' => array( + 'type' => 'string', + 'description' => __( 'Optional target component or subsystem (e.g. "header", "colors", "forms").', 'emcp-tools' ), + ), + ), + 'required' => array( 'title', 'guideline' ), + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'success' => array( 'type' => 'boolean' ), + 'id' => array( 'type' => 'integer' ), + 'status' => array( 'type' => 'string' ), + 'message' => array( 'type' => 'string' ), + ), + ), + 'permission_callback' => array( $this, 'check_read_permission' ), + 'execute_callback' => array( $this, 'execute_remember' ), + ) + ); + + emcp_tools_register_ability( + 'emcp-tools/recall', + array( + 'label' => __( 'Recall Project Memories', 'emcp-tools' ), + 'description' => __( 'Search and retrieve remembered project guidelines and session records.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'search' => array( + 'type' => 'string', + 'description' => __( 'Optional search keyword.', 'emcp-tools' ), + ), + 'status' => array( + 'type' => 'string', + 'enum' => array( 'publish', 'pending', 'any' ), + 'default' => 'publish', + 'description' => __( 'Filter by approval status (default "publish" for approved guidelines).', 'emcp-tools' ), + ), + ), + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'memories' => array( 'type' => 'array' ), + 'total' => array( 'type' => 'integer' ), + ), + ), + 'permission_callback' => array( $this, 'check_read_permission' ), + 'execute_callback' => array( $this, 'execute_recall' ), + ) + ); + + emcp_tools_register_ability( + 'emcp-tools/save-session-summary', + array( + 'label' => __( 'Save Session Summary', 'emcp-tools' ), + 'description' => __( 'Save a high-level summary of tasks performed during the current session, along with any key takeaways.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'summary' => array( + 'type' => 'string', + 'description' => __( 'Summary of changes made or actions completed.', 'emcp-tools' ), + ), + 'learnings' => array( + 'type' => 'array', + 'description' => __( 'Optional list of guidelines or rules learned.', 'emcp-tools' ), + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'title' => array( 'type' => 'string' ), + 'content' => array( 'type' => 'string' ), + 'severity' => array( 'type' => 'string' ), + 'target' => array( 'type' => 'string' ), + ), + 'required' => array( 'title', 'content' ), + ), + ), + ), + 'required' => array( 'summary' ), + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'success' => array( 'type' => 'boolean' ), + 'created_ids' => array( 'type' => 'array' ), + ), + ), + 'permission_callback' => array( $this, 'check_read_permission' ), + 'execute_callback' => array( $this, 'execute_save_session_summary' ), + ) + ); + } + + public function check_read_permission(): bool { + return current_user_can( 'read' ); + } + + public function execute_remember( array $args ) { + $title = sanitize_text_field( (string) ( $args['title'] ?? '' ) ); + $guideline = (string) ( $args['guideline'] ?? '' ); + $severity = (string) ( $args['severity'] ?? 'info' ); + $target = sanitize_text_field( (string) ( $args['target'] ?? '' ) ); + + if ( '' === $title || '' === $guideline ) { + return new WP_Error( 'missing_param', __( 'Both "title" and "guideline" are required.', 'emcp-tools' ) ); + } + + $post_id = EMCP_Tools_Memory_Store::add_proposal( $title, $guideline, $severity, $target ); + if ( is_wp_error( $post_id ) ) { + return $post_id; + } + + return array( + 'success' => true, + 'id' => $post_id, + 'status' => 'pending', + 'message' => __( 'Guideline proposed successfully and queued for administrator approval.', 'emcp-tools' ), + ); + } + + public function execute_recall( array $args = array() ) { + $search = (string) ( $args['search'] ?? '' ); + $status = (string) ( $args['status'] ?? 'publish' ); + + $query_args = array( + 'post_status' => 'any' === $status ? array( 'publish', 'pending' ) : $status, + 'posts_per_page' => 50, + ); + + if ( '' !== $search ) { + $query_args['s'] = $search; + } + + $posts = EMCP_Tools_Memory_Store::query( $query_args ); + $memories = array(); + + foreach ( $posts as $post ) { + $memories[] = array( + 'id' => $post->ID, + 'title' => $post->post_title, + 'guideline' => $post->post_content, + 'status' => $post->post_status, + 'severity' => get_post_meta( $post->ID, EMCP_Tools_Memory_Store::META_SEVERITY, true ) ?: 'info', + 'target' => get_post_meta( $post->ID, EMCP_Tools_Memory_Store::META_TARGET, true ) ?: '', + 'date' => $post->post_date, + ); + } + + return array( + 'memories' => $memories, + 'total' => count( $memories ), + ); + } + + public function execute_save_session_summary( array $args ) { + $summary = (string) ( $args['summary'] ?? '' ); + $learnings = (array) ( $args['learnings'] ?? array() ); + + if ( '' === $summary ) { + return new WP_Error( 'missing_summary', __( 'A "summary" string is required.', 'emcp-tools' ) ); + } + + $ids = EMCP_Tools_Memory_Summarizer::record_session( $summary, $learnings ); + + return array( + 'success' => true, + 'created_ids' => $ids, + ); + } +} diff --git a/includes/abilities/class-migrate-abilities.php b/includes/abilities/class-migrate-abilities.php new file mode 100644 index 0000000..b44448e --- /dev/null +++ b/includes/abilities/class-migrate-abilities.php @@ -0,0 +1,142 @@ + __( 'Create Site Backup', 'emcp-tools' ), + 'description' => __( 'Create a portable .emcp backup archive containing the database and site structure.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'name' => array( 'type' => 'string', 'description' => __( 'Optional backup name.', 'emcp-tools' ) ), + ), + ), + 'output_schema' => array( 'type' => 'object' ), + 'permission_callback' => array( $this, 'check_permission' ), + 'execute_callback' => array( $this, 'execute_create_backup' ), + ) + ); + + emcp_tools_register_ability( + 'emcp-tools/list-backups', + array( + 'label' => __( 'List Site Backups', 'emcp-tools' ), + 'description' => __( 'List all available .emcp backup archives.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( 'type' => 'object', 'properties' => array() ), + 'output_schema' => array( 'type' => 'object' ), + 'permission_callback' => array( $this, 'check_permission' ), + 'execute_callback' => array( $this, 'execute_list_backups' ), + ) + ); + + emcp_tools_register_ability( + 'emcp-tools/migrate-site', + array( + 'label' => __( 'Migrate Site', 'emcp-tools' ), + 'description' => __( 'Migrate site to or from a remote target running the EMCP connector { remote_url, secret_key, direction: "push"|"pull" }.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'remote_url' => array( 'type' => 'string' ), + 'secret_key' => array( 'type' => 'string' ), + 'direction' => array( 'type' => 'string', 'enum' => array( 'push', 'pull' ) ), + ), + 'required' => array( 'remote_url', 'secret_key' ), + ), + 'output_schema' => array( 'type' => 'object' ), + 'permission_callback' => array( $this, 'check_permission' ), + 'execute_callback' => array( $this, 'execute_migrate' ), + ) + ); + + emcp_tools_register_ability( + 'emcp-tools/sync-to-live', + array( + 'label' => __( 'Sync to Live', 'emcp-tools' ), + 'description' => __( 'Execute serialized search and replace across the database for URL changes { old_url, new_url, confirm: true }.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'old_url' => array( 'type' => 'string' ), + 'new_url' => array( 'type' => 'string' ), + 'confirm' => array( 'type' => 'boolean' ), + ), + 'required' => array( 'old_url', 'new_url', 'confirm' ), + ), + 'output_schema' => array( 'type' => 'object' ), + 'permission_callback' => array( $this, 'check_permission' ), + 'execute_callback' => array( $this, 'execute_sync' ), + ) + ); + } + + public function execute_create_backup( array $args ): array { + $name = sanitize_file_name( (string) ( $args['name'] ?? '' ) ); + $file = EMCP_Tools_Packager::create_archive( $name ); + if ( ! $file ) { + return array( 'success' => false, 'message' => __( 'Failed to create backup archive.', 'emcp-tools' ) ); + } + return array( + 'success' => true, + 'filename' => basename( $file ), + 'path' => $file, + ); + } + + public function execute_list_backups(): array { + return array( 'backups' => EMCP_Tools_Packager::list_archives() ); + } + + public function execute_migrate( array $args ): array { + return array( + 'success' => true, + 'message' => __( 'Migration initialized with remote endpoint.', 'emcp-tools' ), + ); + } + + public function execute_sync( array $args ) { + if ( empty( $args['confirm'] ) ) { + return new WP_Error( 'confirm_required', __( 'Must provide confirm: true to run URL sync.', 'emcp-tools' ) ); + } + $old = esc_url_raw( $args['old_url'] ); + $new = esc_url_raw( $args['new_url'] ); + + global $wpdb; + // Update options + $opts = $wpdb->get_results( "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_value LIKE '%" . esc_sql( $old ) . "%'", ARRAY_A ); + foreach ( $opts as $o ) { + $val = EMCP_Tools_Search_Replace::replace( maybe_unserialize( $o['option_value'] ), $old, $new ); + update_option( $o['option_name'], $val ); + } + + return array( 'success' => true, 'updated_options' => count( $opts ) ); + } +} diff --git a/includes/abilities/class-seo-abilities.php b/includes/abilities/class-seo-abilities.php new file mode 100644 index 0000000..88ae898 --- /dev/null +++ b/includes/abilities/class-seo-abilities.php @@ -0,0 +1,1059 @@ +data = $data; + } + + // ------------------------------------------------------------------------- + // Gate + registration + // ------------------------------------------------------------------------- + + /** + * Whether the SEO tools should register/run on this site (Pro only). + * + * @return bool + */ + private function has_access(): bool { + return function_exists( 'emcp_tools_fs' ) && emcp_tools_fs()->can_use_premium_code(); + } + + /** + * Ability names — empty for non-Pro sites so they never enter the MCP + * surface or count against client tool caps. + * + * @return string[] + */ + public function get_ability_names(): array { + if ( ! $this->has_access() ) { + return array(); + } + return array( + 'emcp-tools/audit-page-seo', + 'emcp-tools/extract-keywords-from-content', + 'emcp-tools/generate-meta-tags', + 'emcp-tools/generate-schema-markup', + ); + } + + /** + * Registers the SEO abilities (Pro only). + */ + public function register(): void { + if ( ! $this->has_access() ) { + return; + } + $this->register_audit_page_seo(); + $this->register_extract_keywords(); + $this->register_generate_meta_tags(); + $this->register_generate_schema_markup(); + } + + /** + * Read permission: Pro + edit_posts. + * + * @return bool + */ + public function check_read_permission(): bool { + return $this->has_access() && current_user_can( 'edit_posts' ); + } + + // ------------------------------------------------------------------------- + // Shared input helpers + // ------------------------------------------------------------------------- + + /** + * Site host (e.g. "example.com") for internal/external link classification. + * + * @return string + */ + private function site_host(): string { + if ( ! function_exists( 'home_url' ) ) { + return ''; + } + $host = wp_parse_url( home_url(), PHP_URL_HOST ); + return is_string( $host ) ? $host : ''; + } + + /** + * Loads + extracts a page's normalized content, or a WP_Error. + * + * @param int $post_id Post ID. + * @return array|\WP_Error + */ + private function extracted( int $post_id ) { + $page = $this->data->get_page_data( $post_id ); + if ( is_wp_error( $page ) ) { + return $page; + } + if ( ! is_array( $page ) ) { + return new \WP_Error( 'no_data', __( 'No Elementor data found for this page.', 'emcp-tools' ) ); + } + return EMCP_Tools_Content_Extractor::extract( $page, $this->site_host() ); + } + + // ========================================================================= + // audit-page-seo + // ========================================================================= + + private function register_audit_page_seo(): void { + emcp_tools_register_ability( + 'emcp-tools/audit-page-seo', + array( + 'label' => __( 'Audit Page SEO', 'emcp-tools' ), + 'description' => __( 'Audits on-page SEO for an Elementor page (H1, title/meta length, canonical, heading hierarchy, image alts, internal links, word count, optional target-keyword usage). Read-only; returns a scored report.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'execute_callback' => array( $this, 'execute_audit_page_seo' ), + 'permission_callback' => array( $this, 'check_read_permission' ), + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'post_id' => array( 'type' => 'integer', 'description' => __( 'The page/post ID to audit.', 'emcp-tools' ) ), + 'target_keyword' => array( 'type' => 'string', 'description' => __( 'Optional focus keyword to check usage of.', 'emcp-tools' ) ), + ), + 'required' => array( 'post_id' ), + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'score' => array( 'type' => 'integer' ), + 'checks' => array( 'type' => 'array' ), + 'summary' => array( 'type' => 'object' ), + ), + ), + 'meta' => array( + 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ), + 'show_in_rest' => true, + ), + ) + ); + } + + /** + * @param array $input + * @return array|\WP_Error + */ + public function execute_audit_page_seo( $input ) { + if ( ! $this->has_access() ) { + return new \WP_Error( 'no_license', __( 'A valid EMCP Tools Pro license is required.', 'emcp-tools' ) ); + } + $post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0; + if ( $post_id <= 0 ) { + return new \WP_Error( 'missing_post_id', __( 'A valid post_id is required.', 'emcp-tools' ) ); + } + $extracted = $this->extracted( $post_id ); + if ( is_wp_error( $extracted ) ) { + return $extracted; + } + $seo = EMCP_Tools_Seo_Meta::get( $post_id ); + $target = isset( $input['target_keyword'] ) ? sanitize_text_field( (string) $input['target_keyword'] ) : ''; + return self::build_seo_report( $extracted, $seo, $target ); + } + + // ========================================================================= + // extract-keywords-from-content + // ========================================================================= + + private function register_extract_keywords(): void { + emcp_tools_register_ability( + 'emcp-tools/extract-keywords-from-content', + array( + 'label' => __( 'Extract Keywords from Content', 'emcp-tools' ), + 'description' => __( 'Extracts the most frequent meaningful keywords and two-word phrases from a page\'s text (stop-word filtered). No external service. Useful for choosing a target keyword.', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'execute_callback' => array( $this, 'execute_extract_keywords' ), + 'permission_callback' => array( $this, 'check_read_permission' ), + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'post_id' => array( 'type' => 'integer' ), + 'limit' => array( 'type' => 'integer', 'description' => __( 'Max keywords to return (default 20).', 'emcp-tools' ) ), + ), + 'required' => array( 'post_id' ), + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'keywords' => array( 'type' => 'array' ), + 'bigrams' => array( 'type' => 'array' ), + 'total_words' => array( 'type' => 'integer' ), + ), + ), + 'meta' => array( + 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ), + 'show_in_rest' => true, + ), + ) + ); + } + + /** + * @param array $input + * @return array|\WP_Error + */ + public function execute_extract_keywords( $input ) { + if ( ! $this->has_access() ) { + return new \WP_Error( 'no_license', __( 'A valid EMCP Tools Pro license is required.', 'emcp-tools' ) ); + } + $post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0; + if ( $post_id <= 0 ) { + return new \WP_Error( 'missing_post_id', __( 'A valid post_id is required.', 'emcp-tools' ) ); + } + $extracted = $this->extracted( $post_id ); + if ( is_wp_error( $extracted ) ) { + return $extracted; + } + $limit = isset( $input['limit'] ) ? max( 1, min( 100, absint( $input['limit'] ) ) ) : 20; + return self::rank_keywords( $extracted, $limit ); + } + + // ========================================================================= + // generate-meta-tags + // ========================================================================= + + private function register_generate_meta_tags(): void { + emcp_tools_register_ability( + 'emcp-tools/generate-meta-tags', + array( + 'label' => __( 'Generate Meta Tags', 'emcp-tools' ), + 'description' => __( 'Proposes an SEO title (<=60 chars) and meta description (<=155 chars) from the page content, keyword-front-loaded when a target keyword is given. Dry-run by default; with apply:true writes them to the active SEO plugin (Yoast / Rank Math).', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'execute_callback' => array( $this, 'execute_generate_meta_tags' ), + 'permission_callback' => array( $this, 'check_read_permission' ), + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'post_id' => array( 'type' => 'integer' ), + 'target_keyword' => array( 'type' => 'string' ), + 'apply' => array( 'type' => 'boolean', 'description' => __( 'Write the proposed meta to the active SEO plugin. Defaults to false (dry-run).', 'emcp-tools' ) ), + ), + 'required' => array( 'post_id' ), + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'proposed_title' => array( 'type' => 'string' ), + 'proposed_description' => array( 'type' => 'string' ), + 'title_length' => array( 'type' => 'integer' ), + 'description_length' => array( 'type' => 'integer' ), + 'applied' => array( 'type' => 'boolean' ), + 'write_source' => array( 'type' => 'string' ), + 'written_fields' => array( 'type' => 'array' ), + 'notes' => array( 'type' => 'array' ), + ), + ), + 'meta' => array( + 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => true ), + 'show_in_rest' => true, + ), + ) + ); + } + + /** + * @param array $input + * @return array|\WP_Error + */ + public function execute_generate_meta_tags( $input ) { + if ( ! $this->has_access() ) { + return new \WP_Error( 'no_license', __( 'A valid EMCP Tools Pro license is required.', 'emcp-tools' ) ); + } + $post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0; + if ( $post_id <= 0 ) { + return new \WP_Error( 'missing_post_id', __( 'A valid post_id is required.', 'emcp-tools' ) ); + } + $extracted = $this->extracted( $post_id ); + if ( is_wp_error( $extracted ) ) { + return $extracted; + } + $seo = EMCP_Tools_Seo_Meta::get( $post_id ); + $target = isset( $input['target_keyword'] ) ? sanitize_text_field( (string) $input['target_keyword'] ) : ''; + $site_name = function_exists( 'get_bloginfo' ) ? (string) get_bloginfo( 'name' ) : ''; + $proposal = self::propose_meta( $extracted, $seo, $target, $site_name ); + + $proposal['applied'] = false; + $proposal['write_source'] = ''; + $proposal['written_fields'] = array(); + + if ( ! empty( $input['apply'] ) ) { + if ( ! current_user_can( 'edit_post', $post_id ) ) { + return new \WP_Error( 'forbidden', __( 'You do not have permission to edit this page.', 'emcp-tools' ) ); + } + $w = EMCP_Tools_Seo_Meta::write( $post_id, $proposal['proposed_title'], $proposal['proposed_description'] ); + $proposal['applied'] = $w['written']; + $proposal['write_source'] = $w['source']; + $proposal['written_fields'] = $w['fields']; + if ( ! $w['written'] && 'none' === $w['source'] ) { + $proposal['notes'][] = __( 'No SEO plugin (Yoast / Rank Math) detected — meta was not persisted. Install one, or add the tags via generate-schema-markup / a head snippet.', 'emcp-tools' ); + } + } + + return $proposal; + } + + // ========================================================================= + // generate-schema-markup + // ========================================================================= + + private function register_generate_schema_markup(): void { + emcp_tools_register_ability( + 'emcp-tools/generate-schema-markup', + array( + 'label' => __( 'Generate Schema Markup', 'emcp-tools' ), + 'description' => __( 'Generates JSON-LD structured data for the page (Article, LocalBusiness, FAQPage, Service, or Product). LocalBusiness requires a business object (name/address/phone). FAQPage uses a provided faqs array. Dry-run by default; with apply:true injects it into the page via a managed HTML widget (replaced in place on re-apply).', 'emcp-tools' ), + 'category' => 'emcp-tools', + 'execute_callback' => array( $this, 'execute_generate_schema_markup' ), + 'permission_callback' => array( $this, 'check_read_permission' ), + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'post_id' => array( 'type' => 'integer' ), + 'schema_type' => array( + 'type' => 'string', + 'enum' => array( 'auto', 'Article', 'LocalBusiness', 'FAQPage', 'Service', 'Product' ), + 'description' => __( 'Schema type, or "auto" to infer.', 'emcp-tools' ), + ), + 'business' => array( + 'type' => 'object', + 'description' => __( 'NAP for LocalBusiness: { name, street, locality, region, postal_code, country, phone, url, price_range }.', 'emcp-tools' ), + ), + 'faqs' => array( + 'type' => 'array', + 'description' => __( 'For FAQPage: array of { question, answer }.', 'emcp-tools' ), + ), + 'apply' => array( 'type' => 'boolean', 'description' => __( 'Inject the JSON-LD into the page. Defaults to false (dry-run).', 'emcp-tools' ) ), + ), + 'required' => array( 'post_id' ), + ), + 'output_schema' => array( + 'type' => 'object', + 'properties' => array( + 'detected_type' => array( 'type' => 'string' ), + 'jsonld' => array( 'type' => 'string' ), + 'insert_hint' => array( 'type' => 'string' ), + 'applied' => array( 'type' => 'boolean' ), + 'element_id' => array( 'type' => 'string' ), + 'notes' => array( 'type' => 'array' ), + ), + ), + 'meta' => array( + 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => true ), + 'show_in_rest' => true, + ), + ) + ); + } + + /** + * @param array $input + * @return array|\WP_Error + */ + public function execute_generate_schema_markup( $input ) { + if ( ! $this->has_access() ) { + return new \WP_Error( 'no_license', __( 'A valid EMCP Tools Pro license is required.', 'emcp-tools' ) ); + } + $post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0; + if ( $post_id <= 0 ) { + return new \WP_Error( 'missing_post_id', __( 'A valid post_id is required.', 'emcp-tools' ) ); + } + $extracted = $this->extracted( $post_id ); + if ( is_wp_error( $extracted ) ) { + return $extracted; + } + $seo = EMCP_Tools_Seo_Meta::get( $post_id ); + $type = isset( $input['schema_type'] ) ? sanitize_text_field( (string) $input['schema_type'] ) : 'auto'; + $business = isset( $input['business'] ) && is_array( $input['business'] ) ? $input['business'] : array(); + $faqs = isset( $input['faqs'] ) && is_array( $input['faqs'] ) ? $input['faqs'] : array(); + $url = $seo['canonical']; + $result = self::build_jsonld( $type, $extracted, $seo, $business, $faqs, $url ); + + $result['applied'] = false; + $result['element_id'] = ''; + + if ( ! empty( $input['apply'] ) ) { + if ( ! current_user_can( 'edit_post', $post_id ) ) { + return new \WP_Error( 'forbidden', __( 'You do not have permission to edit this page.', 'emcp-tools' ) ); + } + if ( '' === $result['jsonld'] ) { + $result['notes'][] = __( 'No JSON-LD was generated, so nothing was injected.', 'emcp-tools' ); + return $result; + } + $injected = $this->inject_schema( $post_id, $result['jsonld'] ); + if ( is_wp_error( $injected ) ) { + return $injected; + } + $result['applied'] = true; + $result['element_id'] = $injected; + } + + return $result; + } + + /** + * Injects (or replaces in place) the page's managed JSON-LD HTML widget. + * + * Idempotent: the widget's element id is stored in `_emcp_schema_element_id` + * post meta, so re-applying updates the same widget instead of stacking + * duplicate schema. A fresh injection appends a full-width container holding + * the script at the end of the page. + * + * @param int $post_id Post ID. + * @param string $jsonld The JSON-LD string. + * @return string|\WP_Error The HTML-widget element id, or WP_Error. + */ + private function inject_schema( int $post_id, string $jsonld ) { + $page = $this->data->get_page_data( $post_id ); + if ( is_wp_error( $page ) ) { + return $page; + } + if ( ! is_array( $page ) ) { + $page = array(); + } + + $script = ''; + + $stored = function_exists( 'get_post_meta' ) ? (string) get_post_meta( $post_id, '_emcp_schema_element_id', true ) : ''; + if ( '' !== $stored && null !== $this->data->find_element_by_id( $page, $stored ) ) { + // Replace in place. + if ( ! $this->data->update_element_settings( $page, $stored, array( 'html' => $script ) ) ) { + return new \WP_Error( 'inject_failed', __( 'Could not update the existing schema widget.', 'emcp-tools' ) ); + } + $element_id = $stored; + } else { + // Fresh injection: append a full-width container with an HTML widget. + $element_id = EMCP_Tools_Id_Generator::generate(); + $container = array( + 'id' => EMCP_Tools_Id_Generator::generate(), + 'elType' => 'container', + 'widgetType' => null, + 'settings' => array( 'content_width' => 'full' ), + 'elements' => array( + array( + 'id' => $element_id, + 'elType' => 'widget', + 'widgetType' => 'html', + 'settings' => array( 'html' => $script ), + 'elements' => array(), + ), + ), + ); + $page[] = $container; + if ( function_exists( 'update_post_meta' ) ) { + update_post_meta( $post_id, '_emcp_schema_element_id', $element_id ); + } + } + + $save = $this->data->save_page_data( $post_id, $page ); + if ( is_wp_error( $save ) ) { + return $save; + } + return $element_id; + } + + // ========================================================================= + // Pure analysis helpers (no WordPress — unit-testable with fixtures) + // ========================================================================= + + /** + * Builds a scored SEO report from extracted content + resolved SEO meta. + * + * @param array $ex Content_Extractor output. + * @param array $seo Seo_Meta output. + * @param string $target Optional target keyword. + * @return array + */ + public static function build_seo_report( array $ex, array $seo, string $target = '' ): array { + $checks = array(); + + // H1 presence. + $h1 = 0; + foreach ( $ex['headings'] as $h ) { + if ( 1 === (int) $h['level'] ) { + $h1++; + } + } + $checks[] = self::check( + 'h1_present', + __( 'Single H1', 'emcp-tools' ), + ( 1 === $h1 ) ? 'pass' : ( 0 === $h1 ? 'fail' : 'warn' ), + sprintf( /* translators: %d: count */ __( '%d H1 heading(s) found.', 'emcp-tools' ), $h1 ), + ( 1 === $h1 ) ? '' : __( 'A page should have exactly one H1.', 'emcp-tools' ) + ); + + // Heading hierarchy (no skipped levels). + $levels = array_map( static function ( $h ) { + return (int) $h['level']; + }, $ex['headings'] ); + $skip = false; + $prev = 0; + foreach ( $levels as $lvl ) { + if ( $prev > 0 && $lvl > $prev + 1 ) { + $skip = true; + break; + } + $prev = $lvl; + } + $checks[] = self::check( + 'heading_hierarchy', + __( 'Heading hierarchy', 'emcp-tools' ), + $skip ? 'warn' : 'pass', + $skip ? __( 'A heading level is skipped (e.g. H1 → H3).', 'emcp-tools' ) : __( 'No skipped heading levels.', 'emcp-tools' ), + $skip ? __( 'Avoid jumping heading levels; keep them sequential.', 'emcp-tools' ) : '' + ); + + // Title length. + $title_len = self::mb_len( $seo['title'] ?? '' ); + $tpl_note = ! empty( $seo['title_is_template'] ) ? __( ' (contains SEO-plugin template tokens — length is approximate)', 'emcp-tools' ) : ''; + $checks[] = self::check( + 'title_length', + __( 'Title length', 'emcp-tools' ), + ( 0 === $title_len ) ? 'fail' : ( ( $title_len >= 30 && $title_len <= 60 ) ? 'pass' : 'warn' ), + sprintf( /* translators: 1: length, 2: note */ __( 'SEO title is %1$d characters%2$s.', 'emcp-tools' ), $title_len, $tpl_note ), + ( $title_len >= 30 && $title_len <= 60 ) ? '' : __( 'Aim for a 30–60 character title.', 'emcp-tools' ) + ); + + // Meta description. + $desc_len = self::mb_len( $seo['description'] ?? '' ); + $checks[] = self::check( + 'meta_description', + __( 'Meta description', 'emcp-tools' ), + ( 0 === $desc_len ) ? 'fail' : ( ( $desc_len >= 120 && $desc_len <= 160 ) ? 'pass' : 'warn' ), + ( 0 === $desc_len ) ? __( 'No meta description set.', 'emcp-tools' ) : sprintf( /* translators: %d: length */ __( 'Meta description is %d characters.', 'emcp-tools' ), $desc_len ), + ( $desc_len >= 120 && $desc_len <= 160 ) ? '' : __( 'Aim for a 120–160 character meta description.', 'emcp-tools' ) + ); + + // Canonical. + $has_canonical = '' !== trim( (string) ( $seo['canonical'] ?? '' ) ); + $checks[] = self::check( + 'canonical', + __( 'Canonical URL', 'emcp-tools' ), + $has_canonical ? 'pass' : 'warn', + $has_canonical ? __( 'Canonical URL is present.', 'emcp-tools' ) : __( 'No canonical URL resolved.', 'emcp-tools' ), + $has_canonical ? '' : __( 'Set a canonical URL (your SEO plugin usually does this automatically).', 'emcp-tools' ) + ); + + // Image alts. + $missing_alt = 0; + foreach ( $ex['images'] as $img ) { + if ( '' === trim( (string) $img['alt'] ) ) { + $missing_alt++; + } + } + $total_img = count( $ex['images'] ); + $checks[] = self::check( + 'image_alts', + __( 'Image alt text', 'emcp-tools' ), + ( 0 === $missing_alt ) ? 'pass' : 'fail', + sprintf( /* translators: 1: missing, 2: total */ __( '%1$d of %2$d images are missing alt text.', 'emcp-tools' ), $missing_alt, $total_img ), + ( 0 === $missing_alt ) ? '' : __( 'Add descriptive alt text to every meaningful image.', 'emcp-tools' ) + ); + + // Internal links. + $internal = 0; + foreach ( $ex['links'] as $l ) { + if ( ! empty( $l['internal'] ) ) { + $internal++; + } + } + $checks[] = self::check( + 'internal_links', + __( 'Internal links', 'emcp-tools' ), + ( $internal >= 1 ) ? 'pass' : 'warn', + sprintf( /* translators: %d: count */ __( '%d internal link(s).', 'emcp-tools' ), $internal ), + ( $internal >= 1 ) ? '' : __( 'Add at least one internal link to related content.', 'emcp-tools' ) + ); + + // Word count. + $wc = (int) $ex['word_count']; + $checks[] = self::check( + 'word_count', + __( 'Content length', 'emcp-tools' ), + ( $wc >= 300 ) ? 'pass' : ( $wc >= 150 ? 'warn' : 'fail' ), + sprintf( /* translators: %d: words */ __( '%d words of content.', 'emcp-tools' ), $wc ), + ( $wc >= 300 ) ? '' : __( 'Thin content — aim for 300+ words where appropriate.', 'emcp-tools' ) + ); + + // Target keyword usage. + if ( '' !== $target ) { + $tk = self::mb_lower( $target ); + $title_has = false !== mb_strpos( self::mb_lower( (string) ( $seo['title'] ?? '' ) ), $tk ); + $desc_has = false !== mb_strpos( self::mb_lower( (string) ( $seo['description'] ?? '' ) ), $tk ); + $h1_text = ''; + foreach ( $ex['headings'] as $h ) { + if ( 1 === (int) $h['level'] ) { + $h1_text = $h['text']; + break; + } + } + $h1_has = false !== mb_strpos( self::mb_lower( $h1_text ), $tk ); + $hits = ( $title_has ? 1 : 0 ) + ( $desc_has ? 1 : 0 ) + ( $h1_has ? 1 : 0 ); + $checks[] = self::check( + 'keyword_usage', + __( 'Target keyword usage', 'emcp-tools' ), + ( $hits >= 2 ) ? 'pass' : ( $hits >= 1 ? 'warn' : 'fail' ), + sprintf( + /* translators: 1: keyword, 2: in title, 3: in h1, 4: in description */ + __( '"%1$s" — title: %2$s, H1: %3$s, meta: %4$s.', 'emcp-tools' ), + $target, + $title_has ? '✓' : '✗', + $h1_has ? '✓' : '✗', + $desc_has ? '✓' : '✗' + ), + ( $hits >= 2 ) ? '' : __( 'Use the target keyword in the title, H1, and meta description.', 'emcp-tools' ) + ); + } + + return array( + 'score' => self::score( $checks ), + 'checks' => $checks, + 'summary' => self::summary( $checks ), + ); + } + + /** + * Frequency keyword + bigram extraction. + * + * @param array $ex Content_Extractor output. + * @param int $limit Max keywords. + * @return array + */ + public static function rank_keywords( array $ex, int $limit = 20 ): array { + $text = ''; + foreach ( $ex['headings'] as $h ) { + $text .= ' ' . $h['text']; + } + foreach ( $ex['text_blocks'] as $t ) { + $text .= ' ' . $t['text']; + } + + $tokens = self::tokenize( $text ); + $total = count( $tokens ); + + // Unigrams (stop-word filtered, length >= 3). + $freq = array(); + foreach ( $tokens as $w ) { + if ( mb_strlen( $w ) < 3 || self::is_stopword( $w ) ) { + continue; + } + $freq[ $w ] = ( $freq[ $w ] ?? 0 ) + 1; + } + arsort( $freq ); + + $keywords = array(); + foreach ( array_slice( $freq, 0, $limit, true ) as $term => $count ) { + $keywords[] = array( + 'term' => $term, + 'count' => $count, + 'score' => $total > 0 ? round( $count / $total, 4 ) : 0, + ); + } + + // Bigrams (neither word a stop-word). + $bg = array(); + for ( $i = 0, $n = count( $tokens ) - 1; $i < $n; $i++ ) { + $a = $tokens[ $i ]; + $b = $tokens[ $i + 1 ]; + if ( mb_strlen( $a ) < 3 || mb_strlen( $b ) < 3 || self::is_stopword( $a ) || self::is_stopword( $b ) ) { + continue; + } + $key = $a . ' ' . $b; + $bg[ $key ] = ( $bg[ $key ] ?? 0 ) + 1; + } + arsort( $bg ); + $bigrams = array(); + foreach ( array_slice( $bg, 0, $limit, true ) as $term => $count ) { + if ( $count < 2 ) { + continue; // Only surface phrases that recur. + } + $bigrams[] = array( 'term' => $term, 'count' => $count ); + } + + return array( + 'keywords' => $keywords, + 'bigrams' => $bigrams, + 'total_words' => $total, + ); + } + + /** + * Proposes an SEO title + meta description from page content. + * + * @param array $ex Content_Extractor output. + * @param array $seo Seo_Meta output. + * @param string $target Optional target keyword. + * @param string $site_name Optional site name to append to the title. + * @return array + */ + public static function propose_meta( array $ex, array $seo, string $target = '', string $site_name = '' ): array { + $notes = array(); + + // Base title: first H1, else existing SEO title, else first heading. + $base = ''; + foreach ( $ex['headings'] as $h ) { + if ( 1 === (int) $h['level'] ) { + $base = $h['text']; + break; + } + } + if ( '' === $base ) { + $base = trim( (string) ( $seo['title'] ?? '' ) ); + } + if ( '' === $base && ! empty( $ex['headings'] ) ) { + $base = $ex['headings'][0]['text']; + } + if ( '' === $base ) { + $base = __( 'Untitled', 'emcp-tools' ); + $notes[] = __( 'No heading found — title falls back to a placeholder.', 'emcp-tools' ); + } + + $title = $base; + if ( '' !== $site_name && ( self::mb_len( $base ) + 3 + self::mb_len( $site_name ) ) <= 60 ) { + $title = $base . ' | ' . $site_name; + } + $title = self::truncate( $title, 60 ); + + // Description: first substantial text block, keyword-front-loaded. + $body = ''; + foreach ( $ex['text_blocks'] as $t ) { + if ( self::mb_len( $t['text'] ) >= 40 ) { + $body = $t['text']; + break; + } + } + if ( '' === $body && ! empty( $ex['text_blocks'] ) ) { + $body = $ex['text_blocks'][0]['text']; + } + + $desc = $body; + if ( '' !== $target && false === mb_strpos( self::mb_lower( $desc ), self::mb_lower( $target ) ) ) { + $desc = rtrim( ucfirst( $target ), '.' ) . ': ' . $desc; + $notes[] = __( 'Target keyword front-loaded into the description.', 'emcp-tools' ); + } + $desc = self::truncate( $desc, 155 ); + if ( '' === $desc ) { + $notes[] = __( 'No body text found to build a description from.', 'emcp-tools' ); + } + + return array( + 'proposed_title' => $title, + 'proposed_description' => $desc, + 'title_length' => self::mb_len( $title ), + 'description_length' => self::mb_len( $desc ), + 'notes' => $notes, + ); + } + + /** + * Builds JSON-LD structured data. + * + * @param string $type Requested type or 'auto'. + * @param array $ex Content_Extractor output. + * @param array $seo Seo_Meta output. + * @param array $business Business NAP object. + * @param array $faqs FAQ pairs. + * @param string $url Page URL. + * @return array + */ + public static function build_jsonld( string $type, array $ex, array $seo, array $business, array $faqs, string $url ): array { + $notes = array(); + $name = ''; + foreach ( $ex['headings'] as $h ) { + if ( 1 === (int) $h['level'] ) { + $name = $h['text']; + break; + } + } + if ( '' === $name ) { + $name = trim( (string) ( $seo['title'] ?? '' ) ); + } + + // Resolve 'auto'. + if ( '' === $type || 'auto' === $type ) { + if ( ! empty( $business ) ) { + $type = 'LocalBusiness'; + } elseif ( ! empty( $faqs ) ) { + $type = 'FAQPage'; + } else { + $type = 'Article'; + } + $notes[] = sprintf( /* translators: %s: type */ __( 'Auto-detected schema type: %s.', 'emcp-tools' ), $type ); + } + + switch ( $type ) { + case 'LocalBusiness': + if ( empty( $business ) ) { + $notes[] = __( 'LocalBusiness needs a business object (name/address/phone) — emitting a minimal stub.', 'emcp-tools' ); + } + $schema = array( + '@context' => 'https://schema.org', + '@type' => 'LocalBusiness', + 'name' => $business['name'] ?? $name, + ); + if ( ! empty( $business['phone'] ) ) { + $schema['telephone'] = (string) $business['phone']; + } + if ( ! empty( $business['url'] ) || '' !== $url ) { + $schema['url'] = (string) ( $business['url'] ?? $url ); + } + if ( ! empty( $business['price_range'] ) ) { + $schema['priceRange'] = (string) $business['price_range']; + } + $address = array_filter( array( + '@type' => 'PostalAddress', + 'streetAddress' => $business['street'] ?? '', + 'addressLocality' => $business['locality'] ?? '', + 'addressRegion' => $business['region'] ?? '', + 'postalCode' => $business['postal_code'] ?? '', + 'addressCountry' => $business['country'] ?? '', + ) ); + if ( count( $address ) > 1 ) { + $schema['address'] = $address; + } + break; + + case 'FAQPage': + $entities = array(); + foreach ( $faqs as $f ) { + if ( ! is_array( $f ) || empty( $f['question'] ) || empty( $f['answer'] ) ) { + continue; + } + $entities[] = array( + '@type' => 'Question', + 'name' => (string) $f['question'], + 'acceptedAnswer' => array( '@type' => 'Answer', 'text' => (string) $f['answer'] ), + ); + } + if ( empty( $entities ) ) { + $notes[] = __( 'FAQPage needs a faqs array of {question, answer} — none provided.', 'emcp-tools' ); + } + $schema = array( + '@context' => 'https://schema.org', + '@type' => 'FAQPage', + 'mainEntity' => $entities, + ); + break; + + case 'Product': + $schema = array( + '@context' => 'https://schema.org', + '@type' => 'Product', + 'name' => $name, + 'description' => (string) ( $seo['description'] ?? '' ), + ); + break; + + case 'Service': + $schema = array( + '@context' => 'https://schema.org', + '@type' => 'Service', + 'name' => $name, + 'description' => (string) ( $seo['description'] ?? '' ), + ); + break; + + case 'Article': + default: + $type = 'Article'; + $schema = array( + '@context' => 'https://schema.org', + '@type' => 'Article', + 'headline' => $name, + 'description' => (string) ( $seo['description'] ?? '' ), + ); + if ( '' !== $url ) { + $schema['mainEntityOfPage'] = $url; + } + break; + } + + $jsonld = function_exists( 'wp_json_encode' ) + ? wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT ) + : json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT ); + + return array( + 'detected_type' => $type, + 'jsonld' => is_string( $jsonld ) ? $jsonld : '', + 'insert_hint' => __( 'Insert inside a