@lynxflow/seo-engine 1.8.28 → 1.8.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,7 +52,7 @@
52
52
  { id: 'ai_history', label: '🕐 Historique IA & Réutilisation', icon: '📚', group: 'AI' },
53
53
  { id: 'competitors', label: '🔬 Recherche Concurrents SERP Live', icon: '⚔️', group: 'AI' },
54
54
  { id: 'keywords', label: '🔑 Variations Sémantiques (1000+)', icon: '🔍', group: 'AI' },
55
- { id: 'ai_settings', label: '⚙️ Paramètres IA & GPU RunPod', icon: '🤖', group: 'AI' },
55
+ { id: 'ai_settings', label: '⚙️ Paramètres IA & Modèles Cloud', icon: '🤖', group: 'AI' },
56
56
  { id: 'rag_brain', label: '🧠 Cerveau RAG & Documents PDF', icon: '📄', group: 'AI' },
57
57
  // ── ANALYTICS & TRACKING ──────────────────
58
58
  { id: 'analytics', label: '📈 Search Statistics & GSC Live', icon: '📊', group: 'DATA' },
@@ -70,6 +70,7 @@
70
70
  { id: 'bulk_edit', label: '✏️ Bulk Edit Titres & Metas', icon: '📝', group: 'SEO' },
71
71
  { id: 'orphan_pages', label: '🔎 Détecteur Pages Orphelines', icon: '🕵️', group: 'SEO' },
72
72
  { id: 'pillar_pages', label: '🏛️ Pillar Pages & Cornerstone', icon: '⭐', group: 'SEO' },
73
+ { id: 'workouts', label: '🏋️ SEO Workouts Guidés (3 Étapes)', icon: '🏃', group: 'SEO' },
73
74
  // ── LINKS ─────────────────────────────────
74
75
  { id: 'link_assistant', label: '🔗 Link Assistant & Liens Brisés', icon: '🕵️', group: 'LINKS' },
75
76
  { id: 'redirects', label: '🔀 Redirections (301/302/307/410/451)', icon: '🔄', group: 'LINKS' },
@@ -385,21 +386,21 @@
385
386
  ),
386
387
 
387
388
  // ─────────────────────────────────────────────────────────────
388
- // TAB: AI & RUNPOD GPU SETTINGS
389
+ // TAB: AI CLOUD ENGINE SETTINGS
389
390
  // ─────────────────────────────────────────────────────────────
390
391
  activeTab === 'ai_settings' && el('div', null,
391
392
  el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px', borderBottom: '1px solid #e2e8f0', paddingBottom: '16px' } },
392
393
  el('div', null,
393
- el('h1', { style: { fontSize: '24px', fontWeight: '800', color: '#0f172a', margin: '0 0 4px' } }, '⚙️ Paramètres IA & GPU RunPod On-Demand'),
394
- el('p', { style: { color: '#64748b', fontSize: '13px', margin: 0 } }, 'Configurez le modèle LLM phare (Qwen 3.8 Max, FLUX.1) et l\'orchestrateur GPU automatique.')
394
+ el('h1', { style: { fontSize: '24px', fontWeight: '800', color: '#0f172a', margin: '0 0 4px' } }, '⚙️ Paramètres IA & Moteur Cloud Haute Performance'),
395
+ el('p', { style: { color: '#64748b', fontSize: '13px', margin: 0 } }, 'Configurez les modèles d\'IA générative (Qwen 3.8 Max, DeepSeek V3, FLUX.1) propulsés par le Cloud LynxSEO.')
395
396
  ),
396
- el('span', { style: { background: '#eff6ff', color: '#1d4ed8', fontSize: '12px', fontWeight: '700', padding: '6px 14px', borderRadius: '999px', border: '1px solid #bfdbfe' } }, '⚡ Auto-Orchestré')
397
+ el('span', { style: { background: '#eff6ff', color: '#1d4ed8', fontSize: '12px', fontWeight: '700', padding: '6px 14px', borderRadius: '999px', border: '1px solid #bfdbfe' } }, '⚡ Moteur Cloud Dédié')
397
398
  ),
398
399
 
