'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' => $item, '_cssClasses' => 'av-list-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; }