From 38c0c5031ec803d1723e9ef3d8264bae26b48ad4 Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Thu, 14 May 2026 19:58:48 +0200 Subject: [PATCH] =?UTF-8?q?feat(pages):=20generate=20Phases=208-11=20?= =?UTF-8?q?=E2=80=94=20Technology=20+=20Editorial=20+=20Forms=20+=20Home?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the site to its full programmatic page count : 24 EN + 24 FR pages (48 total), all wired into the WPML CTE workflow established in the previous commit. Each phase reuses tools/_av-bricks-shared.php builders, adds its own filter('av_fr_translation_dict') to extend the FR dictionary, and ships a seed-FR mu-plugin for the initial WPML trid linking. ## Phase 8 — Technology (tools/av-generate-technology.php + seed) Hub /technology/ + 6 pillar pages (vr-hardware, software-ai, weapons-tracking, scenarios, instructor-cockpit, after-action-review). Hub uses a 6-card grid (3+3 layout — added card_count=6 case in av_build_cards_section). EN : 87-93. FR : 94-100. ## Phase 9 — Editorial (tools/av-generate-editorial.php + seed) 5 top-level pages : /why-asterion/, /about/, /customers/, /trust/, /partners/. Required adding empty-parent-path support to av_upsert_detail_page() so pages can be created at the site root (no hub). EN : 107-111. FR : 112-116. ## Phase 10 — Forms (tools/av-generate-forms.php + seed) 4 conversion pages : /request-demo/, /request-te-kit/, /request-quote/, /contact/. Custom page builder (not the shared detail-page pattern) : hero + 2-column section (Bricks `form` element on left, promise list on right) + optional shortcuts row (Contact), eligibility list (T&E Kit), FAQ accordion (Demo). Form actions=email, emailTo=hello@asterionvr.com. Field labels, select options, FAQ Q&A all in the FR dict. EN : 122-125. FR : 126-129. ## Phase 11 — Home page (tools/av-generate-home.php + seed) Custom-written single page (no data file). Sections : hero + 3-pillar cards (Operational reality / Modular / Sovereign) + 4 Industries cards + 4 Solutions cards + 4-stat numbers band + Why-Asterion teaser CTA + Customers teaser CTA + custom navy closer. Set as WP static front via update_option(show_on_front=page, page_on_front=ID). EN : 134. FR : 135. ## tools/_av-bricks-shared.php - av_upsert_detail_page() now accepts empty `$parent_path` → top-level page - av_build_cards_section() handles card_count=6 → 3-per-row layout Co-Authored-By: Claude Opus 4.7 (1M context) --- tools/_av-bricks-shared.php | 17 +- tools/av-generate-editorial.php | 172 +++++++++ tools/av-generate-forms.php | 546 +++++++++++++++++++++++++++ tools/av-generate-home.php | 292 ++++++++++++++ tools/av-generate-technology.php | 306 +++++++++++++++ tools/av-translate-editorial-fr.php | 40 ++ tools/av-translate-forms-fr.php | 34 ++ tools/av-translate-home-fr.php | 40 ++ tools/av-translate-technology-fr.php | 59 +++ 9 files changed, 1500 insertions(+), 6 deletions(-) create mode 100644 tools/av-generate-editorial.php create mode 100644 tools/av-generate-forms.php create mode 100644 tools/av-generate-home.php create mode 100644 tools/av-generate-technology.php create mode 100644 tools/av-translate-editorial-fr.php create mode 100644 tools/av-translate-forms-fr.php create mode 100644 tools/av-translate-home-fr.php create mode 100644 tools/av-translate-technology-fr.php diff --git a/tools/_av-bricks-shared.php b/tools/_av-bricks-shared.php index 43976a1..1b0aea2 100644 --- a/tools/_av-bricks-shared.php +++ b/tools/_av-bricks-shared.php @@ -467,11 +467,11 @@ function av_build_cards_section($slug, $i, $section, $bg = 'var(--color-bg-subtl $flex_desktop = 'calc((100% - 1.5rem) / 2)'; } elseif ($card_count === 3) { $flex_desktop = 'calc((100% - 3rem) / 3)'; - } elseif ($card_count === 5) { - // 5 cards : we wrap them as 3 + 2 — set flex-basis for 3-up + } elseif ($card_count === 5 || $card_count === 6) { + // 5 cards : 3 + 2 — 6 cards : 3 + 3. Both wrap cleanly at 3-per-row. $flex_desktop = 'calc((100% - 3rem) / 3)'; } else { - // default 4 cards/row (and 6 → 4 + 2 wrapped) + // default 4 cards/row (and 7+ → 4 + N wrapped) $flex_desktop = 'calc((100% - 4.5rem) / 4)'; } @@ -905,9 +905,14 @@ function av_write_bricks_page_elements($page_id, $elements) { * Returns ['id' => int, 'action' => 'created'|'updated', 'element_count' => int, 'stored_len' => int]. */ function av_upsert_detail_page($slug, $parent_path, $d) { - $existing = get_page_by_path("$parent_path/$slug"); - $parent = get_page_by_path($parent_path); - $parent_id = $parent ? $parent->ID : 0; + // $parent_path empty/null/'' → top-level page (no parent) + $page_path = $parent_path ? "$parent_path/$slug" : $slug; + $existing = get_page_by_path($page_path); + $parent_id = 0; + if ($parent_path) { + $parent = get_page_by_path($parent_path); + $parent_id = $parent ? $parent->ID : 0; + } if ($existing) { wp_update_post([ diff --git a/tools/av-generate-editorial.php b/tools/av-generate-editorial.php new file mode 100644 index 0000000..3f7e510 --- /dev/null +++ b/tools/av-generate-editorial.php @@ -0,0 +1,172 @@ + 'Pourquoi Asterion', + 'A platform built around the operator. Not the other way around.' + => "Une plateforme construite autour de l'opérateur. Pas l'inverse.", + "Asterion VR was founded in 2016 to solve one specific problem: security forces need more repetition than the ranges, ammunition budgets, and exercise calendars allow. Ten years later, the platform we built does exactly that \u{2014} at every scale, in every environment, for every role." + => "Asterion VR a été fondé en 2016 pour résoudre un problème précis : les forces de sécurité ont besoin de plus de répétition que ce que les stands, les budgets munitions et les calendriers d'exercice permettent. Dix ans plus tard, la plateforme que nous avons construite fait exactement cela \u{2014} à toute échelle, dans tout environnement, pour tout rôle.", + + 'Read our customer stories' => 'Lisez nos retours clients', + 'Meet the team' => "Rencontrez l'équipe", + + 'What makes us different' => 'Ce qui nous différencie', + 'Six commitments engineered into the platform.' => 'Six engagements gravés dans la plateforme.', + + 'French-engineered, EU-sovereign' => 'Conçu en France, souverain européen', + 'Designed and produced in France. Software developed in our French studio. Operates fully offline. No data leaves your network. No foreign cloud dependency. No ITAR exposure. GDPR-native by default.' + => "Conçu et produit en France. Logiciel développé dans notre studio français. Fonctionne entièrement hors ligne. Aucune donnée ne quitte votre réseau. Aucune dépendance à un cloud étranger. Aucune exposition ITAR. RGPD-natif par défaut.", + 'Modular from day one' => 'Modulaire dès le premier jour', + 'MY PROSERVE, FLEX, ACADEMY \u{2014} same software, same scenarios, same instructor tools, three deployment formats. Start solo. Add operators. Scale to a full academy. Your investment never resets.' + => "MY PROSERVE, FLEX, ACADEMY \u{2014} même logiciel, mêmes scénarios, mêmes outils instructeur, trois formats de déploiement. Commencez seul. Ajoutez des opérateurs. Passez à l'académie complète. Votre investissement ne repart jamais à zéro.", + 'Real-recoil weapon integration' => 'Intégration d\'armes à recul réel', + 'Train with the weapon you carry. Our Universal Weapon Digitizer attaches to gas-blowback replicas, CO2 replicas, or real service weapons firing blanks. Recoil is real. Tracking survives recoil. Muscle memory transfers.' + => "Entraînez-vous avec l'arme que vous portez. Notre Universal Weapon Digitizer s'attache aux répliques gas-blowback, CO2, ou aux vraies armes de service à blanc. Le recul est réel. Le tracking survit au recul. La mémoire musculaire transfère.", + 'Behavior, not scripts' => 'Du comportement, pas du script', + 'AI civilians and adversaries with vision, hearing, and configurable personality. Same starting conditions, different outcomes. Trainees train against decisions, not pattern recognition.' + => "Civils et adversaires IA avec vision, ouïe et personnalité configurable. Mêmes conditions de départ, issues différentes. Les stagiaires s'entraînent contre des décisions, pas la reconnaissance de patterns.", + 'Instructor-first, not trainee-first' => 'Instructeur d\'abord, stagiaire ensuite', + 'Most VR products are built for the headset wearer. We built ours for the instructor \u{2014} because the instructor is the one who turns immersion into learning.' + => "La plupart des produits VR sont construits pour celui qui porte le casque. Nous avons construit le nôtre pour l'instructeur \u{2014} car c'est l'instructeur qui transforme l'immersion en apprentissage.", + 'AAR redefined' => 'L\'AAR redéfini', + 'Frame-level replay. Per-trainee statistics. Unit-level dashboards. Comparable across time. Exportable to your reporting systems. Every drill ends measurable.' + => "Rejeu image par image. Statistiques par stagiaire. Tableaux de bord par unité. Comparable dans le temps. Exportable vers vos systèmes de reporting. Chaque exercice se termine mesurable.", + + 'Where we come from' => "D'où nous venons", + 'The thread of Ariadne.' => "Le fil d'Ariane.", + "Asterion is the original Greek name of the Minotaur \u{2014} the creature that lived at the heart of Daedalus's labyrinth. He was confronted by Theseus, who survived by training, intelligence, and the thread of Ariadne. The name we carry is a reminder: in the labyrinth of contemporary security operations, training is the thread that brings operators home." + => "Asterion est le nom grec d'origine du Minotaure \u{2014} la créature qui vivait au c\u{153}ur du labyrinthe de Dédale. Il fut affronté par Thésée, qui survécut grâce à l'entraînement, l'intelligence et le fil d'Ariane. Le nom que nous portons est un rappel : dans le labyrinthe des opérations de sécurité contemporaines, l'entraînement est le fil qui ramène les opérateurs chez eux.", + + 'The numbers' => 'Les chiffres', + 'Asterion VR in figures.' => 'Asterion VR en chiffres.', + + /* ============================ About ============================ */ + 'About' => 'À propos', + 'Built in France, for forces that train where it counts.' + => "Construit en France, pour les forces qui s'entraînent là où ça compte.", + "Asterion VR was founded in 2016 by J\u{E9}r\u{F4}me Foucher and a small team convinced of one thing: that the gap between regulation and reality in security forces training would not close on its own." + => "Asterion VR a été fondé en 2016 par Jérôme Foucher et une petite équipe convaincue d'une chose : que l'écart entre la réglementation et la réalité dans la formation des forces de sécurité ne se refermerait pas tout seul.", + + 'The conviction' => 'La conviction', + 'In 2016, we looked at the numbers.' => 'En 2016, nous avons regardé les chiffres.', + 'French police regulation required 90 cartridges per officer per year. The actual median was between 10 and 15. Tactical errors under stress increased by 40% compared to baseline conditions. 42% of intervention errors were linked to insufficient realistic preparation. The infrastructure to fix this \u{2014} saturated ranges, expensive live exercises, finite instructor time \u{2014} was not going to expand. Something had to change in the methodology. VR was the obvious answer. But the VR available in 2016 was built for entertainment. The recoil was fake. The civilians were on rails. The instructor saw what the trainee saw. The data was nowhere. We started over.' + => "La réglementation police française exigeait 90 cartouches par agent et par an. La médiane réelle était entre 10 et 15. Les erreurs tactiques sous stress augmentaient de 40% par rapport aux conditions de base. 42% des erreurs d'intervention étaient liées à une préparation réaliste insuffisante. L'infrastructure pour y remédier \u{2014} stands saturés, exercices réels coûteux, temps instructeur limité \u{2014} n'allait pas s'étendre. Quelque chose devait changer dans la méthode. La VR était la réponse évidente. Mais la VR disponible en 2016 était construite pour le divertissement. Le recul était factice. Les civils sur rails. L'instructeur voyait ce que voyait le stagiaire. Les données nulle part. Nous avons recommencé.", + + 'Ten years of one focus' => 'Dix ans, une seule mission', + 'Security forces. Nothing else.' => 'Forces de sécurité. Rien d\'autre.', + 'Asterion has never built a single product for entertainment, education, or industry. Our entire roadmap, from day one, has been the training of security forces. Every iteration of our weapon tracker, every line of code in our AI engine, every environment in our scenario library was specified by serving or former operators.' + => "Asterion n'a jamais construit le moindre produit pour le divertissement, l'éducation ou l'industrie. Notre feuille de route entière, dès le premier jour, c'est la formation des forces de sécurité. Chaque itération de notre tracker d'arme, chaque ligne de code de notre moteur IA, chaque environnement de notre bibliothèque de scénarios a été spécifié par des opérateurs en service ou anciens.", + + 'The team' => 'L\'équipe', + 'Engineers, operators, designers.' => 'Ingénieurs, opérateurs, designers.', + + 'Asterion in your network' => 'Asterion dans votre réseau', + 'Members and partners.' => 'Membres et partenaires.', + 'We are members of the French defense innovation cluster, the French Tech ecosystem, and active partners with HTC VIVE Business and Unreal Engine programmes for defense and public sector applications. We work with a network of distributors and integrators across Europe, the Middle East, and Asia.' + => "Nous sommes membres du cluster français d'innovation défense, de l'écosystème French Tech, et partenaires actifs des programmes HTC VIVE Business et Unreal Engine pour les applications défense et secteur public. Nous travaillons avec un réseau de distributeurs et d'intégrateurs en Europe, au Moyen-Orient et en Asie.", + + /* ============================ Customers ============================ */ + 'Customers & Case Studies' => 'Clients & retours d\'expérience', + 'Customers' => 'Clients', + 'Real deployments. Measured results.' => 'Déploiements réels. Résultats mesurés.', + 'PROSERVE is in active service with police, gendarmerie, and tactical units across Europe. Some are public references. Some operate under nondisclosure agreements. The case studies below show \u{2014} under their own names, with their own metrics, in their own words \u{2014} how they train with us.' + => "PROSERVE est en service actif auprès de polices, gendarmeries et unités tactiques à travers l'Europe. Certaines sont des références publiques. Certaines opèrent sous accord de confidentialité. Les retours d'expérience ci-dessous montrent \u{2014} sous leur nom, avec leurs propres métriques, dans leurs propres mots \u{2014} comment ils s'entraînent avec nous.", + + 'Become the next case study' => 'Devenez le prochain retour d\'expérience', + + 'Featured case studies' => 'Retours d\'expérience phares', + 'Sample deployments \u{2014} to be detailed in /customers/[slug]/.' + => "Déploiements types \u{2014} à détailler dans /customers/[slug]/.", + 'National police academy, France' => 'École nationale de police, France', + 'Continuous marksmanship training across 12 stations. 1500 officers trained in the first 18 months. Per-officer median annual cartridge count rose from 14 to 117 (live + simulated). Tactical-error rate on benchmark drill dropped 35%.' + => "Entraînement tir continu sur 12 stations. 1500 agents formés dans les 18 premiers mois. Le nombre médian annuel de cartouches par agent est passé de 14 à 117 (réel + simulé). Le taux d'erreur tactique sur drill de référence a baissé de 35%.", + 'Regional gendarmerie tactical unit' => 'Unité tactique de gendarmerie régionale', + 'PROSERVE FLEX deployed for forward training during multi-week field rotations. Hostage-rescue drill repetition multiplied by 8 over 6 months. Reaction time under contact improved 22% on benchmark drill.' + => "PROSERVE FLEX déployé pour entraînement avancé pendant des rotations terrain pluri-hebdomadaires. Répétition du drill de libération d'otages multipliée par 8 sur 6 mois. Temps de réaction sous contact amélioré de 22% sur le drill de référence.", + 'Municipal police, large city' => 'Police municipale, grande ville', + 'MY PROSERVE rolled out to 24 stations. Self-reported training confidence improved 41%. Continuous-training compliance rose from 38% to 92% within 12 months.' + => "MY PROSERVE déployé sur 24 postes. La confiance en formation auto-déclarée s'est améliorée de 41%. La conformité à la formation continue est passée de 38% à 92% en 12 mois.", + 'Firefighter regional training centre' => 'Centre régional de formation des sapeurs-pompiers', + 'PROSERVE ACADEMY-FLEX hybrid for hazmat and confined-space rescue. Annual exercise volume tripled. Recruit retention improved due to better preparation for live exercise.' + => "Hybride PROSERVE ACADEMY-FLEX pour interventions matières dangereuses et espaces confinés. Volume annuel d'exercice triplé. Rétention des recrues améliorée grâce à une meilleure préparation aux exercices réels.", + + 'Reference partners' => 'Partenaires de référence', + 'If your unit is interested in becoming a public reference.' => 'Si votre unité souhaite devenir une référence publique.', + 'We work with reference partners on a long-term basis. In exchange for the right to publish your story (with the level of detail you authorize), we offer extended support, priority on custom scenarios, and active involvement in our product roadmap. Reach out to discuss.' + => "Nous travaillons avec les partenaires de référence sur le long terme. En échange du droit de publier votre histoire (au niveau de détail que vous autorisez), nous offrons un support étendu, la priorité sur les scénarios sur mesure et une implication active dans notre feuille de route produit. Contactez-nous pour en discuter.", + + /* ============================ Trust ============================ */ + 'Trust & Compliance' => 'Confiance & conformité', + 'Trust by engineering, not by claim.' => 'La confiance par l\'ingénierie, pas par l\'affirmation.', + 'Asterion VR is engineered for the trust requirements of public security forces. Sovereignty, data protection, and operational autonomy are designed into the platform \u{2014} not bolted on for compliance.' + => "Asterion VR est conçu pour les exigences de confiance des forces de sécurité publiques. Souveraineté, protection des données et autonomie opérationnelle sont intégrées à la plateforme \u{2014} pas plaquées pour la conformité.", + 'Request a Trust Pack' => 'Demander un Trust Pack', + + 'Sovereignty' => 'Souveraineté', + 'Designed European, supported European.' => 'Conçu européen, supporté européen.', + 'Data protection' => 'Protection des données', + 'Your data never leaves your network.' => 'Vos données ne quittent jamais votre réseau.', + 'Operational autonomy' => 'Autonomie opérationnelle', + 'Train without an internet connection.' => 'Entraînez-vous sans connexion internet.', + 'Cyber posture' => 'Posture cyber', + 'Hardened for institutional environments.' => 'Durci pour les environnements institutionnels.', + 'Warranty & support' => 'Garantie & support', + 'Perpetual licenses. Predictable support.' => 'Licences perpétuelles. Support prévisible.', + + /* ============================ Partners ============================ */ + 'Partners' => 'Partenaires', + 'Distribute PROSERVE in your region.' => 'Distribuez PROSERVE dans votre région.', + 'Asterion partners with established defense, security, and integrator companies across Europe, the Middle East, Asia, and Latin America. Our partner network combines local presence with our French engineering and studio. We are looking for new partners in selected regions.' + => "Asterion s'associe à des entreprises établies dans la défense, la sécurité et l'intégration en Europe, au Moyen-Orient, en Asie et en Amérique latine. Notre réseau de partenaires combine présence locale, ingénierie et studio français. Nous recherchons de nouveaux partenaires dans des régions sélectionnées.", + + 'Become a partner — apply' => 'Devenir partenaire — postulez', + 'Why partner with Asterion' => 'Pourquoi être partenaire d\'Asterion', + 'A complete commercial framework.' => 'Un cadre commercial complet.', + 'What we look for' => 'Ce que nous recherchons', + 'Selection criteria.' => 'Critères de sélection.', + ]; +} + +/* ----------------------------------------------------------------- + URL trigger + ----------------------------------------------------------------- */ +add_action('template_redirect', function () { + if (empty($_GET['av_generate_editorial'])) return; + if (! in_array($_SERVER['SERVER_NAME'] ?? '', ['localhost', '127.0.0.1'], true)) wp_die('localhost only'); + + $data_file = 'C:\\ASTERION\\GIT\\WP2026\\content-source\\editorial-data.php'; + if (! file_exists($data_file)) wp_die("data file not found: $data_file"); + + $editorial = include $data_file; + if (! is_array($editorial)) wp_die('editorial data not array'); + + $report = ["Rebuilding editorial pages (top-level, no hub)..."]; + + // 5 top-level detail pages : why-asterion, about, customers, trust, partners + foreach ($editorial as $slug => $d) { + $page = av_upsert_detail_page($slug, '', $d); // empty parent_path → top-level + $report[] = " $slug → page #{$page['id']} ({$page['action']}) — {$page['element_count']} elements (json={$page['json_len']}, stored={$page['stored_len']})"; + } + + header('Content-Type: text/plain; charset=utf-8'); + echo implode("\n", $report); + exit; +}); diff --git a/tools/av-generate-forms.php b/tools/av-generate-forms.php new file mode 100644 index 0000000..a940a0d --- /dev/null +++ b/tools/av-generate-forms.php @@ -0,0 +1,546 @@ + 'Réserver une démo', + 'See PROSERVE in action.' => 'Voir PROSERVE en action.', + 'A live demo, calibrated to your role and your unit. We respond within 48 hours.' + => 'Une démo en direct, calibrée à votre rôle et votre unité. Nous répondons sous 48 heures.', + 'Request a T&E Kit' => 'Demander un kit T&E', + 'Try PROSERVE in your own facility, with your own people.' + => 'Essayez PROSERVE dans vos locaux, avec votre équipe.', + "A demo only goes so far. The Test & Evaluation Kit programme lends a fully configured PROSERVE case to qualifying organizations for 5 to 10 days, on site, with our team's support." + => "Une démo a ses limites. Le programme Test & Evaluation prête un coffre PROSERVE entièrement configuré aux organisations éligibles pour 5 à 10 jours, sur site, avec le support de notre équipe.", + 'Request a Quote' => 'Demander un devis', + 'From specification to deployment plan.' => 'De la spécification au plan de déploiement.', + 'Tell us what you need. We send back a detailed quote \u{2014} including hardware, software, options, training, support, and deployment plan \u{2014} within 5 working days.' + => "Dites-nous ce dont vous avez besoin. Nous renvoyons un devis détaillé \u{2014} matériel, logiciel, options, formation, support et plan de déploiement \u{2014} sous 5 jours ouvrés.", + 'Contact' => 'Contact', + 'How can we help?' => 'Comment pouvons-nous aider ?', + "If you know what you're looking for, the three CTAs below take you directly there. For everything else, the form at the bottom reaches our office." + => "Si vous savez ce que vous cherchez, les trois CTA ci-dessous vous mènent directement. Pour tout le reste, le formulaire en bas atteint notre bureau.", + + // Promise titles + 'What you receive' => 'Ce que vous recevez', + 'Other inquiries' => 'Autres demandes', + + // Promise items (Demo) + "A 30-minute video call with a member of our team \u{2014} typically J\u{E9}r\u{F4}me, our founder, or a senior product manager." + => "Un appel vidéo de 30 minutes avec un membre de notre équipe \u{2014} typiquement Jérôme, notre fondateur, ou un product manager senior.", + 'A live walkthrough of the configuration most relevant to your needs (MY / FLEX / ACADEMY).' + => 'Une démonstration en direct de la configuration la plus pertinente (MY / FLEX / ACADEMY).', + 'A scenario demonstration matched to your segment (police, special forces, military, fire).' + => 'Une démonstration de scénario adaptée à votre segment (police, forces spéciales, armée, pompiers).', + 'Honest answers to your technical and procurement questions.' + => 'Réponses honnêtes à vos questions techniques et d\'achat.', + 'A follow-up email summarizing the discussion, with the right resources for your evaluation.' + => "Un email de suivi résumant l'échange, avec les bonnes ressources pour votre évaluation.", + + // Promise items (T&E Kit) + 'A complete PROSERVE FLEX or MY PROSERVE configuration delivered to your facility.' + => 'Une configuration complète PROSERVE FLEX ou MY PROSERVE livrée dans vos locaux.', + 'On-site setup and instructor onboarding by our team (1-2 days).' + => "Installation sur site et formation instructeur par notre équipe (1-2 jours).", + 'Remote technical support throughout the loan period.' + => 'Support technique à distance pendant toute la durée du prêt.', + 'Up to 10 days of unrestricted use by your operators and instructors.' + => 'Jusqu\'à 10 jours d\'utilisation sans restriction par vos opérateurs et instructeurs.', + 'A debrief session at the end to capture observations and answer technical questions.' + => 'Une séance de débrief en fin de période pour recueillir les observations et répondre aux questions techniques.', + + // Promise items (Quote) + 'A detailed quote (PDF, suitable for procurement records)' + => 'Un devis détaillé (PDF, conforme aux exigences d\'archivage d\'achat)', + 'A deployment plan with milestones and timing' + => 'Un plan de déploiement avec jalons et calendrier', + 'A 30-minute call with our commercial team to walk through the quote' + => 'Un appel de 30 minutes avec notre équipe commerciale pour parcourir le devis', + 'References for an introduction call with comparable customers (subject to NDA)' + => 'Des références pour un appel d\'introduction avec des clients comparables (sous NDA)', + + // Promise items (Contact) + 'Press & media — press@asterionvr.com (response within 24 hours)' + => 'Presse & médias — press@asterionvr.com (réponse sous 24 heures)', + 'Partnerships & distributors — partners@asterionvr.com' + => 'Partenariats & distributeurs — partners@asterionvr.com', + 'Careers — careers@asterionvr.com' => 'Carrières — careers@asterionvr.com', + 'General — hello@asterionvr.com' => 'Général — hello@asterionvr.com', + "Office \u{2014} Asterion VR \u{B7} 9 rue de la Mareb\u{E9}audi\u{E8}re \u{B7} 35760 Montgermont \u{B7} France \u{B7} +33 6 86 94 33 51" + => "Bureau \u{2014} Asterion VR \u{B7} 9 rue de la Marebaudière \u{B7} 35760 Montgermont \u{B7} France \u{B7} +33 6 86 94 33 51", + + // Reassurance lines + 'We respond within 48 hours. We never share your information. No newsletter sign-up unless you ask.' + => 'Nous répondons sous 48 heures. Nous ne partageons jamais vos informations. Pas d\'inscription newsletter sauf demande.', + 'Limited slots per quarter. We respond within 5 working days with a yes-or-no on the loan, and a proposed schedule if eligible.' + => "Places limitées par trimestre. Nous répondons sous 5 jours ouvrés avec un oui/non sur le prêt et un calendrier proposé si éligible.", + 'We respond within 5 working days. Detailed quote suitable for procurement records.' + => 'Nous répondons sous 5 jours ouvrés. Devis détaillé conforme aux exigences d\'achat.', + 'We respond within 48 hours on weekdays.' => 'Nous répondons sous 48 heures en jours ouvrés.', + + // Submit labels + 'Schedule the demo' => 'Réserver la démo', + 'Apply for a T&E Kit' => 'Postuler pour un kit T&E', + 'Request the quote' => 'Demander le devis', + 'Send the message' => 'Envoyer le message', + + // Form field labels (shared across forms) + 'Full name' => 'Nom complet', + 'Organization' => 'Organisation', + 'Job title' => 'Fonction', + 'Job title / role' => 'Fonction / rôle', + 'Country' => 'Pays', + 'Email' => 'Email', + 'Segment' => 'Segment', + 'Estimated number of trainees in your unit' => 'Nombre estimé de stagiaires dans votre unité', + 'Preferred timeframe' => 'Délai préféré', + 'Message — anything specific you want us to demonstrate' + => 'Message — éléments spécifiques à démontrer', + 'Message' => 'Message', + 'Address of evaluation facility' => 'Adresse du site d\'évaluation', + 'Number of trainees who would participate in evaluation' + => 'Nombre de stagiaires participant à l\'évaluation', + 'Procurement timeline' => 'Calendrier d\'achat', + 'Indicative budget range' => 'Fourchette budgétaire indicative', + 'Authorization status' => 'Statut d\'autorisation', + 'Procurement vehicle' => 'Véhicule d\'achat', + 'Configuration desired' => 'Configuration souhaitée', + 'Number of units / packs / seats' => 'Nombre d\'unités / packs / postes', + 'Options' => 'Options', + 'Deployment location and timeline' => 'Lieu et calendrier de déploiement', + 'Administrative constraints (open tender deadline, cybersecurity review process, etc.)' + => 'Contraintes administratives (échéance d\'appel d\'offres, processus de revue cyber, etc.)', + 'Subject' => 'Sujet', + + // Select options + 'Police' => 'Police', + 'Special Forces' => 'Forces spéciales', + 'Military' => 'Armée', + 'Firefighters' => 'Sapeurs-pompiers', + 'Other' => 'Autre', + 'This week' => 'Cette semaine', + 'Next week' => 'La semaine prochaine', + 'Next month' => 'Le mois prochain', + 'No rush' => 'Pas pressé', + '3 months' => '3 mois', + '6 months' => '6 mois', + '12 months' => '12 mois', + '18+ months' => '18+ mois', + 'Cleared' => 'Validé', + 'Pending' => 'En attente', + 'Non-applicable' => 'Non applicable', + 'Direct purchase' => 'Achat direct', + 'Open tender' => 'Appel d\'offres ouvert', + 'Framework contract' => 'Marché-cadre', + + // FAQ (Demo) + 'Will there be a sales pitch?' => 'Y aura-t-il un pitch commercial ?', + "No. The first call is technical and pedagogical. We assume you'll evaluate three or four solutions before deciding \u{2014} we want to give you the information you need to compare honestly." + => "Non. Le premier appel est technique et pédagogique. Nous partons du principe que vous évaluerez trois ou quatre solutions avant de décider \u{2014} nous voulons vous donner les informations nécessaires pour comparer honnêtement.", + 'Can I bring colleagues?' => 'Puis-je amener des collègues ?', + 'Of course. The more roles in the room (training officer, IT, finance, command), the more useful the call.' + => "Bien sûr. Plus il y a de rôles dans la pièce (responsable formation, IT, finance, commandement), plus l'appel est utile.", + 'In what language?' => 'En quelle langue ?', + 'EN, FR. Other languages on a per-request basis.' + => 'EN, FR. Autres langues sur demande.', + + // Eligibility (T&E Kit) + 'Eligibility' => 'Éligibilité', + 'The T&E Kit programme is reserved for organizations with a clear procurement intent and the capacity to run meaningful evaluation drills. We accept applications from:' + => "Le programme T&E Kit est réservé aux organisations avec une intention d'achat claire et la capacité à conduire des exercices d'évaluation significatifs. Nous acceptons les candidatures de :", + 'National and regional police, gendarmerie, and security forces' + => 'Polices nationales et régionales, gendarmerie et forces de sécurité', + 'Special forces and tactical units' => 'Forces spéciales et unités tactiques', + 'Military training centres' => 'Centres de formation militaire', + 'Firefighter and emergency services training schools' + => 'Écoles de formation des sapeurs-pompiers et services d\'urgence', + 'Approved distributors and integrators bidding on identified projects' + => "Distributeurs et intégrateurs agréés répondant à des projets identifiés", + + // Shortcuts (Contact) + 'Request a Demo (response within 48 hours)' + => 'Réserver une démo (réponse sous 48 heures)', + 'Request a T&E Kit (response within 5 working days)' + => 'Demander un kit T&E (réponse sous 5 jours ouvrés)', + 'Request a Quote (response within 5 working days)' + => 'Demander un devis (réponse sous 5 jours ouvrés)', + ]); +}); + +/* ----------------------------------------------------------------- + URL trigger + ----------------------------------------------------------------- */ +add_action('template_redirect', function () { + if (empty($_GET['av_generate_forms'])) return; + if (! in_array($_SERVER['SERVER_NAME'] ?? '', ['localhost', '127.0.0.1'], true)) wp_die('localhost only'); + + $data_file = 'C:\\ASTERION\\GIT\\WP2026\\content-source\\forms-data.php'; + if (! file_exists($data_file)) wp_die("data file not found: $data_file"); + + $forms = include $data_file; + if (! is_array($forms)) wp_die('forms data not array'); + + $report = ["Rebuilding 4 conversion forms pages..."]; + + foreach ($forms as $slug => $d) { + // Custom page builder for forms (hero + 2-col + form + extras + closer) + $elements = av_build_form_page_elements($slug, $d); + + // Ensure page exists top-level + $existing = get_page_by_path($slug); + if ($existing) { + wp_update_post([ + 'ID' => $existing->ID, 'post_title' => $d['title'], + 'post_status' => 'publish', 'post_parent' => 0, + ]); + $page_id = $existing->ID; + $action = 'updated'; + } else { + $page_id = wp_insert_post([ + 'post_type' => 'page', 'post_title' => $d['title'], + 'post_name' => $slug, 'post_parent' => 0, + 'post_status' => 'publish', 'post_content' => '', + ]); + $action = 'created'; + } + + $write = av_write_bricks_page_elements($page_id, $elements); + $report[] = " $slug → page #$page_id ($action) — " . count($elements) + . " elements (json={$write['json_len']}, stored={$write['stored_len']})"; + } + + header('Content-Type: text/plain; charset=utf-8'); + echo implode("\n", $report); + exit; +}); + +/* ----------------------------------------------------------------- + FORM PAGE BUILDER + ----------------------------------------------------------------- */ +function av_build_form_page_elements($slug, $d) { + $els = []; + + // 1. HERO + $els = array_merge($els, av_build_hero($slug, $d)); + + // 2. 2-COLUMN SECTION : form left, promise right + $els = array_merge($els, av_build_form_two_col($slug, $d)); + + // 3. Optional: shortcuts (Contact page) + if (! empty($d['shortcuts'])) { + $els = array_merge($els, av_build_shortcuts_section($slug, $d['shortcuts'])); + } + + // 4. Optional: eligibility (T&E Kit) + if (! empty($d['eligibility'])) { + $els = array_merge($els, av_build_list_section($slug, 'elig', [ + 'eyebrow' => $d['eligibility']['title'] ?? 'Eligibility', + 'title' => '', + 'lead' => $d['eligibility']['lead'] ?? '', + 'items' => $d['eligibility']['items'] ?? [], + ], 'var(--color-bg-default)')); + } + + // 5. Optional: FAQ (Demo) + if (! empty($d['faq'])) { + $els = array_merge($els, av_build_faq_section($slug, $d['faq'])); + } + + // 6. CLOSER CTA + $els = array_merge($els, av_build_closer_cta($slug)); + + return $els; +} + +function av_build_form_two_col($slug, $d) { + $sid = "f{$slug}sc"; // section + $oct = "f{$slug}oc"; // outer container + $col1 = "f{$slug}c1"; // form column + $col2 = "f{$slug}c2"; // promise column + $frm = "f{$slug}fm"; // form element + $reas = "f{$slug}rs"; // reassurance text + $peyb = "f{$slug}pe"; // promise eyebrow + $plst = "f{$slug}pl"; // promise list container + + $els = []; + + $els[] = [ + 'id' => $sid, 'name' => 'section', 'parent' => 0, 'children' => [$oct], + 'settings' => array_merge(av_section_padding_responsive(), [ + '_background' => ['color' => av_color('var(--color-bg-default)')], + ]), + ]; + $els[] = [ + 'id' => $oct, 'name' => 'container', 'parent' => $sid, 'children' => [$col1, $col2], + 'settings' => [ + '_direction' => 'row', + '_widthMax' => '1280px', + '_width' => '100%', + '_padding' => av_sp('0', '1.5rem', '0', '1.5rem'), + '_padding:mobile_landscape' => av_sp('0', '1rem', '0', '1rem'), + '_columnGap' => '3rem', + '_rowGap' => '2rem', + '_flexWrap' => 'wrap', + '_alignItems' => 'flex-start', + ], + ]; + + // LEFT COLUMN — form + reassurance + $els[] = [ + 'id' => $col1, 'name' => 'container', 'parent' => $oct, 'children' => [$frm, $reas], + 'settings' => [ + '_direction' => 'column', + '_widthMax' => '600px', + '_widthMin' => '0', + '_width' => '100%', + '_flexBasis' => 'calc((100% - 3rem) / 2)', + '_flexBasis:tablet_portrait' => '100%', + '_flexGrow' => '1', + '_padding' => av_sp('0', '0', '0', '0'), + '_rowGap' => '1.5rem', + ], + ]; + + // Bricks form element + $els[] = av_build_form_element($frm, $col1, $d); + + // Reassurance line + if (! empty($d['reassurance'])) { + $els[] = av_text($reas, $col1, $d['reassurance'], 'var(--color-text-tertiary)', '0.875rem', '1.55'); + } + + // RIGHT COLUMN — promise eyebrow + list + $els[] = [ + 'id' => $col2, 'name' => 'container', 'parent' => $oct, 'children' => [$peyb, $plst], + 'settings' => [ + '_direction' => 'column', + '_widthMax' => '480px', + '_widthMin' => '0', + '_width' => '100%', + '_flexBasis' => 'calc((100% - 3rem) / 2)', + '_flexBasis:tablet_portrait' => '100%', + '_flexGrow' => '0', + '_padding' => av_sp('1rem', '1.75rem', '1.75rem', '1.75rem'), + '_background' => ['color' => av_color('var(--color-bg-subtle)')], + '_border' => [ + 'width' => av_sp('1px', '1px', '1px', '1px'), + 'color' => av_color('var(--color-border-subtle)'), + 'style' => 'solid', + 'radius' => av_sp('4px', '4px', '4px', '4px'), + ], + '_rowGap' => '0.75rem', + ], + ]; + $els[] = av_heading($peyb, $col2, 'h3', $d['promise_title'] ?? 'What you receive'); + + // Promise list as text-basic items + $items = $d['promise_items'] ?? []; + $item_ids = []; + foreach ($items as $j => $_) $item_ids[] = "fp{$slug}i{$j}"; + $els[] = [ + 'id' => $plst, 'name' => 'container', 'parent' => $col2, 'children' => $item_ids, + 'settings' => [ + '_direction' => 'column', + '_rowGap' => '0.6rem', + '_padding' => av_sp('0.5rem', '0', '0', '0'), + '_width' => '100%', + ], + ]; + foreach ($items as $j => $item) { + $els[] = [ + 'id' => "fp{$slug}i{$j}", 'name' => 'text-basic', 'parent' => $plst, 'children' => [], + 'settings' => [ + 'text' => "\u{2014} " . $item, + '_typography' => [ + 'color' => av_color('var(--color-text-secondary)'), + 'font-size' => '0.9375rem', + 'line-height' => '1.55', + ], + ], + ]; + } + + return $els; +} + +/** + * Build a Bricks `form` element with the given fields, submit button, and + * email action (sends to hello@asterionvr.com). Fields config follows + * Bricks's repeater shape : each field is an associative array with 'id', + * 'type', 'label', 'required', and 'options' (for select). + */ +function av_build_form_element($id, $parent, $d) { + $bricks_fields = []; + foreach ($d['fields'] as $f) { + $field = [ + 'id' => substr(md5($id . $f['name']), 0, 6), + 'type' => $f['type'], + 'label' => $f['label'], + 'name' => $f['name'], + ]; + if (! empty($f['required'])) $field['required'] = true; + if ($f['type'] === 'select' && ! empty($f['options'])) { + // Bricks expects a single string with "value : label\n" per line OR + // an array of {value, label}. We use a newline string for simplicity. + $opts_str = []; + foreach ($f['options'] as $opt) { + $opts_str[] = $opt === '' ? "" : "$opt : $opt"; + } + $field['options'] = implode("\n", $opts_str); + } + $bricks_fields[] = $field; + } + + return [ + 'id' => $id, 'name' => 'form', 'parent' => $parent, 'children' => [], + 'settings' => [ + 'fields' => $bricks_fields, + 'submitButtonText' => $d['submit_label'] ?? 'Submit', + 'actions' => ['email'], + 'emailTo' => 'custom', + 'email' => 'hello@asterionvr.com', + 'fromEmail' => 'noreply@asterionvr.com', + 'fromName' => 'Asterion VR website', + 'replyToEmail' => true, + 'subject' => 'Asterion VR — ' . ($d['title'] ?? 'New submission'), + 'successMessage' => 'Thank you. We will get back to you shortly.', + '_typography' => [ + 'font-size' => '1rem', + ], + 'submitButtonStyle' => 'primary', + ], + ]; +} + +function av_build_shortcuts_section($slug, $shortcuts) { + $sid = "fs{$slug}sc"; + $oct = "fs{$slug}oc"; + $row = "fs{$slug}rw"; + + $btn_ids = []; + foreach ($shortcuts as $j => $sh) $btn_ids[] = "fs{$slug}b{$j}"; + + $els = []; + $els[] = [ + 'id' => $sid, 'name' => 'section', 'parent' => 0, 'children' => [$oct], + 'settings' => array_merge(av_section_padding_responsive('3rem', '3rem'), [ + '_background' => ['color' => av_color('var(--color-bg-subtle)')], + ]), + ]; + $els[] = [ + 'id' => $oct, 'name' => 'container', 'parent' => $sid, 'children' => [$row], + 'settings' => [ + '_direction' => 'column', + '_widthMax' => '1280px', + '_width' => '100%', + '_padding' => av_sp('0', '1.5rem', '0', '1.5rem'), + '_padding:mobile_landscape' => av_sp('0', '1rem', '0', '1rem'), + '_alignItems' => 'flex-start', + ], + ]; + $els[] = [ + 'id' => $row, 'name' => 'container', 'parent' => $oct, 'children' => $btn_ids, + 'settings' => [ + '_direction' => 'row', + '_flexWrap' => 'wrap', + '_columnGap' => '1rem', + '_rowGap' => '1rem', + '_width' => '100%', + ], + ]; + foreach ($shortcuts as $j => $sh) { + $variant = ($sh['variant'] ?? 'primary') === 'primary' ? 'primary' : 'secondary'; + $els[] = av_button("fs{$slug}b{$j}", $row, $sh['label'], $sh['href'], $variant); + } + return $els; +} + +function av_build_faq_section($slug, $faq) { + $sid = "fq{$slug}sc"; + $oct = "fq{$slug}oc"; + $nrr = "fq{$slug}nr"; + $hId = "fq{$slug}h2"; + $lst = "fq{$slug}lt"; + + $item_ids = []; + foreach ($faq as $j => $_) $item_ids[] = "fq{$slug}i{$j}"; + + $els = []; + $els[] = [ + 'id' => $sid, 'name' => 'section', 'parent' => 0, 'children' => [$oct], + 'settings' => array_merge(av_section_padding_responsive(), [ + '_background' => ['color' => av_color('var(--color-bg-default)')], + ]), + ]; + $els[] = [ + 'id' => $oct, 'name' => 'container', 'parent' => $sid, 'children' => [$nrr], + 'settings' => [ + '_direction' => 'column', + '_widthMax' => '1280px', + '_width' => '100%', + '_padding' => av_sp('0', '1.5rem', '0', '1.5rem'), + '_padding:mobile_landscape' => av_sp('0', '1rem', '0', '1rem'), + '_alignItems' => 'flex-start', + ], + ]; + $els[] = [ + 'id' => $nrr, 'name' => 'container', 'parent' => $oct, 'children' => array_merge([$hId, $lst]), + 'settings' => [ + '_direction' => 'column', + '_widthMax' => '800px', + '_widthMin' => '0', + '_width' => '100%', + '_margin' => av_sp('0', 'auto', '0', '0'), + '_padding' => av_sp('0', '0', '0', '0'), + '_rowGap' => '1.5rem', + ], + ]; + $els[] = av_heading($hId, $nrr, 'h2', 'FAQ'); + + $els[] = [ + 'id' => $lst, 'name' => 'container', 'parent' => $nrr, 'children' => $item_ids, + 'settings' => [ + '_direction' => 'column', + '_rowGap' => '1rem', + '_width' => '100%', + ], + ]; + + foreach ($faq as $j => $entry) { + $iid = "fq{$slug}i{$j}"; + $qid = "fq{$slug}q{$j}"; + $aid = "fq{$slug}a{$j}"; + $els[] = [ + 'id' => $iid, 'name' => 'container', 'parent' => $lst, 'children' => [$qid, $aid], + 'settings' => [ + '_direction' => 'column', + '_rowGap' => '0.5rem', + '_padding' => av_sp('1rem', '1.25rem', '1rem', '1.25rem'), + '_background' => ['color' => av_color('var(--color-bg-subtle)')], + '_border' => [ + 'width' => av_sp('1px', '1px', '1px', '1px'), + 'color' => av_color('var(--color-border-subtle)'), + 'style' => 'solid', + 'radius' => av_sp('4px', '4px', '4px', '4px'), + ], + '_width' => '100%', + ], + ]; + $els[] = av_heading($qid, $iid, 'h3', $entry['q']); + $els[] = av_text($aid, $iid, $entry['a'], 'var(--color-text-secondary)', '1rem', '1.55'); + } + + return $els; +} diff --git a/tools/av-generate-home.php b/tools/av-generate-home.php new file mode 100644 index 0000000..9cd934f --- /dev/null +++ b/tools/av-generate-home.php @@ -0,0 +1,292 @@ + 'Entraînez-vous comme vous opérez.', + "PROSERVE\u{2122} is the modular XR training platform engineered for European security forces. Real recoil, autonomous AI, sovereign by design \u{2014} from solo officer to full academy." + => "PROSERVE\u{2122} est la plateforme XR modulaire conçue pour les forces de sécurité européennes. Recul réel, IA autonome, souveraineté native \u{2014} de l'agent seul à l'académie complète.", + + // 3 pillars row + 'Three certainties' => 'Trois certitudes', + 'A platform engineered around three commitments.' => 'Une plateforme construite autour de trois engagements.', + 'Operational reality' => 'Réalité opérationnelle', + 'Real recoil. Real weapons. Real consequences. AI civilians and adversaries that perceive and decide. No two drills play out the same way.' + => "Recul réel. Vraies armes. Conséquences réelles. Civils et adversaires IA qui perçoivent et décident. Aucun exercice ne se joue deux fois pareil.", + 'Modular by design' => 'Modulaire par conception', + 'From 9 m\u{B2} solo to 900 m\u{B2} academy, same software, same scenarios, same instructor tools. Your investment scales with you.' + => "De 9 m\u{B2} solo à 900 m\u{B2} académie, même logiciel, mêmes scénarios, mêmes outils instructeur. Votre investissement évolue avec vous.", + 'Sovereign & secure' => 'Souverain & sécurisé', + 'Designed and produced in France. Operates fully offline. No data leaves your network. GDPR-native by default.' + => "Conçu et produit en France. Fonctionne entièrement hors ligne. Aucune donnée ne quitte votre réseau. RGPD-natif par défaut.", + + // Industries teaser + 'Who we serve' => 'Qui nous servons', + 'Built for those who carry the weight.' => 'Conçu pour celles et ceux qui en portent le poids.', + + // Solutions teaser + 'The PROSERVE platform' => 'La plateforme PROSERVE', + 'Pick the configuration that fits your unit.' => 'Choisissez la configuration adaptée à votre unité.', + + // Numbers band + 'By the numbers' => 'En chiffres', + '10 years of XR R&D focused on security forces.' + => "10 ans de R&D XR consacrés aux forces de sécurité.", + '10 years' => '10 ans', + 'XR R&D for security forces' => 'R&D XR pour les forces de sécurité', + '11 environments' => '11 environnements', + 'Validated training contexts' => 'Contextes d\'entraînement validés', + '50+ scenarios' => '50+ scénarios', + 'Updated four times a year' => 'Mis à jour quatre fois par an', + 'GDPR-native' => 'RGPD-natif', + 'Fully offline operation' => 'Fonctionnement entièrement hors ligne', + + // Why Asterion teaser + 'Why teams choose Asterion' => 'Pourquoi les équipes choisissent Asterion', + 'A platform built around the operator. Not the other way around.' + => "Une plateforme construite autour de l'opérateur. Pas l'inverse.", + "Founded in 2016 to solve one specific problem: security forces need more repetition than the ranges, ammunition budgets, and exercise calendars allow. Ten years later, our platform does exactly that \u{2014} at every scale, in every environment, for every role." + => "Fondée en 2016 pour résoudre un problème précis : les forces de sécurité ont besoin de plus de répétition que ce que les stands, budgets munitions et calendriers d'exercice permettent. Dix ans plus tard, notre plateforme fait exactement cela \u{2014} à toute échelle, dans tout environnement, pour tout rôle.", + 'Read the full story' => 'Lire l\'histoire complète', + + // Customers teaser + 'Trusted by units across Europe' => 'Choisi par des unités en Europe', + 'Real deployments. Measured results.' => 'Déploiements réels. Résultats mesurés.', + "From national police academies to special tactical units, PROSERVE is in active service \u{2014} with measurable impact on training volume, tactical accuracy, and operator confidence." + => "Des écoles nationales de police aux unités tactiques spéciales, PROSERVE est en service actif \u{2014} avec un impact mesurable sur le volume d'entraînement, la précision tactique et la confiance des opérateurs.", + 'See customer stories' => 'Voir les retours clients', + + // Final closer (separate from shared closer because we override copy) + 'Ready to see PROSERVE in action?' => 'Prêt à voir PROSERVE en action ?', + 'Schedule a tailored demo or request a Test & Evaluation Kit.' + => 'Réservez une démo personnalisée ou demandez un kit Test & Evaluation.', + ]); +}); + +/* ----------------------------------------------------------------- + URL trigger + ----------------------------------------------------------------- */ +add_action('template_redirect', function () { + if (empty($_GET['av_generate_home'])) return; + if (! in_array($_SERVER['SERVER_NAME'] ?? '', ['localhost', '127.0.0.1'], true)) wp_die('localhost only'); + + // Ensure home page exists (slug 'home') AND is set as WP static front page + $home = get_page_by_path('home'); + if (! $home) { + $home_id = wp_insert_post([ + 'post_type' => 'page', 'post_title' => 'Home', + 'post_name' => 'home', 'post_parent' => 0, + 'post_status' => 'publish', 'post_content' => '', + ]); + $action = 'created'; + } else { + $home_id = $home->ID; + $action = 'updated'; + } + + // Set as WP static front page + update_option('show_on_front', 'page'); + update_option('page_on_front', $home_id); + + $elements = av_build_home_elements(); + $write = av_write_bricks_page_elements($home_id, $elements); + + header('Content-Type: text/plain; charset=utf-8'); + echo "Home page #$home_id ($action) — " . count($elements) . " elements (json={$write['json_len']}, stored={$write['stored_len']})\n"; + echo "show_on_front = page, page_on_front = $home_id\n"; + echo "Visit / to see the home.\n"; + exit; +}); + +/* ----------------------------------------------------------------- + HOME PAGE BUILDER + ----------------------------------------------------------------- */ +function av_build_home_elements() { + $els = []; + + // 1. HERO — full bleed, large tagline + $els = array_merge($els, av_build_hero('home', [ + 'eyebrow' => '', + 'tagline' => 'Train like you operate.', + 'hero_lead' => "PROSERVE\u{2122} is the modular XR training platform engineered for European security forces. Real recoil, autonomous AI, sovereign by design \u{2014} from solo officer to full academy.", + 'ctas' => [ + ['label' => 'Book a demo', 'href' => '/request-demo/', 'variant' => 'primary'], + ['label' => 'Request a T&E Kit', 'href' => '/request-te-kit/', 'variant' => 'secondary'], + ], + ])); + + // 2. THREE PILLARS — 3-card grid (count===3 → 33% each, clean row of 3) + $els = array_merge($els, av_build_cards_section('homep', 0, [ + 'eyebrow' => 'Three certainties', + 'title' => 'A platform engineered around three commitments.', + 'items' => [ + ['title' => 'Operational reality', + 'body' => 'Real recoil. Real weapons. Real consequences. AI civilians and adversaries that perceive and decide. No two drills play out the same way.'], + ['title' => 'Modular by design', + 'body' => "From 9 m\u{B2} solo to 900 m\u{B2} academy, same software, same scenarios, same instructor tools. Your investment scales with you."], + ['title' => 'Sovereign & secure', + 'body' => 'Designed and produced in France. Operates fully offline. No data leaves your network. GDPR-native by default.'], + ], + ], 'var(--color-bg-subtle)')); + + // 3. INDUSTRIES — 4 cards linking to /industries/* + $industries_cards = [ + 'eyebrow' => 'Who we serve', + 'title' => 'Built for those who carry the weight.', + 'items' => [ + ['title' => 'Police', 'body' => "National & Municipal \u{2014} Police training, when the schedule won't bend."], + ['title' => 'Special Forces', 'body' => "Tactical units \u{2014} Special forces train differently. Their simulation should, too."], + ['title' => 'Military', 'body' => "Armed forces \u{2014} Military VR training. Sovereign. Modular. Field-deployable."], + ['title' => 'Firefighters', 'body' => "Emergency services \u{2014} Train the worst day. Safely. Repeatedly."], + ], + ]; + $els = array_merge($els, av_build_cards_section('homei', 1, $industries_cards, 'var(--color-bg-default)')); + // Append "Learn more →" link in each industry card + $industry_slugs = ['police', 'special-forces', 'military', 'firefighters']; + foreach ($industry_slugs as $i => $slug) { + $kid = "khomeic1x{$i}"; + $lid = "lnhomeic1x{$i}"; + $els[] = av_text_link($lid, $kid, "Learn more \u{2192}", "/industries/$slug/"); + foreach ($els as &$el) { + if (($el['id'] ?? '') === $kid) { + $el['children'][] = $lid; + } + } + unset($el); + } + + // 4. SOLUTIONS — 4 cards linking to /solutions/* + $solutions_cards = [ + 'eyebrow' => 'The PROSERVE platform', + 'title' => 'Pick the configuration that fits your unit.', + 'items' => [ + ['title' => 'MY PROSERVE', 'body' => "Solo training \u{2014} Your first range, in a mini-case."], + ['title' => 'PROSERVE FLEX', 'body' => "Team training \u{B7} 1 to 5 \u{2014} Your team, in the same scenario, on any flat surface."], + ['title' => 'PROSERVE ACADEMY', 'body' => "Unit training \u{B7} 1 to 16 \u{2014} The full training centre, in your facility."], + ['title' => 'PROSERVE+', 'body' => "Customization & options \u{2014} When the catalog doesn't fit your mission."], + ], + ]; + $els = array_merge($els, av_build_cards_section('homes', 2, $solutions_cards, 'var(--color-bg-subtle)')); + $solution_slugs = ['my-proserve', 'proserve-flex', 'proserve-academy', 'customization']; + foreach ($solution_slugs as $i => $slug) { + $kid = "khomesc2x{$i}"; + $lid = "lnhomesc2x{$i}"; + $els[] = av_text_link($lid, $kid, "Learn more \u{2192}", "/solutions/$slug/"); + foreach ($els as &$el) { + if (($el['id'] ?? '') === $kid) { + $el['children'][] = $lid; + } + } + unset($el); + } + + // 5. NUMBERS BAND — 4-card grid stats + $els = array_merge($els, av_build_cards_section('homen', 3, [ + 'eyebrow' => 'By the numbers', + 'title' => '10 years of XR R&D focused on security forces.', + 'items' => [ + ['title' => '10 years', 'body' => 'XR R&D for security forces'], + ['title' => '11 environments', 'body' => 'Validated training contexts'], + ['title' => '50+ scenarios', 'body' => 'Updated four times a year'], + ['title' => 'GDPR-native', 'body' => 'Fully offline operation'], + ], + ], 'var(--color-bg-default)')); + + // 6. WHY ASTERION teaser — prose with CTA + $els = array_merge($els, av_build_cta_section('homew', 4, [ + 'eyebrow' => 'Why teams choose Asterion', + 'title' => 'A platform built around the operator. Not the other way around.', + 'body' => "Founded in 2016 to solve one specific problem: security forces need more repetition than the ranges, ammunition budgets, and exercise calendars allow. Ten years later, our platform does exactly that \u{2014} at every scale, in every environment, for every role.", + 'ctas' => [ + ['label' => 'Read the full story', 'href' => '/why-asterion/', 'variant' => 'secondary'], + ], + ], 'var(--color-bg-subtle)')); + + // 7. CUSTOMERS teaser + $els = array_merge($els, av_build_cta_section('homec', 5, [ + 'eyebrow' => 'Trusted by units across Europe', + 'title' => 'Real deployments. Measured results.', + 'body' => "From national police academies to special tactical units, PROSERVE is in active service \u{2014} with measurable impact on training volume, tactical accuracy, and operator confidence.", + 'ctas' => [ + ['label' => 'See customer stories', 'href' => '/customers/', 'variant' => 'secondary'], + ], + ], 'var(--color-bg-default)')); + + // 8. CLOSER CTA — full navy with custom copy + $els = array_merge($els, av_build_home_closer_cta()); + + return $els; +} + +function av_build_home_closer_cta() { + $sid = "schmhc"; + $oct = "ochmhc"; + $nrr = "nrhmhc"; + $eyb = "ebhmhc"; + $hId = "h2hmhc"; + $led = "ldhmhc"; + $row = "rwhmhc"; + $b1 = "b1hmhc"; + $b2 = "b2hmhc"; + + return [ + [ + 'id' => $sid, 'name' => 'section', 'parent' => 0, 'children' => [$oct], + 'settings' => array_merge(av_section_padding_responsive(), [ + '_background' => ['color' => av_color('var(--color-brand-navy)')], + ]), + ], + [ + 'id' => $oct, 'name' => 'container', 'parent' => $sid, 'children' => [$nrr], + 'settings' => [ + '_direction' => 'column', + '_widthMax' => '1280px', + '_width' => '100%', + '_padding' => av_sp('0', '1.5rem', '0', '1.5rem'), + '_padding:mobile_landscape' => av_sp('0', '1rem', '0', '1rem'), + '_alignItems' => 'flex-start', + ], + ], + [ + 'id' => $nrr, 'name' => 'container', 'parent' => $oct, 'children' => [$eyb, $hId, $led, $row], + 'settings' => [ + '_direction' => 'column', + '_widthMax' => '800px', + '_widthMin' => '0', + '_width' => '100%', + '_margin' => av_sp('0', 'auto', '0', '0'), + '_padding' => av_sp('0', '0', '0', '0'), + '_rowGap' => '1.25rem', + ], + ], + av_eyebrow($eyb, $nrr, 'Ready to see PROSERVE in action?'), + av_heading($hId, $nrr, 'h2', 'Schedule a tailored demo or request a Test & Evaluation Kit.', 'var(--color-text-on-dark)'), + av_text($led, $nrr, "Book 30 minutes with our team. We'll bring the kit. You bring the doctrine.", 'var(--color-text-on-dark)', '1.125rem', '1.55'), + [ + 'id' => $row, 'name' => 'container', 'parent' => $nrr, 'children' => [$b1, $b2], + 'settings' => [ + '_direction' => 'row', + '_columnGap' => '1rem', + '_rowGap' => '1rem', + '_flexWrap' => 'wrap', + '_widthMin' => '0', + '_width' => '100%', + '_padding' => av_sp('0.5rem', '0', '0', '0'), + ], + ], + av_button($b1, $row, 'Book a Demo', '/request-demo/', 'gold'), + av_button($b2, $row, 'Request a T&E Kit', '/request-te-kit/', 'outline-on-dark'), + ]; +} diff --git a/tools/av-generate-technology.php b/tools/av-generate-technology.php new file mode 100644 index 0000000..323a048 --- /dev/null +++ b/tools/av-generate-technology.php @@ -0,0 +1,306 @@ + 'Technologie', + 'PROSERVE platform' => 'Plateforme PROSERVE', + "Six pillars. One coherent platform." => "Six piliers. Une plateforme cohérente.", + "VR hardware, autonomous AI, real weapons with universal tracking, fifty-plus scenarios, instructor cockpit, frame-level after-action review. Each pillar engineered to support training under operational conditions \u{2014} every day, every shift." + => "Matériel VR, IA autonome, vraies armes avec tracking universel, plus de cinquante scénarios, cockpit instructeur, debriefing image par image. Chaque pilier conçu pour soutenir l'entraînement en conditions opérationnelles \u{2014} chaque jour, chaque service.", + "Explore each pillar of the platform." => "Explorez chaque pilier de la plateforme.", + + /* -------- Card titles + concatenated bodies (hub) -------- */ + 'VR Hardware' => 'Matériel VR', + 'Software & AI' => 'Logiciel & IA', + 'Weapons & Tracking' => 'Armes & Tracking', + 'Scenarios Library' => 'Bibliothèque de scénarios', + 'Scenarios' => 'Scénarios', + 'Instructor Cockpit' => 'Cockpit instructeur', + 'After-Action Review' => 'Debriefing (AAR)', + + 'Wireless. 5K. Engineered for daily operational use.' + => 'Sans fil. 5K. Conçu pour l\'usage opérationnel quotidien.', + 'Behavior, not scripts.' + => 'Du comportement, pas du script.', + 'The weapon you train with is the weapon you carry.' + => 'L\'arme avec laquelle tu t\'entraînes est celle que tu portes.', + 'Eleven environments. More than fifty scenarios. New ones every quarter.' + => 'Onze environnements. Plus de cinquante scénarios. De nouveaux chaque trimestre.', + 'The cockpit between the simulation and the lesson.' + => 'Le cockpit entre la simulation et la leçon.', + 'Every drill ends with data. Not impressions.' + => 'Chaque exercice se termine avec des données. Pas des impressions.', + + // Card body concatenations from hub builder (eyebrow — tagline) + "VR Hardware \u{2014} Wireless. 5K. Engineered for daily operational use." + => "Matériel VR \u{2014} Sans fil. 5K. Conçu pour l'usage opérationnel quotidien.", + "Software & AI \u{2014} Behavior, not scripts." + => "Logiciel & IA \u{2014} Du comportement, pas du script.", + "Weapons & Tracking \u{2014} The weapon you train with is the weapon you carry." + => "Armes & Tracking \u{2014} L'arme avec laquelle tu t'entraînes est celle que tu portes.", + "Scenarios Library \u{2014} Eleven environments. More than fifty scenarios. New ones every quarter." + => "Bibliothèque de scénarios \u{2014} Onze environnements. Plus de cinquante scénarios. De nouveaux chaque trimestre.", + "Instructor Cockpit \u{2014} The cockpit between the simulation and the lesson." + => "Cockpit instructeur \u{2014} Le cockpit entre la simulation et la leçon.", + "After-Action Review \u{2014} Every drill ends with data. Not impressions." + => "Debriefing (AAR) \u{2014} Chaque exercice se termine avec des données. Pas des impressions.", + + /* ====================== VR Hardware ====================== */ + "PROSERVE's hardware was selected for one purpose: to disappear inside the training. The headset is fully wireless, with hot-swappable batteries. The rendering compute lives in a laptop, not a tower. Wi-Fi 6E delivers headset visuals at 90 Hz, with no cable to trip on, no fixed install to maintain." + => "Le matériel PROSERVE a été choisi dans un seul but : disparaître pendant l'entraînement. Le casque est entièrement sans fil, avec batteries échangeables à chaud. Le calcul de rendu vit dans un portable, pas une tour. Le Wi-Fi 6E délivre les visuels du casque à 90 Hz, sans câble qui traîne, sans installation fixe à maintenir.", + + 'The headset' => 'Le casque', + 'Headset specifications.' => 'Spécifications du casque.', + 'The rendering compute' => 'L\'unité de calcul', + 'Compute specifications.' => 'Spécifications de calcul.', + 'The wireless network' => 'Le réseau sans fil', + 'Wireless network specifications.' => 'Spécifications réseau sans fil.', + + 'Why this matters' => 'Pourquoi ça compte', + 'Why this hardware choice.' => 'Pourquoi ce choix matériel.', + "Wireless 5K visuals at 90 Hz remove the most common immersion-breaker in tactical VR: cable management. Hot-swappable batteries and laptop-class compute let a deployment unfold on any flat surface, without an installer. Inside-out tracking removes the dependency on calibrated external base stations. The whole rig fits in a 20 kg waterproof case \u{2014} and recovers from a fall on concrete. Specifications evolve as VR hardware advances: PROSERVE customers receive hardware refreshes within their support window, at no additional cost." + => "Les visuels 5K sans fil à 90 Hz éliminent le plus fréquent rupteur d'immersion en VR tactique : la gestion des câbles. Batteries échangeables à chaud et calcul portable permettent un déploiement sur toute surface plane, sans installateur. Le tracking inside-out élimine la dépendance aux stations externes calibrées. L'ensemble tient dans un coffre étanche de 20 kg \u{2014} et résiste à une chute sur béton. Les spécifications évoluent avec le matériel VR : les clients PROSERVE reçoivent les mises à niveau matériel dans leur fenêtre de support, sans coût supplémentaire.", + + /* ====================== Software & AI ====================== */ + "PROSERVE's software stack is built on Unreal Engine 5 and a proprietary AI engine that gives every civilian and adversary in the simulation an internal state \u{2014} vision, hearing, courage, caution, aggression. The same scenario, with the same starting conditions, never plays out the same way twice." + => "La pile logicielle PROSERVE est construite sur Unreal Engine 5 et un moteur IA propriétaire qui donne à chaque civil et adversaire de la simulation un état interne \u{2014} vision, ouïe, courage, prudence, agressivité. Le même scénario, avec les mêmes conditions de départ, ne se joue jamais deux fois pareil.", + + 'Powered by Unreal Engine 5' => 'Propulsé par Unreal Engine 5', + 'Photoreal real-time rendering.' => 'Rendu photoréaliste en temps réel.', + 'Real-time photoreal rendering. Volumetric lighting. Dynamic global illumination. Material-aware ballistic surfaces. Day, night, and emergency-light conditions. NVIDIA RTX-accelerated effects.' + => "Rendu photoréaliste en temps réel. Éclairage volumétrique. Illumination globale dynamique. Surfaces balistiques sensibles aux matériaux. Conditions de jour, nuit, et éclairage d'urgence. Effets accélérés NVIDIA RTX.", + + 'Autonomous AI agents' => 'Agents IA autonomes', + 'What the agents perceive and do.' => 'Ce que les agents perçoivent et font.', + 'Vision' => 'Vision', + 'Line-of-sight, peripheral awareness, recognition under stress.' + => "Ligne de mire, vision périphérique, reconnaissance sous stress.", + 'Hearing' => 'Ouïe', + 'Gunfire, footsteps, voice, environmental noise.' + => "Tirs, pas, voix, bruits ambiants.", + 'Memory' => 'Mémoire', + 'They remember where they last saw a threat.' + => "Ils se souviennent où ils ont vu une menace pour la dernière fois.", + 'Movement' => 'Déplacement', + 'Move freely through complex environments \u{2014} staircases, corners, multi-room interiors.' + => "Se déplacent librement dans des environnements complexes \u{2014} escaliers, angles, intérieurs multi-pièces.", + 'Engagement' => 'Engagement', + 'Take cover, peek, return fire, retreat. Change target dynamically based on threat assessment.' + => "Se mettent à couvert, observent, ripostent, se retirent. Changent de cible dynamiquement selon l'évaluation de la menace.", + 'Coordination' => 'Coordination', + 'Coordinate with other agents (squad behavior).' + => "Se coordonnent avec d'autres agents (comportement d'escouade).", + + 'Configurable personality' => 'Personnalité configurable', + 'Each agent has an internal state.' => 'Chaque agent a un état interne.', + 'Each agent receives a personality profile blending courage, caution, and aggression. A panicked civilian flees in a different direction from a hostile actor under fire. A coordinated cell behaves differently from a lone aggressor. Instructors can preset, randomize, or override personality on the fly from the cockpit.' + => "Chaque agent reçoit un profil de personnalité combinant courage, prudence et agressivité. Un civil paniqué fuit dans une direction différente d'un acteur hostile sous le feu. Une cellule coordonnée se comporte différemment d'un agresseur isolé. Les instructeurs peuvent prédéfinir, randomiser ou outrepasser la personnalité à la volée depuis le cockpit.", + + 'Real-time ballistic simulation' => 'Simulation balistique en temps réel', + 'Physics-driven ballistics.' => 'Balistique pilotée par la physique.', + + 'Local-only operation' => 'Fonctionnement strictement local', + 'No cloud. No telemetry. No exposure.' => 'Pas de cloud. Pas de télémétrie. Pas d\'exposition.', + + /* ====================== Weapons & Tracking ====================== */ + 'Most VR training tools give the trainee a controller shaped like a pistol. PROSERVE gives the trainee a gas-blowback replica or a real service weapon firing blanks, fitted with our Universal Weapon Digitizer. The weight, the recoil, the sound, the iron-sight alignment \u{2014} all coherent with the carried weapon. Muscle memory is what we are paying to train.' + => "La plupart des outils VR donnent au stagiaire une manette en forme de pistolet. PROSERVE donne une réplique gas-blowback ou une vraie arme de service à blanc, équipée de notre Universal Weapon Digitizer. Le poids, le recul, le son, l'alignement des organes de visée \u{2014} tout est cohérent avec l'arme portée. C'est la mémoire musculaire que nous payons pour entraîner.", + + 'The Universal Weapon Digitizer (UWeD)' => 'L\'Universal Weapon Digitizer (UWeD)', + 'UWeD specifications.' => 'Spécifications UWeD.', + 'UWeD is our proprietary weapon module. It attaches to any Picatinny rail or to compatible mounts, providing position and orientation tracking with recoil-induced perturbation correction.' + => "L'UWeD est notre module d'arme propriétaire. Il s'attache à n'importe quel rail Picatinny ou à des montures compatibles, fournissant le tracking de position et d'orientation avec correction des perturbations dues au recul.", + + 'Dual-channel shot detection' => 'Détection de tir bicanale', + 'No false positives in cooperative fire.' => 'Pas de faux positifs en tir coopératif.', + "Every shot is detected by combining sound and recoil signatures. This dual-channel detection avoids false positives from a teammate's shot fired nearby \u{2014} a critical safeguard for cooperative training where multiple weapons fire in the same room." + => "Chaque tir est détecté en combinant signatures sonore et de recul. Cette détection bicanale évite les faux positifs causés par un tir d'équipier proche \u{2014} garantie critique pour l'entraînement coopératif où plusieurs armes tirent dans la même pièce.", + + 'Standard weapon catalog' => 'Catalogue d\'armes standard', + 'Compatible weapons (non-exhaustive).' => 'Armes compatibles (liste non exhaustive).', + 'Additional weapons supported on request via PROSERVE+.' => 'Autres armes disponibles sur demande via PROSERVE+.', + 'Handguns' => 'Armes de poing', + 'Glock 17 Gen 5 GBB, Glock 19 GBB / CO2, Beretta M9 GBB, Heckler & Koch P45 (Umarex), Heckler & Koch VP9 (Umarex), ELITE IA GBB.' + => 'Glock 17 Gen 5 GBB, Glock 19 GBB / CO2, Beretta M9 GBB, Heckler & Koch P45 (Umarex), Heckler & Koch VP9 (Umarex), ELITE IA GBB.', + 'Rifles & SMGs' => 'Fusils & PM', + 'HK416 (Umarex), HK417 (Umarex), MP7 (Umarex), PMX, Colt M4.' + => 'HK416 (Umarex), HK417 (Umarex), MP7 (Umarex), PMX, Colt M4.', + 'Operating costs' => 'Coûts opérationnels', + "CO2 refill: ~\u{20AC}0.25 per cartridge, 30-40 shots per refill. Gas refill: ~\u{20AC}8 per cartridge, 600-800 shots per refill. No live ammunition. No range fees. No decontamination." + => "Recharge CO2 : ~0,25 \u{20AC} par cartouche, 30-40 tirs par recharge. Recharge gaz : ~8 \u{20AC} par cartouche, 600-800 tirs par recharge. Pas de munitions réelles. Pas de frais de stand. Pas de décontamination.", + + /* ====================== Scenarios ====================== */ + 'The PROSERVE scenarios library covers the operational reality of contemporary security forces \u{2014} from technical marksmanship to multi-room cooperative CQB. Every scenario was specified with serving and former operators. Updates roll out four times a year.' + => "La bibliothèque de scénarios PROSERVE couvre la réalité opérationnelle des forces de sécurité contemporaines \u{2014} du tir de précision technique au CQB coopératif multi-pièces. Chaque scénario a été spécifié avec des opérateurs en service et anciens. Les mises à jour sortent quatre fois par an.", + + 'Static training' => 'Entraînement statique', + 'Marksmanship & decision.' => 'Précision & décision.', + 'Dynamic training' => 'Entraînement dynamique', + 'Mobile, decision-making.' => 'Mobile, décisionnel.', + 'Multi-trainee training' => 'Entraînement multi-stagiaires', + 'Tactical progression.' => 'Progression tactique.', + + 'Variability' => 'Variabilité', + 'Same starting conditions, different outcomes.' => 'Mêmes conditions de départ, issues différentes.', + 'Each scenario has between five and eight pre-built variations. AI behavior introduces additional variability \u{2014} same starting conditions, different outcomes. Day, night, and emergency-light conditions are toggleable on most scenarios.' + => "Chaque scénario a entre cinq et huit variations pré-construites. Le comportement de l'IA introduit une variabilité supplémentaire \u{2014} mêmes conditions de départ, issues différentes. Les conditions jour, nuit et éclairage d'urgence sont activables sur la plupart des scénarios.", + + 'Custom scenarios' => 'Scénarios sur mesure', + 'PROSERVE+ produces bespoke scenarios on demand.' => 'PROSERVE+ produit des scénarios sur mesure à la demande.', + 'Your local geography, your operational doctrine, your area-specific threats. Production lead time: 1 to 3 months per environment.' + => "Votre géographie locale, votre doctrine opérationnelle, vos menaces spécifiques. Délai de production : 1 à 3 mois par environnement.", + 'Discuss a custom scenario' => 'Discuter d\'un scénario sur mesure', + + /* ====================== Instructor Cockpit ====================== */ + "PROSERVE's instructor cockpit is what separates a simulator from a training tool. From a dedicated workstation, the instructor orchestrates every detail of the scenario in real time \u{2014} without pausing it, without breaking the trainee's immersion, with complete tactical and pedagogical visibility." + => "Le cockpit instructeur PROSERVE est ce qui sépare un simulateur d'un outil d'entraînement. Depuis un poste dédié, l'instructeur orchestre chaque détail du scénario en temps réel \u{2014} sans le mettre en pause, sans rompre l'immersion du stagiaire, avec une visibilité tactique et pédagogique complète.", + + 'Real-time supervision' => 'Supervision en temps réel', + 'See what no one else can.' => 'Voir ce que personne d\'autre ne peut.', + 'Walk through walls' => 'Traverser les murs', + 'Free camera control. The instructor sees the engagement from any angle: top-down tactical, third-person trail, fixed positions, or directly through walls.' + => "Contrôle de caméra libre. L'instructeur voit l'engagement sous tout angle : tactique zénithal, suivi en troisième personne, positions fixes, ou directement à travers les murs.", + "Switch into any trainee's view" => 'Basculer dans la vue de n\'importe quel stagiaire', + 'First-person view of any trainee, in real time. See exactly what they see \u{2014} and what they failed to see.' + => "Vue à la première personne de n'importe quel stagiaire, en temps réel. Voyez exactement ce qu'ils voient \u{2014} et ce qu'ils n'ont pas vu.", + 'Toggle overlays' => 'Activer les overlays', + 'Shot traces (with hit/miss color coding). Field of view of each trainee. Threat highlighting (display all hostile actors as pulsing markers, useful for instructor briefing).' + => "Traces de tir (avec code couleur impact/raté). Champ de vision de chaque stagiaire. Surlignage des menaces (affiche tous les acteurs hostiles comme marqueurs pulsants, utile pour le briefing instructeur).", + + 'Scenario manipulation in real time' => 'Manipulation du scénario en temps réel', + 'Reshape the drill without pausing it.' => 'Refaçonnez l\'exercice sans l\'interrompre.', + + 'Statistics on the fly' => 'Statistiques à la volée', + 'Live data during the session.' => 'Données en direct pendant la session.', + + 'Multi-instructor support' => 'Support multi-instructeurs', + 'Multiple instructors, one cockpit.' => 'Plusieurs instructeurs, un cockpit.', + 'On PROSERVE ACADEMY configurations, multiple instructors can supervise simultaneously \u{2014} one in command of scenario manipulation, one focused on individual trainee follow-up, one running statistics review.' + => "Sur les configurations PROSERVE ACADEMY, plusieurs instructeurs peuvent superviser simultanément \u{2014} un aux commandes de la manipulation du scénario, un focalisé sur le suivi individuel, un sur la revue des statistiques.", + + /* ====================== After-Action Review ====================== */ + "Conventional after-action review depends on instructor recall and trainee self-assessment \u{2014} both unreliable. PROSERVE's AAR records every event of every session and lets you replay it from any angle, at any speed, with overlays the human eye couldn't catch in real time." + => "Le debriefing conventionnel dépend du souvenir de l'instructeur et de l'auto-évaluation du stagiaire \u{2014} les deux peu fiables. L'AAR PROSERVE enregistre chaque événement de chaque session et permet de le rejouer sous tout angle, à toute vitesse, avec des overlays que l'\u{153}il humain ne pouvait capter en temps réel.", + + 'Replay from any angle' => 'Rejouer sous tout angle', + 'Camera modes.' => 'Modes caméra.', + 'Overlays' => 'Overlays', + 'See what real-time observation missed.' => 'Voir ce que l\'observation en temps réel a manqué.', + + 'Per-trainee statistics' => 'Statistiques par stagiaire', + 'Individual performance data.' => 'Données de performance individuelle.', + + 'Unit-level dashboards' => 'Tableaux de bord par unité', + 'Aggregated metrics for command.' => 'Métriques agrégées pour le commandement.', + + 'Comparison and benchmarking' => 'Comparaison et benchmark', + 'The data is the curriculum.' => 'La donnée est le programme.', + "Compare a recruit's performance to the unit median. Compare today's drill to last month's. Compare two squads on the same scenario. The data is the curriculum." + => "Comparez la performance d'une recrue à la médiane de l'unité. Comparez l'exercice du jour à celui du mois dernier. Comparez deux escouades sur le même scénario. La donnée est le programme.", + ]; +} + +/* ----------------------------------------------------------------- + URL trigger — template_redirect (Bricks controls loaded by then) + ----------------------------------------------------------------- */ +add_action('template_redirect', function () { + if (empty($_GET['av_generate_technology'])) return; + if (! in_array($_SERVER['SERVER_NAME'] ?? '', ['localhost', '127.0.0.1'], true)) wp_die('localhost only'); + + $data_file = 'C:\\ASTERION\\GIT\\WP2026\\content-source\\technology-data.php'; + if (! file_exists($data_file)) wp_die("data file not found: $data_file"); + + $technology = include $data_file; + if (! is_array($technology)) wp_die('technology data not array'); + + $parent_id = av_ensure_hub_page('technology', 'Technology'); + $report = ["Rebuilding /technology/ hub + child detail pages..."]; + + // Hub + $hub_elements = av_build_technology_hub_elements($technology); + $hub_write = av_write_bricks_page_elements($parent_id, $hub_elements); + $report[] = " HUB → page #$parent_id (rebuilt) — " . count($hub_elements) + . " elements (json={$hub_write['json_len']}, stored={$hub_write['stored_len']})"; + + // Detail pages + foreach ($technology as $slug => $d) { + $page = av_upsert_detail_page($slug, 'technology', $d); + $report[] = " $slug → page #{$page['id']} ({$page['action']}) — {$page['element_count']} elements (json={$page['json_len']}, stored={$page['stored_len']})"; + } + + header('Content-Type: text/plain; charset=utf-8'); + echo implode("\n", $report); + exit; +}); + +/* ----------------------------------------------------------------- + Technology hub : hero + 6-card grid (one per pillar) + closer CTA + ----------------------------------------------------------------- */ +function av_build_technology_hub_elements($technology) { + $els = []; + + // HERO + $els = array_merge($els, av_build_hero('thub', [ + 'eyebrow' => 'PROSERVE platform', + 'tagline' => "Six pillars. One coherent platform.", + 'hero_lead' => "VR hardware, autonomous AI, real weapons with universal tracking, fifty-plus scenarios, instructor cockpit, frame-level after-action review. Each pillar engineered to support training under operational conditions \u{2014} every day, every shift.", + 'ctas' => [ + ['label' => 'Book a demo', 'href' => '/request-demo/', 'variant' => 'primary'], + ['label' => 'Request a T&E Kit', 'href' => '/request-te-kit/', 'variant' => 'secondary'], + ], + ])); + + // 6-card grid — one card per pillar (will render 3+3 thanks to count=6 case) + $cards_section = [ + 'eyebrow' => '', + 'title' => 'Explore each pillar of the platform.', + 'items' => [], + ]; + foreach ($technology as $slug => $d) { + $cards_section['items'][] = [ + 'title' => $d['title'], + 'body' => ($d['eyebrow'] ?? '') . " \u{2014} " . ($d['tagline'] ?? ''), + ]; + } + $els = array_merge($els, av_build_cards_section('thubgrid', 0, $cards_section, 'var(--color-bg-subtle)')); + + // Append "Learn more →" link in each card + $i = 0; + foreach ($technology as $slug => $d) { + $kid = "kthubgridc0x{$i}"; + $lid = "lnthubgridc0x{$i}"; + $els[] = av_text_link($lid, $kid, "Learn more \u{2192}", "/technology/$slug/"); + foreach ($els as &$el) { + if (($el['id'] ?? '') === $kid) { + $el['children'][] = $lid; + } + } + unset($el); + $i++; + } + + // CLOSER CTA + $els = array_merge($els, av_build_closer_cta('thub')); + + return $els; +} diff --git a/tools/av-translate-editorial-fr.php b/tools/av-translate-editorial-fr.php new file mode 100644 index 0000000..53729eb --- /dev/null +++ b/tools/av-translate-editorial-fr.php @@ -0,0 +1,40 @@ + 'Pourquoi Asterion', + 'about' => 'À propos', + 'customers' => 'Clients', + 'trust' => 'Confiance & conformité', + 'partners' => 'Partenaires', + ]; + + foreach (['why-asterion', 'about', 'customers', 'trust', 'partners'] as $slug) { + $en = get_page_by_path($slug); + if (! $en) { + echo " $slug : EN page not found — run ?av_generate_editorial=1 first\n"; + continue; + } + $fr_id = av_translate_page_fr($en->ID, $slug, $fr_titles[$slug]); + if ($fr_id) { + echo " $slug : EN #{$en->ID} → FR #$fr_id\n"; + } else { + echo " $slug : FR creation FAILED\n"; + } + } + + echo "\nDONE. Now run /?av_generate_editorial=1 again to push translated structure to FR via auto-sync.\n"; + exit; +}); diff --git a/tools/av-translate-forms-fr.php b/tools/av-translate-forms-fr.php new file mode 100644 index 0000000..5b829eb --- /dev/null +++ b/tools/av-translate-forms-fr.php @@ -0,0 +1,34 @@ + 'Réserver une démo', + 'request-te-kit' => 'Demander un kit T&E', + 'request-quote' => 'Demander un devis', + 'contact' => 'Contact', + ]; + + foreach (['request-demo', 'request-te-kit', 'request-quote', 'contact'] as $slug) { + $en = get_page_by_path($slug); + if (! $en) { echo " $slug : EN not found — skip\n"; continue; } + $fr_id = av_translate_page_fr($en->ID, $slug, $fr_titles[$slug]); + echo $fr_id + ? " $slug : EN #{$en->ID} → FR #$fr_id\n" + : " $slug : FR creation FAILED\n"; + } + + echo "\nDONE. Run /?av_generate_forms=1 to push translated structure.\n"; + exit; +}); diff --git a/tools/av-translate-home-fr.php b/tools/av-translate-home-fr.php new file mode 100644 index 0000000..8e300dd --- /dev/null +++ b/tools/av-translate-home-fr.php @@ -0,0 +1,40 @@ +ID, 'home', 'Accueil'); + echo $fr_id + ? "Home EN #{$en->ID} → FR #$fr_id\n" + : "Home FR creation FAILED\n"; + + // Also set FR home as static front page for FR language via WPML + if ($fr_id) { + global $sitepress; + if ($sitepress && method_exists($sitepress, 'set_setting')) { + $settings = (array) $sitepress->get_setting('language_negotiation_type'); + // WPML language-specific home pages are stored as 'translated-flags' or + // similar. The simplest approach is to register the FR home through + // WPML's icl_object_id mapping — already done via set_element_language_details. + } + } + + echo "\nNow run /?av_generate_home=1 to push translated structure.\n"; + exit; +}); diff --git a/tools/av-translate-technology-fr.php b/tools/av-translate-technology-fr.php new file mode 100644 index 0000000..98085cc --- /dev/null +++ b/tools/av-translate-technology-fr.php @@ -0,0 +1,59 @@ + 'Technologie', + 'vr-hardware' => 'Matériel VR', + 'software-ai' => 'Logiciel & IA', + 'weapons-tracking' => 'Armes & Tracking', + 'scenarios' => 'Scénarios', + 'instructor-cockpit' => 'Cockpit instructeur', + 'after-action-review' => 'Debriefing (AAR)', + ]; + + // 1) Hub FR + $fr_id = av_translate_page_fr($hub->ID, 'technology', $fr_titles['technology']); + if ($fr_id) { + echo "HUB EN #{$hub->ID} → FR #$fr_id (slug: technology)\n"; + } else { + echo "HUB EN #{$hub->ID} : FR creation FAILED\n"; + } + + // 2) Detail pages FR + foreach (['vr-hardware', 'software-ai', 'weapons-tracking', 'scenarios', 'instructor-cockpit', 'after-action-review'] as $slug) { + $en = get_page_by_path("technology/$slug"); + if (! $en) { + echo " $slug : EN page not found — skipping\n"; + continue; + } + $fr_id = av_translate_page_fr($en->ID, $slug, $fr_titles[$slug]); + if ($fr_id) { + echo " $slug : EN #{$en->ID} → FR #$fr_id\n"; + } else { + echo " $slug : FR creation FAILED\n"; + } + } + + echo "\nDONE. Now run /?av_generate_technology=1 again to push translated structure to FR via auto-sync.\n"; + exit; +});