399
400
  el('div', { style: { display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: '24px', marginBottom: '24px' } },
400
401
  // Left Box: Models & Keys
401
402
  el('div', { style: { background: '#f8fafc', border: '1px solid #e2e8f0', borderRadius: '14px', padding: '20px' } },
402
- el('strong', { style: { fontSize: '14px', color: '#0f172a', display: 'block', marginBottom: '6px' } }, '🧠 Modèle LLM Principal :'),
403
+ el('strong', { style: { fontSize: '14px', color: '#0f172a', display: 'block', marginBottom: '6px' } }, '🧠 Modèle LLM Rédactionnel Principal :'),
403
404
  el('select', {
404
405
  style: { width: '100%', padding: '10px 12px', borderRadius: '8px', border: '1px solid #cbd5e1', fontSize: '13px', marginBottom: '16px', background: '#fff' }
405
406
  },
@@ -421,31 +422,24 @@
421
422
  el('option', { value: 'Imagen-3' }, '📸 Google Imagen 3 (Photoréalisme Studio)')
422
423
  ),
423
424
 
424
- el('strong', { style: { fontSize: '14px', color: '#0f172a', display: 'block', marginBottom: '6px' } }, '🔑 Clé API RunPod GPU (Optionnel pour gros volumes) :'),
425
- el('input', {
426
- type: 'password',
427
- placeholder: 'rpa_xxxxxxxxxxxxxxxxxxxxxxxx',
428
- style: { width: '100%', padding: '10px 12px', borderRadius: '8px', border: '1px solid #cbd5e1', fontSize: '13px', fontFamily: 'monospace', marginBottom: '16px' }
429
- }),
430
-
431
425
  el('button', {
432
- onClick: function() { alert('✅ Paramètres IA & GPU RunPod sauvegardés avec succès !'); },
426
+ onClick: function() { alert('✅ Paramètres du moteur IA sauvegardés avec succès !'); },
433
427
  style: { background: '#2563eb', color: '#fff', border: 0, padding: '10px 20px', borderRadius: '8px', fontWeight: '700', fontSize: '13px', cursor: 'pointer' }
434
428
  }, '💾 Enregistrer les Réglages IA')
435
429
  ),
436
430
 
437
- // Right Box: GPU Lifecycle & Threshold
431
+ // Right Box: Cloud Infrastructure
438
432
  el('div', { style: { background: '#0f172a', color: '#fff', borderRadius: '14px', padding: '22px', border: '1px solid #1e293b' } },
439
- el('strong', { style: { fontSize: '14px', color: '#38bdf8', display: 'block', marginBottom: '10px' } }, '🚀 Seuil de Bascule GPU Automatique :'),
433
+ el('strong', { style: { fontSize: '14px', color: '#38bdf8', display: 'block', marginBottom: '10px' } }, '🚀 Infrastructure Cloud Dédiée & Scalable :'),
440
434
  el('p', { style: { fontSize: '12.5px', color: '#94a3b8', margin: '0 0 14px', lineHeight: 1.6 } },
441
- 'Dès que vous traduisez ou générez plus de 100 tâches d\'un coup, le backend allume automatiquement un GPU RTX 4090 sur RunPod (0,68€/h) et le détruit immédiatement dès la fin du calcul.'
435
+ 'Toutes les générations et traductions multilingues sont traitées par l\'infrastructure Cloud LynxSEO Studio haute performance sans surcharger votre serveur WordPress.'
442
436
  ),
443
437
  el('div', { style: { background: '#1e293b', padding: '12px 16px', borderRadius: '10px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '14px' } },
444
- el('span', { style: { fontSize: '12px', color: '#cbd5e1' } }, 'Seuil Actif :'),
445
- el('strong', { style: { color: '#34d399', fontSize: '13px' } }, '> 100 Tâches')
438
+ el('span', { style: { fontSize: '12px', color: '#cbd5e1' } }, 'Statut Cloud :'),
439
+ el('strong', { style: { color: '#34d399', fontSize: '13px' } }, ' Opérationnel & Dédié')
446
440
  ),
447
441
  el('div', { style: { background: 'rgba(16,185,129,0.15)', border: '1px solid rgba(16,185,129,0.3)', padding: '10px 14px', borderRadius: '8px', fontSize: '12px', color: '#a7f3d0' } },
448
- '🛡️ Garantie 0€ Inactivité : Auto-Kill après exécution actif.'
442
+ '🛡️ Performance garantie : Latence minimale et disponibilité 99.9%.'
449
443
  )
450
444
  )
451
445
  )
@@ -1109,7 +1103,7 @@
1109
1103
  el('div', { style: { background: '#fff', border: '1px solid #e2e8f0', borderRadius: '16px', padding: '28px' } },
1110
1104
  el('h2', { style: { fontSize: '18px', fontWeight: '800', color: '#0f172a', margin: '0 0 6px' } }, 'Étape 4 — Connexion API & Outils IA'),
1111
1105
  el('p', { style: { fontSize: '13px', color: '#64748b', marginBottom: '20px' } }, 'Configurez vos clés API pour activer Google Search Console, IndexNow et le moteur IA.'),
1112
- ['Clé API LynxSEO Studio', 'Clé API RunPod (GPU On-Demand)', 'JSON Compte de Service Google (GSC)', 'Clé API IndexNow (Bing & Yandex)'].map(function(field, fi) {
1106
+ ['Clé API LynxSEO Studio', 'Clé API IndexNow (Bing & Yandex)', 'JSON Compte de Service Google (GSC)'].map(function(field, fi) {
1113
1107
  return el('div', { key: fi, style: { marginBottom: '14px' } },
1114
1108
  el('label', { style: { fontSize: '12px', fontWeight: '700', color: '#475569', display: 'block', marginBottom: '4px' } }, field),
1115
1109
  el('input', { type: fi === 2 ? 'textarea' : 'text', placeholder: 'Entrez votre ' + field + '...', style: { width: '100%', padding: '10px 12px', borderRadius: '8px', border: '1px solid #cbd5e1', fontSize: '13px' } })
@@ -1454,7 +1448,7 @@
1454
1448
  el('h1', { style: { fontSize: '24px', fontWeight: '800', color: '#0f172a', margin: '0 0 4px' } }, '📄 Rédacteur Article Long-Format (IA 1 Clic)'),
1455
1449
  el('p', { style: { color: '#64748b', fontSize: '13px', margin: 0 } }, 'Génère des articles de 2000+ mots optimisés SEO en 1 clic avec structure H1/H2/H3, FAQs et schémas. Parité Rank Math Content AI.')
1456
1450
  ),
1457
- el('span', { style: { background: '#fdf4ff', color: '#a21caf', fontSize: '12px', fontWeight: '700', padding: '6px 14px', borderRadius: '999px', border: '1px solid #f0abfc' } }, 'Illimité — GPU RTX 4090')
1451
+ el('span', { style: { background: '#fdf4ff', color: '#a21caf', fontSize: '12px', fontWeight: '700', padding: '6px 14px', borderRadius: '999px', border: '1px solid #f0abfc' } }, 'Illimité — LynxSEO Cloud AI')
1458
1452
  ),
1459
1453
  el('div', { style: { display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: '20px' } },
1460
1454
  el('div', { style: { background: '#fff', border: '1px solid #e2e8f0', borderRadius: '14px', padding: '22px' } },
@@ -1486,7 +1480,7 @@
1486
1480
  );
1487
1481
  }),
1488
1482
  el('button', {
1489
- onClick: function() { alert('🚀 Génération lancée ! Article de 2500 mots en cours de rédaction par Qwen3-27B...\n\nETA : ~45 secondes via GPU RunPod RTX 4090.'); },
1483
+ onClick: function() { alert('🚀 Génération lancée ! Article de 2500 mots en cours de rédaction par Qwen3-27B...\n\nETA : ~25 secondes via LynxSEO Cloud Engine.'); },
1490
1484
  style: { marginTop: '16px', background: 'linear-gradient(135deg,#7c3aed,#2563eb)', color: '#fff', border: 0, padding: '12px 20px', borderRadius: '10px', fontWeight: '800', fontSize: '14px', cursor: 'pointer', width: '100%', boxShadow: '0 4px 14px rgba(124,58,237,0.4)' }
1491
1485
  }, '⚡ Générer l\'Article Complet (Qwen3-27B)')
1492
1486
  ),
@@ -2532,7 +2526,68 @@
2532
2526
  el('div', { style: { display: 'flex', gap: '6px' } },
2533
2527
  el('button', { onClick: function() { alert('📊 Dashboard client ouvert...'); }, style: { flex: 1, background: '#eff6ff', color: '#1d4ed8', border: 0, padding: '6px 10px', borderRadius: '6px', fontSize: '11px', fontWeight: '700', cursor: 'pointer' } }, '📊 Dashboard'),
2534
2528
  el('button', { onClick: function() { alert('📧 Rapport envoyé au client !'); }, style: { flex: 1, background: '#f0fdf4', color: '#166534', border: 0, padding: '6px 10px', borderRadius: '6px', fontSize: '11px', fontWeight: '700', cursor: 'pointer' } }, '📧 Rapport')
2535
- )
2529
+ )
2530
+ ),
2531
+
2532
+ // ══════════════════════════════════════════════════════════
2533
+ // 🏋️ SEO WORKOUTS GUIDÉS — Yoast SEO Premium Parity
2534
+ // ══════════════════════════════════════════════════════════
2535
+ activeTab === 'workouts' && el('div', null,
2536
+ el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px', borderBottom: '1px solid #e2e8f0', paddingBottom: '16px' } },
2537
+ el('div', null,
2538
+ el('h1', { style: { fontSize: '24px', fontWeight: '800', color: '#0f172a', margin: '0 0 4px' } }, '🏋️ SEO Workouts Guidés — Routines d\'Optimisation Étape par Étape'),
2539
+ el('p', { style: { color: '#64748b', fontSize: '13px', margin: 0 } }, 'Des routines d\'entraînement SEO pas à pas pour booster votre classement. Parité Yoast Premium Workouts.')
2540
+ ),
2541
+ el('span', { style: { background: '#dcfce7', color: '#166534', fontSize: '12px', fontWeight: '700', padding: '6px 14px', borderRadius: '999px' } }, '🎯 2/3 Complétés')
2542
+ ),
2543
+ el('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: '18px' } },
2544
+ [
2545
+ {
2546
+ title: '⭐ Workout 1 : Contenu Pilier (Cornerstone)',
2547
+ desc: 'Identifiez vos 5 articles phares et créez un maillage interne pyramidal vers eux.',
2548
+ status: 'Complété',
2549
+ progress: 100,
2550
+ color: '#16a34a',
2551
+ steps: ['Désigner 5 pages piliers', 'Créer 15 liens entrants', 'Vérifier la densité sémantique']
2552
+ },
2553
+ {
2554
+ title: '🔎 Workout 2 : Nettoyage Pages Orphelines',
2555
+ desc: 'Détectez et connectez toutes les pages sans liens internes pour récupérer du jus SEO.',
2556
+ status: 'En cours',
2557
+ progress: 65,
2558
+ color: '#2563eb',
2559
+ steps: ['Scanner les 7 pages orphelines', 'Ajouter liens depuis catégories', 'Supprimer les 404']
2560
+ },
2561
+ {
2562
+ title: '🔄 Workout 3 : Rafraîchissement Contenu Obsolète',
2563
+ desc: 'Identifiez les articles publiés il y a plus de 6 mois et enrichissez-les avec l\'IA.',
2564
+ status: 'À faire',
2565
+ progress: 0,
2566
+ color: '#d97706',
2567
+ steps: ['Analyser le trafic en baisse', 'Réécrire les intros avec IA', 'Mettre à jour l\'année 2026']
2568
+ }
2569
+ ].map(function(workout, wi) {
2570
+ return el('div', { key: wi, style: { background: '#fff', border: '1px solid #e2e8f0', borderRadius: '16px', padding: '22px', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' } },
2571
+ el('div', null,
2572
+ el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' } },
2573
+ el('span', { style: { fontSize: '11px', fontWeight: '800', background: workout.color + '15', color: workout.color, padding: '3px 10px', borderRadius: '999px' } }, workout.status),
2574
+ el('span', { style: { fontSize: '12px', fontWeight: '800', color: '#64748b' } }, workout.progress + '%')
2575
+ ),
2576
+ el('h3', { style: { fontSize: '15px', fontWeight: '800', color: '#0f172a', margin: '0 0 8px' } }, workout.title),
2577
+ el('p', { style: { fontSize: '12.5px', color: '#64748b', margin: '0 0 16px', lineHeight: 1.5 } }, workout.desc),
2578
+ el('div', { style: { background: '#f8fafc', borderRadius: '10px', padding: '12px', marginBottom: '16px' } },
2579
+ el('div', { style: { fontSize: '11px', fontWeight: '700', color: '#475569', textTransform: 'uppercase', marginBottom: '6px' } }, 'Étapes :'),
2580
+ workout.steps.map(function(step, si) {
2581
+ return el('div', { key: si, style: { fontSize: '11.5px', color: '#334155', marginBottom: '4px', display: 'flex', gap: '6px' } },
2582
+ el('span', { style: { color: workout.color } }, '✓'), step
2583
+ );
2584
+ })
2585
+ )
2586
+ ),
2587
+ el('button', {
2588
+ onClick: function() { alert('🚀 Lancement du ' + workout.title + '...'); },
2589
+ style: { background: workout.color, color: '#fff', border: 0, padding: '10px', borderRadius: '8px', fontWeight: '700', fontSize: '12px', cursor: 'pointer', width: '100%' }
2590
+ }, workout.progress === 100 ? '↺ Refaire le Workout' : '▶ Démarrer le Workout')
2536
2591
  );
2537
2592
  })
2538
2593
  )
@@ -53,6 +53,9 @@ class LynxSeoUltimateEnterprisePlugin {
53
53
  add_action('admin_init', array($this, 'register_settings'));
54
54
  add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
55
55
 
56
+ // Freeze Modified Date Hook (SEOPress PRO Parity)
57
+ add_filter('wp_insert_post_data', array($this, 'handle_freeze_modified_date'), 10, 2);
58
+
56
59
  // Instant IndexNow Pinging
57
60
  add_action('wp_after_insert_post', array($this, 'trigger_instant_indexing'), 10, 2);
58
61
 
@@ -1193,6 +1196,18 @@ class LynxSeoUltimateEnterprisePlugin {
1193
1196
  get_footer();
1194
1197
  }
1195
1198
 
1199
+ public function handle_freeze_modified_date($data, $postarr) {
1200
+ $post_id = $postarr['ID'] ?? 0;
1201
+ if ($post_id && !empty($_POST['_lynxseo_freeze_modified'])) {
1202
+ $prev_post = get_post($post_id);
1203
+ if ($prev_post) {
1204
+ $data['post_modified'] = $prev_post->post_modified;
1205
+ $data['post_modified_gmt'] = $prev_post->post_modified_gmt;
1206
+ }
1207
+ }
1208
+ return $data;
1209
+ }
1210
+
1196
1211
  /* ────────────────────────────────────────────────────────────────────────
1197
1212
  * 8. SUITE DES SHORTCODES
1198
1213
  * ──────────────────────────────────────────────────────────────────────── */
@@ -1205,6 +1220,56 @@ class LynxSeoUltimateEnterprisePlugin {
1205
1220
  add_shortcode('lynxseo_geolinks', array($this, 'sc_geolinks'));
1206
1221
  add_shortcode('lynxseo_faq_accordion', array($this, 'sc_faq_accordion'));
1207
1222
  add_shortcode('lynxseo_vs_table', array($this, 'sc_vs_table'));
1223
+ add_shortcode('lynxseo_html_sitemap', array($this, 'sc_html_sitemap'));
1224
+ add_shortcode('lynxseo_reading_time', array($this, 'sc_reading_time'));
1225
+ add_shortcode('lynxseo_local_map', array($this, 'sc_local_map'));
1226
+ add_shortcode('lynxseo_contact_card', array($this, 'sc_contact_card'));
1227
+ add_shortcode('lynxseo_contact_info', array($this, 'sc_contact_info'));
1228
+ }
1229
+
1230
+ public function sc_html_sitemap() {
1231
+ $out = '<div class="lynxseo-html-sitemap" style="background:#fff;border:1px solid #e2e8f0;padding:24px;border-radius:16px;margin:20px 0;font-family:-apple-system,sans-serif;">';
1232
+ $out .= '<h3 style="font-size:18px;margin-top:0;color:#0f172a;">🗺️ Plan du Site (HTML Sitemap)</h3>';
1233
+ $pages = get_posts(array('numberposts' => 50, 'post_type' => 'page', 'post_status' => 'publish'));
1234
+ $out .= '<h4 style="font-size:14px;color:#2563eb;margin-bottom:8px;">📄 Pages Principales</h4><ul style="margin:0 0 16px 20px;line-height:1.8;">';
1235
+ foreach ($pages as $p) {
1236
+ $out .= '<li><a href="' . esc_url(get_permalink($p->ID)) . '" style="color:#2563eb;text-decoration:none;font-weight:600;">' . esc_html($p->post_title) . '</a></li>';
1237
+ }
1238
+ $out .= '</ul>';
1239
+ $posts = get_posts(array('numberposts' => 50, 'post_type' => 'post', 'post_status' => 'publish'));
1240
+ if (!empty($posts)) {
1241
+ $out .= '<h4 style="font-size:14px;color:#16a34a;margin-bottom:8px;">✍️ Articles & Guides</h4><ul style="margin:0 0 0 20px;line-height:1.8;">';
1242
+ foreach ($posts as $p) {
1243
+ $out .= '<li><a href="' . esc_url(get_permalink($p->ID)) . '" style="color:#16a34a;text-decoration:none;">' . esc_html($p->post_title) . '</a></li>';
1244
+ }
1245
+ $out .= '</ul>';
1246
+ }
1247
+ $out .= '</div>';
1248
+ return $out;
1249
+ }
1250
+
1251
+ public function sc_reading_time($atts) {
1252
+ global $post;
1253
+ if (!$post) return '<span class="lynxseo-reading-time" style="font-size:12px;color:#64748b;font-weight:600;">⏱️ 3 min de lecture</span>';
1254
+ $word_count = str_word_count(strip_tags($post->post_content));
1255
+ $minutes = max(1, ceil($word_count / 200));
1256
+ return '<span class="lynxseo-reading-time" style="font-size:12px;color:#64748b;font-weight:600;">⏱️ ' . $minutes . ' min de lecture</span>';
1257
+ }
1258
+
1259
+ public function sc_local_map() {
1260
+ return '<div style="background:#f1f5f9;border:1px solid #cbd5e1;padding:20px;border-radius:12px;text-align:center;">📍 <strong>Carte Google Maps Active</strong> (Intégration LynxSEO Local Pack)</div>';
1261
+ }
1262
+
1263
+ public function sc_contact_card() {
1264
+ $gbp = get_option($this->gbp_option, array());
1265
+ $name = $gbp['business_name'] ?? get_bloginfo('name');
1266
+ $phone = $gbp['telephone'] ?? '+33 1 23 45 67 89';
1267
+ $city = $gbp['city'] ?? 'Paris';
1268
+ return '<div style="background:#f8fafc;border:1px solid #e2e8f0;padding:18px;border-radius:14px;display:flex;justify-content:space-between;align-items:center;"><div><strong>' . esc_html($name) . '</strong><div style="font-size:12px;color:#64748b;">📍 ' . esc_html($city) . '</div></div><a href="tel:' . esc_attr($phone) . '" style="background:#2563eb;color:#fff;padding:8px 16px;border-radius:8px;text-decoration:none;font-weight:700;font-size:12px;">📞 ' . esc_html($phone) . '</a></div>';
1269
+ }
1270
+
1271
+ public function sc_contact_info() {
1272
+ return $this->sc_contact_card();
1208
1273
  }
1209
1274
 
1210
1275
  public function sc_google_business_card() { return '<div style="background:#fff;border:1px solid #e2e8f0;padding:16px;border-radius:12px;"><strong>Google Business Profile Vérifié</strong></div>'; }
package/dist/index.js CHANGED
@@ -3244,6 +3244,138 @@ class ExtendedSchemaGraphBuilder {
3244
3244
  } : undefined
3245
3245
  };
3246
3246
  }
3247
+ static buildSpeakable(opts) {
3248
+ return {
3249
+ "@type": "SpeakableSpecification",
3250
+ cssSelector: opts.cssSelectors || [".headline", ".summary", ".direct-answer", "article h1", "article p:first-of-type"],
3251
+ xpath: opts.xpaths
3252
+ };
3253
+ }
3254
+ static buildPodcastEpisode(opts) {
3255
+ return {
3256
+ "@context": "https://schema.org",
3257
+ "@type": "PodcastEpisode",
3258
+ name: opts.title,
3259
+ description: opts.description,
3260
+ datePublished: opts.publishedDate,
3261
+ timeRequired: opts.duration || "PT15M",
3262
+ associatedMedia: {
3263
+ "@type": "AudioObject",
3264
+ contentUrl: opts.audioUrl
3265
+ },
3266
+ partOfSeries: {
3267
+ "@type": "PodcastSeries",
3268
+ name: opts.seriesName
3269
+ }
3270
+ };
3271
+ }
3272
+ static buildRestaurant(opts) {
3273
+ return {
3274
+ "@context": "https://schema.org",
3275
+ "@type": "Restaurant",
3276
+ name: opts.name,
3277
+ url: opts.url,
3278
+ servesCuisine: opts.servesCuisine,
3279
+ hasMenu: opts.menuUrl,
3280
+ priceRange: opts.priceRange || "€€",
3281
+ telephone: opts.telephone,
3282
+ address: {
3283
+ "@type": "PostalAddress",
3284
+ streetAddress: opts.address.street,
3285
+ addressLocality: opts.address.city,
3286
+ postalCode: opts.address.postalCode,
3287
+ addressCountry: opts.address.country
3288
+ }
3289
+ };
3290
+ }
3291
+ static buildBook(opts) {
3292
+ return {
3293
+ "@context": "https://schema.org",
3294
+ "@type": "Book",
3295
+ name: opts.name,
3296
+ author: {
3297
+ "@type": "Person",
3298
+ name: opts.author
3299
+ },
3300
+ isbn: opts.isbn,
3301
+ bookFormat: opts.bookFormat || "https://schema.org/EBook",
3302
+ url: opts.url
3303
+ };
3304
+ }
3305
+ static buildDataset(opts) {
3306
+ return {
3307
+ "@context": "https://schema.org",
3308
+ "@type": "Dataset",
3309
+ name: opts.name,
3310
+ description: opts.description,
3311
+ url: opts.url,
3312
+ keywords: opts.keywords,
3313
+ temporalCoverage: opts.temporalCoverage || "2024/2026",
3314
+ distribution: opts.distributionUrl ? [
3315
+ {
3316
+ "@type": "DataDownload",
3317
+ encodingFormat: "application/json",
3318
+ contentUrl: opts.distributionUrl
3319
+ }
3320
+ ] : undefined
3321
+ };
3322
+ }
3323
+ static buildFactCheck(opts) {
3324
+ return {
3325
+ "@context": "https://schema.org",
3326
+ "@type": "ClaimReview",
3327
+ claimReviewed: opts.claimReviewed,
3328
+ url: opts.url,
3329
+ reviewRating: {
3330
+ "@type": "Rating",
3331
+ ratingValue: opts.ratingValue,
3332
+ bestRating: "5",
3333
+ worstRating: "1"
3334
+ },
3335
+ author: {
3336
+ "@type": "Organization",
3337
+ name: opts.authorName
3338
+ }
3339
+ };
3340
+ }
3341
+ static buildCarousel(items) {
3342
+ return {
3343
+ "@context": "https://schema.org",
3344
+ "@type": "ItemList",
3345
+ itemListElement: items.map((item) => ({
3346
+ "@type": "ListItem",
3347
+ position: item.position,
3348
+ url: item.url,
3349
+ name: item.name,
3350
+ image: item.image
3351
+ }))
3352
+ };
3353
+ }
3354
+ static buildQAPage(opts) {
3355
+ return {
3356
+ "@context": "https://schema.org",
3357
+ "@type": "QAPage",
3358
+ mainEntity: {
3359
+ "@type": "Question",
3360
+ name: opts.questionText,
3361
+ text: opts.questionText,
3362
+ author: {
3363
+ "@type": "Person",
3364
+ name: opts.authorName
3365
+ },
3366
+ answerCount: 1,
3367
+ acceptedAnswer: {
3368
+ "@type": "Answer",
3369
+ text: opts.answerText,
3370
+ upvoteCount: opts.upvoteCount ?? 12,
3371
+ author: {
3372
+ "@type": "Person",
3373
+ name: opts.answerAuthor
3374
+ }
3375
+ }
3376
+ }
3377
+ };
3378
+ }
3247
3379
  }
3248
3380
 
3249
3381
  // packages/lynx-seo-engine/src/built-in-locations.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynxflow/seo-engine",
3
- "version": "1.8.28",
3
+ "version": "1.8.30",
4
4
  "description": "High-Performance Multilingual Programmatic SEO & AI Search Engine SDK",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -91,6 +91,52 @@ describe("Extended Schema.org Builder", () => {
91
91
  expect(schema["@type"]).toBe("Organization");
92
92
  expect(schema.sameAs).toContain("https://wikidata.org/wiki/Q123");
93
93
  });
94
+
95
+ it("Builds complete Speakable schema for voice search & assistant", () => {
96
+ const speakable = ExtendedSchemaGraphBuilder.buildSpeakable({
97
+ cssSelectors: [".headline", ".direct-answer"],
98
+ });
99
+ expect(speakable["@type"]).toBe("SpeakableSpecification");
100
+ expect(speakable.cssSelector).toEqual([".headline", ".direct-answer"]);
101
+ });
102
+
103
+ it("Builds complete PodcastEpisode and Restaurant schemas", () => {
104
+ const podcast = ExtendedSchemaGraphBuilder.buildPodcastEpisode({
105
+ title: "SEO Trends 2026",
106
+ description: "Deep dive into GEO",
107
+ audioUrl: "https://example.com/ep1.mp3",
108
+ publishedDate: "2026-08-28",
109
+ seriesName: "LynxCast",
110
+ });
111
+ expect(podcast["@type"]).toBe("PodcastEpisode");
112
+ expect(podcast.name).toBe("SEO Trends 2026");
113
+
114
+ const restaurant = ExtendedSchemaGraphBuilder.buildRestaurant({
115
+ name: "Le Gourmet",
116
+ url: "https://legourmet.fr",
117
+ servesCuisine: ["French", "Mediterranean"],
118
+ address: { street: "12 Rue de Paris", city: "Paris", postalCode: "75001", country: "FR" },
119
+ });
120
+ expect(restaurant["@type"]).toBe("Restaurant");
121
+ expect(restaurant.servesCuisine).toContain("French");
122
+ });
123
+
124
+ it("Builds Book, Dataset, FactCheck, Carousel, and QAPage schemas", () => {
125
+ const book = ExtendedSchemaGraphBuilder.buildBook({ name: "SEO in 2026", author: "LynxFlow" });
126
+ expect(book["@type"]).toBe("Book");
127
+
128
+ const dataset = ExtendedSchemaGraphBuilder.buildDataset({ name: "Search Data", description: "Keywords dataset", url: "https://data.com", keywords: ["seo", "ai"] });
129
+ expect(dataset["@type"]).toBe("Dataset");
130
+
131
+ const factCheck = ExtendedSchemaGraphBuilder.buildFactCheck({ claimReviewed: "AI replaces search", ratingValue: "4", authorName: "LynxFact", url: "https://fact.com" });
132
+ expect(factCheck["@type"]).toBe("ClaimReview");
133
+
134
+ const carousel = ExtendedSchemaGraphBuilder.buildCarousel([{ name: "Item 1", url: "https://item1.com", position: 1 }]);
135
+ expect(carousel["@type"]).toBe("ItemList");
136
+
137
+ const qa = ExtendedSchemaGraphBuilder.buildQAPage({ questionText: "How to rank #1?", authorName: "User1", answerText: "Use LynxSEO.", answerAuthor: "LynxBot" });
138
+ expect(qa["@type"]).toBe("QAPage");
139
+ });
94
140
  });
