Files
AsterionWP2026/tools/av-generate-technology.php
j.foucher 38c0c5031e feat(pages): generate Phases 8-11 — Technology + Editorial + Forms + Home
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) <noreply@anthropic.com>
2026-05-14 19:58:48 +02:00

307 lines
25 KiB
PHP

<?php
/**
* Plugin Name: Asterion — Generate technology pages (one-shot)
* Description: /?av_generate_technology=1 reads content-source/technology-data.php
* and creates/updates : hub /technology/ + 6 child pages (vr-hardware,
* software-ai, weapons-tracking, scenarios, instructor-cockpit,
* after-action-review). Idempotent.
*/
defined('ABSPATH') || exit;
require_once __DIR__ . '/_av-bricks-shared.php';
/* -----------------------------------------------------------------
Register Technology-specific FR strings into the shared dict.
Brand names (PROSERVE, PROSERVE+, UWeD, Asterion VR) verbatim.
Hardware specs (numbers, MHz, GB, mm) often need no translation.
----------------------------------------------------------------- */
add_filter('av_fr_translation_dict', function ($dict) {
return array_merge($dict, av_technology_fr_dict());
});
function av_technology_fr_dict() {
return [
/* ============================ HUB ============================ */
'Technology' => '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;
}