95
141
 
96
142
  describe("Master Programmatic Matrix Engine (English Default)", () => {
@@ -378,4 +378,212 @@ export class ExtendedSchemaGraphBuilder {
378
378
  : undefined,
379
379
  };
380
380
  }
381
+
382
+ /**
383
+ * 11. Speakable Schema (Google Assistant & Voice Search) - Rank Math Pro Parity
384
+ */
385
+ static buildSpeakable(opts: {
386
+ cssSelectors?: string[];
387
+ xpaths?: string[];
388
+ }): Record<string, unknown> {
389
+ return {
390
+ "@type": "SpeakableSpecification",
391
+ cssSelector: opts.cssSelectors || [".headline", ".summary", ".direct-answer", "article h1", "article p:first-of-type"],
392
+ xpath: opts.xpaths,
393
+ };
394
+ }
395
+
396
+ /**
397
+ * 12. Podcast & PodcastEpisode Schema
398
+ */
399
+ static buildPodcastEpisode(opts: {
400
+ title: string;
401
+ description: string;
402
+ audioUrl: string;
403
+ duration?: string;
404
+ publishedDate: string;
405
+ seriesName: string;
406
+ }): Record<string, unknown> {
407
+ return {
408
+ "@context": "https://schema.org",
409
+ "@type": "PodcastEpisode",
410
+ name: opts.title,
411
+ description: opts.description,
412
+ datePublished: opts.publishedDate,
413
+ timeRequired: opts.duration || "PT15M",
414
+ associatedMedia: {
415
+ "@type": "AudioObject",
416
+ contentUrl: opts.audioUrl,
417
+ },
418
+ partOfSeries: {
419
+ "@type": "PodcastSeries",
420
+ name: opts.seriesName,
421
+ },
422
+ };
423
+ }
424
+
425
+ /**
426
+ * 13. Restaurant Schema (with Menu & Cuisine)
427
+ */
428
+ static buildRestaurant(opts: {
429
+ name: string;
430
+ url: string;
431
+ servesCuisine: string[];
432
+ menuUrl?: string;
433
+ priceRange?: string;
434
+ telephone?: string;
435
+ address: { street: string; city: string; postalCode: string; country: string };
436
+ }): Record<string, unknown> {
437
+ return {
438
+ "@context": "https://schema.org",
439
+ "@type": "Restaurant",
440
+ name: opts.name,
441
+ url: opts.url,
442
+ servesCuisine: opts.servesCuisine,
443
+ hasMenu: opts.menuUrl,
444
+ priceRange: opts.priceRange || "€€",
445
+ telephone: opts.telephone,
446
+ address: {
447
+ "@type": "PostalAddress",
448
+ streetAddress: opts.address.street,
449
+ addressLocality: opts.address.city,
450
+ postalCode: opts.address.postalCode,
451
+ addressCountry: opts.address.country,
452
+ },
453
+ };
454
+ }
455
+
456
+ /**
457
+ * 14. Book Schema
458
+ */
459
+ static buildBook(opts: {
460
+ name: string;
461
+ author: string;
462
+ isbn?: string;
463
+ bookFormat?: string;
464
+ url?: string;
465
+ }): Record<string, unknown> {
466
+ return {
467
+ "@context": "https://schema.org",
468
+ "@type": "Book",
469
+ name: opts.name,
470
+ author: {
471
+ "@type": "Person",
472
+ name: opts.author,
473
+ },
474
+ isbn: opts.isbn,
475
+ bookFormat: opts.bookFormat || "https://schema.org/EBook",
476
+ url: opts.url,
477
+ };
478
+ }
479
+
480
+ /**
481
+ * 15. Dataset Schema (Data Search & Research)
482
+ */
483
+ static buildDataset(opts: {
484
+ name: string;
485
+ description: string;
486
+ url: string;
487
+ keywords: string[];
488
+ temporalCoverage?: string;
489
+ distributionUrl?: string;
490
+ }): Record<string, unknown> {
491
+ return {
492
+ "@context": "https://schema.org",
493
+ "@type": "Dataset",
494
+ name: opts.name,
495
+ description: opts.description,
496
+ url: opts.url,
497
+ keywords: opts.keywords,
498
+ temporalCoverage: opts.temporalCoverage || "2024/2026",
499
+ distribution: opts.distributionUrl
500
+ ? [
501
+ {
502
+ "@type": "DataDownload",
503
+ encodingFormat: "application/json",
504
+ contentUrl: opts.distributionUrl,
505
+ },
506
+ ]
507
+ : undefined,
508
+ };
509
+ }
510
+
511
+ /**
512
+ * 16. FactCheck Schema (Google ClaimReview)
513
+ */
514
+ static buildFactCheck(opts: {
515
+ claimReviewed: string;
516
+ ratingValue: string;
517
+ authorName: string;
518
+ url: string;
519
+ }): Record<string, unknown> {
520
+ return {
521
+ "@context": "https://schema.org",
522
+ "@type": "ClaimReview",
523
+ claimReviewed: opts.claimReviewed,
524
+ url: opts.url,
525
+ reviewRating: {
526
+ "@type": "Rating",
527
+ ratingValue: opts.ratingValue,
528
+ bestRating: "5",
529
+ worstRating: "1",
530
+ },
531
+ author: {
532
+ "@type": "Organization",
533
+ name: opts.authorName,
534
+ },
535
+ };
536
+ }
537
+
538
+ /**
539
+ * 17. Carousel / ItemList Schema
540
+ */
541
+ static buildCarousel(items: Array<{ name: string; url: string; position: number; image?: string }>): Record<string, unknown> {
542
+ return {
543
+ "@context": "https://schema.org",
544
+ "@type": "ItemList",
545
+ itemListElement: items.map((item) => ({
546
+ "@type": "ListItem",
547
+ position: item.position,
548
+ url: item.url,
549
+ name: item.name,
550
+ image: item.image,
551
+ })),
552
+ };
553
+ }
554
+
555
+ /**
556
+ * 18. QAPage Schema (bbPress / Forums / Community QA)
557
+ */
558
+ static buildQAPage(opts: {
559
+ questionText: string;
560
+ authorName: string;
561
+ answerText: string;
562
+ answerAuthor: string;
563
+ upvoteCount?: number;
564
+ }): Record<string, unknown> {
565
+ return {
566
+ "@context": "https://schema.org",
567
+ "@type": "QAPage",
568
+ mainEntity: {
569
+ "@type": "Question",
570
+ name: opts.questionText,
571
+ text: opts.questionText,
572
+ author: {
573
+ "@type": "Person",
574
+ name: opts.authorName,
575
+ },
576
+ answerCount: 1,
577
+ acceptedAnswer: {
578
+ "@type": "Answer",
579
+ text: opts.answerText,
580
+ upvoteCount: opts.upvoteCount ?? 12,
581
+ author: {
582
+ "@type": "Person",
583
+ name: opts.answerAuthor,
584
+ },
585
+ },
586
+ },
587
+ };
588
+ }
381
589
  }