chore: phase 1 reset — minimalist child theme + content-source extraction

Resets the child theme to its Bricks-first minimalist form (per RESTART-PLAN.md
phases 1a-1d) and extracts the textual copy into a runtime-independent
/content-source/ directory.

Child theme files removed
- header.php, footer.php, page.php, index.php, front-page.php
- template-parts/{solution,industry,conversion-form}.php
- templates/page-*.php (10 Custom Page Templates)
- inc/render-blocks.php, render-blocks-native.php, render-bricks.php
- inc/shortcodes.php, i18n.php, form-handler.php, seo.php
- assets/css/components.css, assets/js/main.js

Reasoning: Bricks Templates (Header / Footer / Single — Page) and Bricks-edited
pages now drive every screen. Anything emitted from the child theme would
fight Bricks' own rendering and waste CSS specificity battles.

Child theme now contains only
- style.css, functions.php (simplified to ~60 lines), theme.json
- inc/cpt.php (case_study + scenario + industry taxonomy)
- assets/css/tokens.css (untouched — design system source of truth)
- assets/css/utilities.css (trimmed to skip-link + sr-only + focus-visible
  + reduced motion — everything else handled by Bricks)
- assets/fonts/Inter-Variable*.woff2 (RGPD self-hosted)
- README.md spelling out the Bricks-first contract

Content moved to /content-source/
- solutions-data.php, industries-data.php, technology-data.php,
  editorial-data.php, forms-data.php — git mv preserves history
- README.md explaining their dual role: copy reference while building
  in Bricks, and source for programmatic page generation when we
  replicate an archetype across its variants.

Root README.md updated with the new repo layout + a pointer to
RESTART-PLAN.md and the archive/session-2-php-templates branch.

Memory file project_asterion_status.md updated for next-session resume.

What's preserved as before
- /BRIEF-Claude-Code.md, both PDFs, all .txt extracts
- /MISSING-ASSETS.md
- /ThirdParty/ (Bricks + WPML ZIPs, gitignored)
- LocalWP site at localhost:10004, Bricks v2.3.4 + WPML 4.9.3 active
- WPML EN/FR with directory URL strategy
- All Git history, both main and archive/session-2-php-templates branches

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 14:36:19 +02:00
parent 026be81373
commit fba223004f
39 changed files with 217 additions and 5025 deletions

View File

@@ -62,31 +62,46 @@ Puis dans WP Admin → Apparence → Thèmes, activer **Asterion Bricks** (le ch
---
## Approche
**Bricks-first, child theme minimaliste.** Le rendu de chaque page est piloté par Bricks Builder (Templates pour header/footer/archives + pages éditées dans le builder). Le child theme se contente d'apporter le design system (tokens CSS), Inter self-hosté (RGPD) et les Custom Post Types. Voir `wp-content/themes/asterion-bricks/README.md` pour le détail.
Une session de PHP-rendering plus traditionnelle a été archivée dans la branche Git `archive/session-2-php-templates` ainsi que dans `RESTART-PLAN.md` à la racine. Ne pas réintroduire ce pattern sans relire ces deux sources.
## Structure du repo
```
WP2026/
├── BRIEF-Claude-Code.md # brief projet (lecture obligatoire)
├── README.md # ce fichier
├── RESTART-PLAN.md # historique de la décision Bricks-first
├── MISSING-ASSETS.md # tracking des visuels/medias manquants
├── *.pdf # 2 livrables stratégie + design (sources de vérité)
├── ThirdParty/ # gitignored — ZIP plugins commerciaux à déposer manuellement
├── ThirdParty/ # gitignored — ZIP plugins commerciaux
│ ├── bricks.*.zip
│ └── sitepress-multilingual-cms.*.zip
├── content-source/ # copy EN extrait du PDF (référence + génération)
│ ├── solutions-data.php
│ ├── industries-data.php
│ ├── technology-data.php
│ ├── editorial-data.php
│ ├── forms-data.php
│ └── README.md
└── wp-content/
└── themes/
└── asterion-bricks/ # le child theme — coeur du travail
├── style.css # en-tête WP + import tokens
├── functions.php # hooks, registers, queries
├── theme.json # tokens globaux (Gutenberg + Bricks)
├── assets/
│ ├── css/ # tokens.css, components.css, utilities.css
── js/ # vanilla JS, defer
│ ├── fonts/ # Inter + Inter Display self-hosted (RGPD)
── img/
├── templates/ # exports Bricks JSON (versionnés)
├── inc/ # CPT, ACF field groups, SEO schema
└── README.md
└── asterion-bricks/ # child theme minimaliste, voir son README
├── style.css
├── functions.php
├── theme.json
├── README.md
├── inc/
── cpt.php
└── assets/
── css/
│ ├── tokens.css # design system, source de vérité
└── utilities.css # skip-link, sr-only, focus-visible
├── fonts/ # Inter Variable self-hosté
└── img/
```
---

52
content-source/README.md Normal file
View File

@@ -0,0 +1,52 @@
# Content source — copy reference
PHP arrays of all the textual content authored in the Strategy & Content PDF (`Asterion-VR_Refonte-2026_Strategie-et-Contenu.pdf`, sections D.1 to D.29). Each file holds the EN copy for one site section, keyed by page slug.
These files are **not** loaded at runtime. They serve two purposes:
1. **Reference while building Bricks pages** — open the file matching the page you're editing and copy/paste the EN copy into the Bricks elements (Heading, Basic Text, Button…).
2. **Source for programmatic generation** — once a Bricks archetype template is built and validated visually, a one-shot script can iterate over the data here, clone the template, swap in each variant's strings, and create the WP page programmatically. This avoids rebuilding the same layout 46 times for similar pages (e.g. the 4 industry detail pages).
## Files
| File | Pages covered |
|---|---|
| `solutions-data.php` | `/solutions/my-proserve/`, `/solutions/proserve-flex/`, `/solutions/proserve-academy/`, `/solutions/customization/` |
| `industries-data.php` | `/industries/police/`, `/industries/special-forces/`, `/industries/military/`, `/industries/firefighters/` |
| `technology-data.php` | 6 technology pillars under `/technology/` |
| `editorial-data.php` | Why Asterion, About, Customers hub, Trust, Partners |
| `forms-data.php` | Conversion forms (Demo, T&E Kit, Quote, Contact) |
## Schema
Every entry follows roughly the same shape :
```php
'slug-of-page' => [
'eyebrow' => 'Small overline above the H1',
'title' => 'Short page title',
'tagline' => 'Hero H1',
'meta_title' => 'For <head><title> + OpenGraph',
'meta_description' => 'For <head> meta description',
'hero_lead' => 'Long sub-headline under the H1',
'ctas' => [['label' => '...', 'href' => '/...', 'variant' => 'primary'], ],
'sections' => [
['type' => 'prose' , 'eyebrow' => '…', 'title' => '…', 'body' => '…'],
['type' => 'cards' , 'eyebrow' => '…', 'title' => '…', 'items' => [['title' => '…', 'body' => '…'], ]],
['type' => 'list' , 'eyebrow' => '…', 'title' => '…', 'items' => ['line 1', 'line 2', ]],
['type' => 'cta' , 'eyebrow' => '…', 'title' => '…', 'body' => '…', 'ctas' => []],
],
],
```
`forms-data.php` deviates slightly — see its inline header comment.
## Why we keep these in PHP arrays (vs JSON / YAML / Markdown)
- They were extracted programmatically from the PDF and were already used by the previous PHP-rendering approach. Keeping the format means zero re-extraction work.
- A future PHP migration script will `include $file_path` and use the returned array directly — no parsing layer required.
- For the human reading them while building in Bricks, plain PHP arrays are perfectly readable.
## French translations
These files are **EN only**. The FR translations will live inside WordPress, written by the linguist via WPML's Translation Editor against each Bricks-edited page.

View File

@@ -1,53 +1,84 @@
# Asterion Bricks — Child theme
# Asterion Bricks — Child theme (minimalist, Bricks-first)
Custom WordPress child theme for **asterionvr.com**, built on top of [Bricks Builder](https://bricksbuilder.io). Tactical-cinematic design system for the PROSERVE XR training platform.
Custom WordPress child theme for **asterionvr.com**, built on top of [Bricks Builder](https://bricksbuilder.io). The whole site is rendered by Bricks Templates and Bricks-edited pages — this theme exists only to give Bricks a design system, GDPR-compliant fonts, and the project's Custom Post Types.
## Structure
## What this theme does
- 🎨 Loads `assets/css/tokens.css` on the public frontend AND inside the Bricks builder iframe — so every `--color-*`, `--space-*`, `--font-*` variable defined there is available to Bricks elements via the **CSS Class** field or **Custom CSS** panel.
- 🔤 Self-hosts Inter Variable (latin subset) for RGPD compliance, with a `<link rel="preload">` injected on every page.
- 📰 Registers two Custom Post Types — `case_study` (slug `/customers/`) and `scenario` (slug `/scenarios/`) — plus the shared `industry` taxonomy.
- 📐 Adds three image sizes : `av-hero` (2400×1350), `av-card` (800×600), `av-thumb` (400×300).
- 🌐 Ships an `asterion-bricks` text domain for future i18n.
## What this theme does NOT do (intentionally)
- ❌ No `header.php` / `footer.php` / `page.php` / `front-page.php` / `index.php`. Bricks Templates (Header / Footer / Single — Page) drive every screen.
- ❌ No Custom Page Templates (`templates/page-*.php`). Pages render through their Bricks data (`_bricks_page_content_2` post meta).
- ❌ No PHP renderers, Gutenberg block helpers, shortcodes. Content lives in Bricks data and WPML translations.
If you find yourself adding any of the above back, stop and revisit `RESTART-PLAN.md` at the repo root — there are usually better Bricks-native alternatives.
## File map
```
asterion-bricks/
├── style.css # WP theme metadata only (CSS lives in assets/css/)
├── functions.php # enqueues, theme support, includes
├── theme.json # Gutenberg color/typo palette
├── assets/
├── css/
│ ├── tokens.css # design tokens — source of truth
│ │ ├── utilities.css # CSS reset + utility classes (.av-container, etc.)
│ └── components.css # component styles (buttons, cards, header, footer…)
│ ├── js/
│ └── main.js # vanilla JS, defer-loaded
├── fonts/ # self-hosted Inter (RGPD)
│ └── img/ # static visuals
├── templates/ # Bricks template JSON exports (versioned)
└── inc/
├── cpt.php # custom post types: case_study, scenario
└── seo.php # Schema.org JSON-LD overrides
├── style.css # WP theme metadata only
├── functions.php # enqueue tokens, preload font, theme support, CPT include
├── theme.json # Gutenberg color/typo palette aligned with tokens.css
├── README.md # this file
├── inc/
└── cpt.php # case_study, scenario, industry taxonomy
└── assets/
├── css/
│ ├── tokens.css # design system source of truth (single file, big)
│ └── utilities.css # skip-link, sr-only, focus-visible, reduced motion
├── fonts/
├── Inter-Variable.woff2
└── Inter-Variable-Italic.woff2
└── img/
```
## Conventions
Total : ~10 files, < 700 lines of code (with tokens.css being the lion's share).
- **CSS prefix** : `av-` (asterion-vr) on every custom selector to avoid collision with Bricks classes.
- **BEM-light** naming : `.av-card`, `.av-card--feature`, `.av-card__icon`, `.av-card.is-loading`.
- **Zero hardcoded values** — every color, spacing, radius and duration goes through `tokens.css`.
- **JS** : vanilla, defer-loaded, prefer `IntersectionObserver` over scroll listeners.
- **A11y** : keyboard reachable, `:focus-visible` outline, ARIA where needed, contrast ≥ 4.5:1, `prefers-reduced-motion` respected.
## Where the content copy lives
The textual copy from the Strategy & Content PDF (page-by-page EN copy, deliverable D.1 to D.29) lives at the repo root in `/content-source/`. Those are PHP arrays keyed by page slug — they are not loaded at runtime. Their job is twofold:
- **Reference** when building a Bricks page : copy/paste the appropriate strings into the Bricks elements as you build.
- **Source for programmatic generation** when we replicate a Bricks-built archetype across its variants (e.g. the 4 industry detail pages, the 6 technology pillars).
## Activation
1. Ensure Bricks parent is installed at `wp-content/themes/bricks/`.
2. WP Admin → Appearance → Themes → activate **Asterion Bricks**.
1. Install Bricks parent theme (`wp-content/themes/bricks/`) — the ZIP lives in `ThirdParty/`.
2. Install + activate WPML core (also in `ThirdParty/`).
3. WP Admin → Appearance → Themes → activate **Asterion Bricks**.
4. WP Admin → Bricks → Settings → check **Page** in « Builder access · Post types ».
## Performance budget (per BRIEF section 9)
## Tokens to know
| Asset | Budget |
|-----------------|-------------------------------|
| HTML initial | < 100 KB gzipped |
| CSS total | < 50 KB gzipped |
| JS initial | < 200 KB gzipped |
| Hero video | < 2 MB |
| Hero poster | < 150 KB |
| LCP / CLS / TBT | < 2.5s / < 0.1 / < 200ms |
The full design system lives in `assets/css/tokens.css`. The most-referenced tokens:
| Variable | Value |
|---|---|
| `--color-brand-navy` | `#0B1F3A` |
| `--color-brand-gold` | `#C9A45A` |
| `--color-text-primary` | `#0B1F3A` |
| `--font-display` | Inter Display fallback to Inter |
| `--font-body` | Inter |
| `--space-1..32` | 8-pt grid (4px → 128px) |
| `--container-max` | 1280px |
| `--ease-default` | `cubic-bezier(0.16, 1, 0.3, 1)` |
| `--shadow-sm`/`--shadow-md`/`--shadow-lg` | navy-tinted elevation |
Reference them inside Bricks via Custom CSS:
```css
%root% {
background: var(--color-brand-navy);
color: var(--color-text-on-dark);
padding-block: var(--space-20);
}
```
## License
GPL v2 or later. Same as WordPress core.
GPL v2 or later, matching WordPress core.

File diff suppressed because it is too large Load Diff

View File

@@ -1,110 +1,13 @@
/**
* Asterion Bricks — Utilities
* Asterion Bricks — Utilities (minimalist)
*
* Modern CSS reset + reusable utility classes.
* Loaded after tokens.css, before components.css.
*
* Naming : prefix `av-` on every custom class.
* Loaded after tokens.css. Holds the small set of helper classes Bricks
* Builder cannot easily provide on its own (skip link, screen-reader-only
* text, focus ring). Everything else — section padding, container widths,
* button design, card styles — is set inside Bricks via tokens.
*/
/* ============================================================================
Modern CSS reset (inspired by Andy Bell's modern-css-reset, Josh Comeau)
============================================================================ */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }
html {
font-size: 100%;
-webkit-text-size-adjust: 100%;
scroll-behavior: smooth;
text-rendering: optimizeLegibility;
}
body {
font-family: var(--font-body);
font-size: var(--text-body-md);
line-height: var(--leading-body-md);
color: var(--color-text-primary);
background-color: var(--color-bg-default);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
min-height: 100vh;
}
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
height: auto;
}
input, button, textarea, select { font: inherit; color: inherit; }
p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }
a {
color: inherit;
text-decoration: none;
}
ul, ol { list-style: none; }
table { border-collapse: collapse; border-spacing: 0; }
/* ============================================================================
Heading hierarchy — opinionated defaults consumed when an element
carries no Bricks-specific class. Bricks may override, that's expected.
============================================================================ */
h1 {
font-family: var(--font-display);
font-size: var(--text-h1);
line-height: var(--leading-h1);
font-weight: var(--font-weight-bold);
letter-spacing: var(--tracking-snug);
}
h2 {
font-family: var(--font-display);
font-size: var(--text-h2);
line-height: var(--leading-h2);
font-weight: var(--font-weight-bold);
letter-spacing: var(--tracking-snug);
}
h3 {
font-family: var(--font-display);
font-size: var(--text-h3);
line-height: var(--leading-h3);
font-weight: var(--font-weight-semibold);
letter-spacing: var(--tracking-snug);
}
h4 {
font-family: var(--font-display);
font-size: var(--text-h4);
line-height: var(--leading-h4);
font-weight: var(--font-weight-semibold);
}
p {
max-width: 72ch; /* prose readability per Design Handoff section 2.2 */
}
/* ============================================================================
Focus visible — keyboard accessibility
Removes outline only when focus came from the mouse; preserves it for keyboard.
============================================================================ */
:focus { outline: none; }
:focus-visible {
outline: var(--focus-ring-width) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
}
/* ============================================================================
Skip link — first focusable element on every page (a11y)
============================================================================ */
/* === A11y skip link — first focusable element on every page === */
.av-skip-link {
position: absolute;
top: -40px;
@@ -114,6 +17,7 @@ p {
color: var(--color-text-on-dark);
font-size: var(--text-body-sm);
font-weight: var(--font-weight-semibold);
text-decoration: none;
z-index: var(--z-toast);
border-bottom-right-radius: var(--radius-md);
}
@@ -122,10 +26,7 @@ p {
top: 0;
}
/* ============================================================================
Visually hidden — content readable by screen readers, hidden visually
============================================================================ */
/* === Visually hidden — readable by screen readers, hidden visually === */
.av-sr-only {
position: absolute;
width: 1px;
@@ -138,77 +39,25 @@ p {
border: 0;
}
/* ============================================================================
Container — centred max-width with responsive padding
============================================================================ */
.av-container {
max-width: var(--container-max);
margin-inline: auto;
padding-inline: var(--container-padding-mobile);
width: 100%;
/* === Focus visible — keyboard accessibility on every focusable element === */
:focus { outline: none; }
:focus-visible {
outline: var(--focus-ring-width, 2px) solid var(--focus-ring-color, var(--color-info));
outline-offset: var(--focus-ring-offset, 2px);
}
@media (min-width: 768px) {
.av-container { padding-inline: var(--container-padding-tablet); }
}
@media (min-width: 1280px) {
.av-container { padding-inline: var(--container-padding-desktop); }
}
.av-container--narrow {
max-width: 800px; /* article body */
}
/* ============================================================================
Tap target minimum — WCAG AA / Apple HIG (44×44px)
============================================================================ */
/* === Touch target minimum (WCAG AA / Apple HIG) === */
.av-tap-target {
min-width: var(--tap-target-min);
min-height: var(--tap-target-min);
min-width: var(--tap-target-min, 44px);
min-height: var(--tap-target-min, 44px);
}
/* ============================================================================
Stack helpers — vertical rhythm via owl-selector
============================================================================ */
.av-stack > * + * { margin-top: var(--av-stack-gap, var(--space-4)); }
.av-stack--sm > * + * { margin-top: var(--space-2); }
.av-stack--md > * + * { margin-top: var(--space-4); }
.av-stack--lg > * + * { margin-top: var(--space-8); }
.av-stack--xl > * + * { margin-top: var(--space-12); }
/* ============================================================================
Section padding helpers (vertical rhythm between page sections)
============================================================================ */
.av-section {
padding-block: var(--space-16);
/* === Reduced motion — respect user preference === */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
@media (min-width: 1024px) {
.av-section { padding-block: var(--space-20); }
}
.av-section--hero {
padding-block: var(--space-24);
}
@media (min-width: 1024px) {
.av-section--hero { padding-block: var(--space-32); }
}
.av-section--dark {
background-color: var(--color-bg-dark);
color: var(--color-text-on-dark);
}
/* ============================================================================
Print
============================================================================ */
@media print {
.av-skip-link, .av-no-print { display: none !important; }
body { color: #000; background: #fff; }
a::after { content: " (" attr(href) ")"; font-size: 0.8em; }
}

View File

@@ -1,149 +0,0 @@
/**
* Asterion Bricks — main JS
*
* Vanilla JS, defer-loaded. Wraps everything in DOMContentLoaded.
* Performance budget : initial bundle < 200 KB gzipped.
*
* Modules planned :
* - Header scroll behavior (background swap after 80px)
* - Mega menu trigger (hover desktop, click touch, ESC closes)
* - Mobile drawer (full-screen overlay, accordion sections)
* - Scroll-triggered fade-in (IntersectionObserver, threshold 0.15)
* - Stat callouts count-up (1000ms ease-out)
* - Hero video lazy-load + reduced-motion fallback to poster
*/
(function () {
'use strict';
/**
* Detect reduced motion once and expose a flag for downstream modules.
* Updated reactively if the user toggles the setting mid-session.
*/
const reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
let prefersReducedMotion = reducedMotionQuery.matches;
reducedMotionQuery.addEventListener('change', (e) => {
prefersReducedMotion = e.matches;
});
/**
* Header scroll behavior.
* After 80px scroll, header gets a `is-scrolled` class
* (consumed by .av-header.is-scrolled rule in components.css).
*/
function initHeaderScroll() {
const header = document.querySelector('.av-header');
if (!header) return;
let ticking = false;
const onScroll = () => {
if (!ticking) {
window.requestAnimationFrame(() => {
header.classList.toggle('is-scrolled', window.scrollY > 80);
ticking = false;
});
ticking = true;
}
};
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
}
/**
* Scroll-triggered fade-in (Design Handoff section 5.3).
* Sections fade-in + slide-up 16px on entry, threshold 0.15, trigger once.
* Skipped entirely if user prefers reduced motion.
*/
function initScrollReveal() {
if (prefersReducedMotion) return;
const targets = document.querySelectorAll('[data-av-reveal]');
if (!targets.length) return;
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-revealed');
observer.unobserve(entry.target);
}
});
}, { threshold: 0.15, rootMargin: '0px 0px -10% 0px' });
targets.forEach((target) => observer.observe(target));
}
/**
* Stat count-up (Design Handoff section 5.3).
* Element : <span data-av-countup="0|target|duration">target</span>
* Animates from 0 to target value over duration ms, ease-out.
*/
function initStatCountUp() {
if (prefersReducedMotion) return;
const stats = document.querySelectorAll('[data-av-countup]');
if (!stats.length) return;
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const el = entry.target;
const target = parseFloat(el.dataset.avCountup) || 0;
const duration = parseInt(el.dataset.avCountupDuration || '1000', 10);
const start = performance.now();
const tick = (now) => {
const elapsed = Math.min((now - start) / duration, 1);
// ease-out cubic
const eased = 1 - Math.pow(1 - elapsed, 3);
el.textContent = Math.round(target * eased).toLocaleString();
if (elapsed < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
observer.unobserve(el);
});
}, { threshold: 0.5 });
stats.forEach((s) => observer.observe(s));
}
/**
* Mobile drawer toggle.
* Trigger : <button class="av-drawer-toggle" aria-controls="av-drawer">
* Drawer : <div id="av-drawer" class="av-drawer">
*/
function initMobileDrawer() {
const toggle = document.querySelector('.av-drawer-toggle');
const drawer = document.getElementById('av-drawer');
if (!toggle || !drawer) return;
const open = () => {
drawer.classList.add('is-open');
toggle.setAttribute('aria-expanded', 'true');
document.body.style.overflow = 'hidden';
};
const close = () => {
drawer.classList.remove('is-open');
toggle.setAttribute('aria-expanded', 'false');
document.body.style.overflow = '';
};
toggle.addEventListener('click', () => {
drawer.classList.contains('is-open') ? close() : open();
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && drawer.classList.contains('is-open')) close();
});
}
/**
* Boot.
*/
document.addEventListener('DOMContentLoaded', () => {
initHeaderScroll();
initScrollReveal();
initStatCountUp();
initMobileDrawer();
});
})();

View File

@@ -1,104 +0,0 @@
<?php
/**
* Asterion Bricks — Global footer
*
* Per Design Handoff section 3.5 :
* - 5 columns desktop : Solutions / Industries / Why Asterion / Resources / Newsletter
* - Stack on mobile (could become accordions later)
* - Bottom strip : copyright · privacy · terms · cookies · social · language switcher
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
$av_year = date_i18n('Y');
// Mirror the bail-out in header.php: don't emit our footer chrome inside
// the Bricks builder canvas/iframe.
if (function_exists('bricks_is_builder') && bricks_is_builder()) {
wp_footer();
echo '</body></html>';
return;
}
?>
</main><!-- /#main -->
<footer class="av-footer" role="contentinfo">
<div class="av-container">
<div class="av-footer__cols">
<div class="av-footer__brand">
<a href="<?php echo esc_url(home_url('/')); ?>" class="av-header__logo" aria-label="Asterion VR — home">
ASTERION&nbsp;VR
</a>
<p>
<?php echo esc_html(av_t('The modular, sovereign XR platform for European security forces. Train like the threat is now.', 'asterion-bricks')); ?>
</p>
</div>
<div class="av-footer__col">
<h2 class="av-footer__col-title"><?php echo esc_html(av_t('Solutions', 'asterion-bricks')); ?></h2>
<ul class="av-footer__list">
<li><a href="<?php echo esc_url(home_url('/solutions/my-proserve/')); ?>">MY PROSERVE</a></li>
<li><a href="<?php echo esc_url(home_url('/solutions/proserve-flex/')); ?>">PROSERVE FLEX</a></li>
<li><a href="<?php echo esc_url(home_url('/solutions/proserve-academy/')); ?>">PROSERVE ACADEMY</a></li>
<li><a href="<?php echo esc_url(home_url('/solutions/customization/')); ?>">PROSERVE+</a></li>
</ul>
</div>
<div class="av-footer__col">
<h2 class="av-footer__col-title"><?php echo esc_html(av_t('Industries', 'asterion-bricks')); ?></h2>
<ul class="av-footer__list">
<li><a href="<?php echo esc_url(home_url('/industries/police/')); ?>"><?php echo esc_html(av_t('Police', 'asterion-bricks')); ?></a></li>
<li><a href="<?php echo esc_url(home_url('/industries/special-forces/')); ?>"><?php echo esc_html(av_t('Special Forces', 'asterion-bricks')); ?></a></li>
<li><a href="<?php echo esc_url(home_url('/industries/military/')); ?>"><?php echo esc_html(av_t('Military', 'asterion-bricks')); ?></a></li>
<li><a href="<?php echo esc_url(home_url('/industries/firefighters/')); ?>"><?php echo esc_html(av_t('Firefighters', 'asterion-bricks')); ?></a></li>
</ul>
</div>
<div class="av-footer__col">
<h2 class="av-footer__col-title"><?php echo esc_html(av_t('Why Asterion', 'asterion-bricks')); ?></h2>
<ul class="av-footer__list">
<li><a href="<?php echo esc_url(home_url('/about/')); ?>"><?php echo esc_html(av_t('Our story', 'asterion-bricks')); ?></a></li>
<li><a href="<?php echo esc_url(home_url('/customers/')); ?>"><?php echo esc_html(av_t('Customers', 'asterion-bricks')); ?></a></li>
<li><a href="<?php echo esc_url(home_url('/trust/')); ?>"><?php echo esc_html(av_t('Trust & Compliance', 'asterion-bricks')); ?></a></li>
<li><a href="<?php echo esc_url(home_url('/partners/')); ?>"><?php echo esc_html(av_t('Partners', 'asterion-bricks')); ?></a></li>
</ul>
</div>
<div class="av-footer__newsletter">
<h2 class="av-footer__col-title"><?php echo esc_html(av_t('Newsletter', 'asterion-bricks')); ?></h2>
<p style="color:var(--color-text-on-dark-muted);font-size:var(--text-body-sm);">
<?php echo esc_html(av_t('Insights from the front of immersive training. One email per month. No spam.', 'asterion-bricks')); ?>
</p>
<form class="av-footer__newsletter-form" method="post" action="#" aria-label="<?php esc_attr_e('Newsletter signup', 'asterion-bricks'); ?>">
<label for="av-newsletter-email" class="av-sr-only"><?php echo esc_html(av_t('Your email', 'asterion-bricks')); ?></label>
<input type="email" id="av-newsletter-email" name="email" class="av-input" placeholder="<?php esc_attr_e('your@email.com', 'asterion-bricks'); ?>" required>
<button type="submit" class="av-btn av-btn--primary av-btn--md"><?php echo esc_html(av_t('Subscribe', 'asterion-bricks')); ?></button>
</form>
</div>
</div>
<div class="av-footer__bottom">
<div>© <?php echo esc_html($av_year); ?> Asterion VR. <?php echo esc_html(av_t('All rights reserved.', 'asterion-bricks')); ?></div>
<div class="av-footer__bottom-links">
<a href="<?php echo esc_url(home_url('/legal/privacy/')); ?>"><?php echo esc_html(av_t('Privacy', 'asterion-bricks')); ?></a>
<a href="<?php echo esc_url(home_url('/legal/terms/')); ?>"><?php echo esc_html(av_t('Terms', 'asterion-bricks')); ?></a>
<a href="<?php echo esc_url(home_url('/legal/cookies/')); ?>"><?php echo esc_html(av_t('Cookies', 'asterion-bricks')); ?></a>
<a href="<?php echo esc_url(home_url('/legal/notice/')); ?>"><?php echo esc_html(av_t('Legal notice', 'asterion-bricks')); ?></a>
</div>
<div class="av-footer__social">
<a href="https://www.linkedin.com/company/asterion-vr/" rel="noopener" aria-label="LinkedIn">LinkedIn</a>
·
<a href="https://www.youtube.com/" rel="noopener" aria-label="YouTube">YouTube</a>
</div>
</div>
</div>
</footer>
<?php wp_footer(); ?>
</body>
</html>

View File

@@ -1,377 +0,0 @@
<?php
/**
* Asterion Bricks — Front page (home)
*
* Implements the 9-section home wireframe from the Design Handoff
* (section 4.1) using the EN copy from Strategy & Content (D.1).
*
* 1. Hero — dark, full-bleed video, signature
* 2. Trust bar — sector marks + 4 inline metrics
* 3. Solutions — 3 cards (MY / FLEX / ACADEMY)
* 4. Industries — 4 cards 2x2 (Police / SF / Military / Firefighters)
* 5. Feature heroes — 3 alternating (UWeD / Instructor Cockpit / AAR)
* 6. Featured case — single highlighted case
* 7. Testimonials — 3 quotes (slider becomes JS-driven later)
* 8. Latest insights — 3 blog cards
* 9. Conversion banner — Demo / T&E / Quote
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
get_header();
$home_url = home_url('/');
$theme = get_stylesheet_directory_uri();
?>
<!-- ============================================================== -->
<!-- 1. HERO -->
<!-- ============================================================== -->
<section class="av-hero" aria-labelledby="hero-headline">
<div class="av-hero__media" aria-hidden="true">
<?php /* Hero video placeholder. Replace with a 25-35s loop, MP4 H.264 + WebM, < 2 MB, per Design Handoff 5.4. */ ?>
<div style="width:100%;height:100%;background:
radial-gradient(ellipse at 20% 30%, rgba(201,164,90,0.15), transparent 60%),
radial-gradient(ellipse at 80% 70%, rgba(30,91,168,0.20), transparent 60%),
linear-gradient(135deg, #081427 0%, #0B1F3A 50%, #13294B 100%);"></div>
</div>
<div class="av-container">
<div class="av-hero__inner">
<p class="av-hero__eyebrow"><?php esc_html_e('Modular XR training for security forces', 'asterion-bricks'); ?></p>
<h1 id="hero-headline" class="av-hero__headline">Your Last Mistake Should Be in VR.</h1>
<p class="av-hero__sub">
PROSERVE&trade; is the modular, fully portable XR platform built for police, special forces, military and firefighters. Deploy in five minutes. Train anywhere. Debrief everything.
</p>
<div class="av-hero__ctas">
<a href="<?php echo esc_url($home_url . 'request-demo/'); ?>" class="av-btn av-btn--primary av-btn--xl"><?php esc_html_e('Request a Demo', 'asterion-bricks'); ?></a>
<a href="#feature-heroes" class="av-btn av-btn--on-dark av-btn--secondary av-btn--xl"><?php esc_html_e('Watch the 90-second film', 'asterion-bricks'); ?></a>
</div>
</div>
</div>
</section>
<!-- ============================================================== -->
<!-- 2. TRUST BAR -->
<!-- ============================================================== -->
<section class="av-logo-strip" aria-label="<?php esc_attr_e('Trusted by', 'asterion-bricks'); ?>">
<div class="av-container">
<p class="av-logo-strip__intro"><?php esc_html_e('Trusted by security forces across Europe', 'asterion-bricks'); ?></p>
<div class="av-logo-strip__row">
<?php /* Real client logos pending NDA confirmation — see MISSING-ASSETS.md. */ ?>
<?php for ($i = 1; $i <= 6; $i++) : ?>
<div class="av-logo-strip__logo">Logo <?php echo (int) $i; ?></div>
<?php endfor; ?>
</div>
<div class="av-logo-strip__metrics">
<div class="av-logo-strip__metric"><?php esc_html_e('10 years of XR research for security forces', 'asterion-bricks'); ?></div>
<div class="av-logo-strip__metric"><?php esc_html_e('11 environments · 50+ scenarios · monthly updates', 'asterion-bricks'); ?></div>
<div class="av-logo-strip__metric"><?php esc_html_e('< 5 minutes from case to active training', 'asterion-bricks'); ?></div>
<div class="av-logo-strip__metric"><?php esc_html_e('Made in France · GDPR-native · works fully offline', 'asterion-bricks'); ?></div>
</div>
</div>
</section>
<!-- ============================================================== -->
<!-- 3. SOLUTIONS -->
<!-- ============================================================== -->
<section class="av-section av-section--dark" aria-labelledby="solutions-title" data-av-reveal>
<div class="av-container">
<div class="av-section__head">
<span class="av-eyebrow av-eyebrow--gold"><?php esc_html_e('Solutions', 'asterion-bricks'); ?></span>
<h2 id="solutions-title" class="av-section__title">Three solutions. One platform. Zero compromise.</h2>
<p class="av-section__lead">
From the lone officer rebuilding muscle memory to a 16-trainee unit running a coordinated assault — PROSERVE scales with your mission, not the other way around.
</p>
</div>
<div class="av-grid-3">
<article class="av-card is-on-dark">
<div class="av-card__media" style="background:linear-gradient(135deg,#13294B,#0B1F3A);"></div>
<div class="av-card__body">
<span class="av-card__eyebrow"><?php esc_html_e('Solo training', 'asterion-bricks'); ?></span>
<h3 class="av-card__title">MY PROSERVE</h3>
<p class="av-card__excerpt">
Mini-case. Anywhere, anytime. Built for the officer who needs to train 30 minutes between two shifts. 9 m² of free space, a headset, a real-recoil replica. The legal minimum becomes the daily standard.
</p>
<div class="av-card__cta">
<a href="<?php echo esc_url($home_url . 'solutions/my-proserve/'); ?>" class="av-link av-link--on-dark av-link--with-arrow"><?php esc_html_e('Discover MY PROSERVE', 'asterion-bricks'); ?></a>
</div>
</div>
</article>
<article class="av-card is-on-dark">
<div class="av-card__media" style="background:linear-gradient(135deg,#13294B,#0B1F3A);"></div>
<div class="av-card__body">
<span class="av-card__eyebrow"><?php esc_html_e('Team training · 1 to 5', 'asterion-bricks'); ?></span>
<h3 class="av-card__title">PROSERVE FLEX</h3>
<p class="av-card__excerpt">
One case per operator. Multi-user cooperative scenarios, instructor PC, full AAR. Deploy on any flat surface from 9 to 100 m². For tactical units that move with their training.
</p>
<div class="av-card__cta">
<a href="<?php echo esc_url($home_url . 'solutions/proserve-flex/'); ?>" class="av-link av-link--on-dark av-link--with-arrow"><?php esc_html_e('Discover PROSERVE FLEX', 'asterion-bricks'); ?></a>
</div>
</div>
</article>
<article class="av-card is-on-dark">
<div class="av-card__media" style="background:linear-gradient(135deg,#13294B,#0B1F3A);"></div>
<div class="av-card__body">
<span class="av-card__eyebrow"><?php esc_html_e('Unit training · 1 to 16', 'asterion-bricks'); ?></span>
<h3 class="av-card__title">PROSERVE ACADEMY</h3>
<p class="av-card__excerpt">
The full training centre, in your facility. Up to 900 m² of free-roam space, instructor cockpit, racked compute, parallel sessions. Built for academies and continuous training programs.
</p>
<div class="av-card__cta">
<a href="<?php echo esc_url($home_url . 'solutions/proserve-academy/'); ?>" class="av-link av-link--on-dark av-link--with-arrow"><?php esc_html_e('Discover PROSERVE ACADEMY', 'asterion-bricks'); ?></a>
</div>
</div>
</article>
</div>
</div>
</section>
<!-- ============================================================== -->
<!-- 4. INDUSTRIES -->
<!-- ============================================================== -->
<section class="av-section" aria-labelledby="industries-title" data-av-reveal>
<div class="av-container">
<div class="av-section__head">
<span class="av-eyebrow"><?php esc_html_e('Industries', 'asterion-bricks'); ?></span>
<h2 id="industries-title" class="av-section__title">Built with operators, for operators.</h2>
<p class="av-section__lead">
PROSERVE is not a generic VR platform repackaged for the public sector. Every scenario, every weapon profile, every instructor metric was specified by serving and former operators in close partnership with our engineering team.
</p>
</div>
<div class="av-grid-4" style="grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));">
<article class="av-card">
<div class="av-card__media" style="background:linear-gradient(135deg,#1F3252,#0B1F3A);"></div>
<div class="av-card__body">
<span class="av-card__eyebrow"><?php esc_html_e('National & Municipal', 'asterion-bricks'); ?></span>
<h3 class="av-card__title"><?php esc_html_e('Police', 'asterion-bricks'); ?></h3>
<p class="av-card__excerpt">Vehicle stops, identity checks, urban CQB, de-escalation, riot containment. Built for the realities of contemporary policing.</p>
<div class="av-card__cta"><a href="<?php echo esc_url($home_url . 'industries/police/'); ?>" class="av-link av-link--with-arrow"><?php esc_html_e('Police training', 'asterion-bricks'); ?></a></div>
</div>
</article>
<article class="av-card">
<div class="av-card__media" style="background:linear-gradient(135deg,#1F3252,#0B1F3A);"></div>
<div class="av-card__body">
<span class="av-card__eyebrow"><?php esc_html_e('Tactical Units', 'asterion-bricks'); ?></span>
<h3 class="av-card__title"><?php esc_html_e('Special Forces', 'asterion-bricks'); ?></h3>
<p class="av-card__excerpt">Hostage rescue, dynamic entries, hostile crowd extraction, sniper-spotter coordination. Built for the units where every error has a name.</p>
<div class="av-card__cta"><a href="<?php echo esc_url($home_url . 'industries/special-forces/'); ?>" class="av-link av-link--with-arrow"><?php esc_html_e('Special forces training', 'asterion-bricks'); ?></a></div>
</div>
</article>
<article class="av-card">
<div class="av-card__media" style="background:linear-gradient(135deg,#1F3252,#0B1F3A);"></div>
<div class="av-card__body">
<span class="av-card__eyebrow"><?php esc_html_e('Armed forces', 'asterion-bricks'); ?></span>
<h3 class="av-card__title"><?php esc_html_e('Military', 'asterion-bricks'); ?></h3>
<p class="av-card__excerpt">MOUT, convoy escort, FOB defense, IED awareness, joint operation drills. Built for armed forces that deploy fast and adapt faster.</p>
<div class="av-card__cta"><a href="<?php echo esc_url($home_url . 'industries/military/'); ?>" class="av-link av-link--with-arrow"><?php esc_html_e('Military training', 'asterion-bricks'); ?></a></div>
</div>
</article>
<article class="av-card">
<div class="av-card__media" style="background:linear-gradient(135deg,#1F3252,#0B1F3A);"></div>
<div class="av-card__body">
<span class="av-card__eyebrow"><?php esc_html_e('Emergency services', 'asterion-bricks'); ?></span>
<h3 class="av-card__title"><?php esc_html_e('Firefighters', 'asterion-bricks'); ?></h3>
<p class="av-card__excerpt">Structure fires, hazmat, multi-casualty triage, confined-space rescue, industrial accidents. Built for first responders who can't rehearse the worst day at full scale.</p>
<div class="av-card__cta"><a href="<?php echo esc_url($home_url . 'industries/firefighters/'); ?>" class="av-link av-link--with-arrow"><?php esc_html_e('Firefighter training', 'asterion-bricks'); ?></a></div>
</div>
</article>
</div>
</div>
</section>
<!-- ============================================================== -->
<!-- 5. FEATURE HEROES (3 alternating) -->
<!-- ============================================================== -->
<section id="feature-heroes" class="av-section av-section--dark" data-av-reveal>
<div class="av-container">
<div class="av-section__head">
<span class="av-eyebrow av-eyebrow--gold"><?php esc_html_e('Technology', 'asterion-bricks'); ?></span>
<h2 class="av-section__title">Behavior, not scripts.</h2>
</div>
<?php
$features = [
[
'eyebrow' => 'Real-recoil weapon integration',
'title' => 'The weapon you train with is the weapon you carry.',
'body' => 'Our Universal Weapon Digitizer (UWeD&trade;) attaches to any gas-blowback or CO2 replica — Glock 17, HK416, MP7, M9, and dozens more — or to your real service weapon firing blanks. Bullet trajectory is physically simulated: ricochet, penetration, material density. Recoil-induced tracking loss is corrected by an exclusive IMU compensation system.',
'link' => 'See how UWeD works',
'href' => '/technology/weapons-tracking/',
],
[
'eyebrow' => 'Instructor Cockpit',
'title' => 'You orchestrate. The trainees adapt.',
'body' => "From a dedicated instructor PC, you orchestrate every detail of the scenario in real time. Walk through walls. Switch to any trainee's first-person view. Toggle field of view, shot traces, and threat highlighting. Add or remove civilians, change a hostage's behavior, escalate or de-escalate a contact — without pausing the scenario.",
'link' => 'Inside the Instructor Cockpit',
'href' => '/technology/instructor-cockpit/',
],
[
'eyebrow' => 'After-Action Review',
'title' => 'The session ends; the learning starts.',
'body' => "Every event is logged: shots fired, shots hit, shots missed by target type, reaction time, field-of-view direction, weapon muzzling, position over time. Replay any session from any angle — top-down tactical, free-camera, or in-eye view of any trainee. Speed up, slow down, pause. Compare a recruit's reaction time to the unit's median.",
'link' => 'Explore After-Action Review',
'href' => '/technology/after-action-review/',
],
];
foreach ($features as $i => $f) :
$reverse = ($i % 2 === 1);
?>
<div style="display:grid;grid-template-columns:1fr;gap:var(--space-8);align-items:center;margin-bottom:var(--space-16);<?php echo $reverse ? 'direction:rtl;' : ''; ?>" class="av-feature-row">
<div style="aspect-ratio:16/9;background:linear-gradient(135deg,#13294B,#081427);border-radius:var(--radius-md);direction:ltr;"></div>
<div style="direction:ltr;display:flex;flex-direction:column;gap:var(--space-3);">
<span class="av-eyebrow av-eyebrow--gold"><?php echo esc_html($f['eyebrow']); ?></span>
<h3 style="font-family:var(--font-display);font-size:var(--text-display-md);line-height:var(--leading-display-md);font-weight:var(--font-weight-bold);letter-spacing:var(--tracking-tight);color:var(--color-text-on-dark);margin:0;"><?php echo esc_html($f['title']); ?></h3>
<p style="color:var(--color-text-on-dark-muted);font-size:var(--text-body-lg);line-height:var(--leading-body-lg);max-width:60ch;"><?php echo wp_kses_post($f['body']); ?></p>
<div><a href="<?php echo esc_url($home_url . ltrim($f['href'], '/')); ?>" class="av-link av-link--on-dark av-link--with-arrow"><?php echo esc_html($f['link']); ?></a></div>
</div>
</div>
<?php endforeach; ?>
<style>
@media (min-width: 768px) {
.av-feature-row { grid-template-columns: 1fr 1fr !important; }
}
</style>
</div>
</section>
<!-- ============================================================== -->
<!-- 6. FEATURED CASE STUDY -->
<!-- ============================================================== -->
<section class="av-section" aria-labelledby="case-title" data-av-reveal>
<div class="av-container">
<div style="display:grid;grid-template-columns:1fr;gap:var(--space-10);align-items:center;" class="av-case-row">
<div style="aspect-ratio:4/3;background:linear-gradient(135deg,#3E4C5E,#0B1F3A);border-radius:var(--radius-md);"></div>
<div style="display:flex;flex-direction:column;gap:var(--space-4);">
<span class="av-eyebrow"><?php esc_html_e('Case study', 'asterion-bricks'); ?></span>
<h2 id="case-title" class="av-section__title">How a European tactical unit cut tactical errors by 35%.</h2>
<p class="av-section__lead">
In 2025, a European tactical unit deployed PROSERVE FLEX as part of its continuous training program. Six months in, instructor-rated tactical errors dropped 35% across the unit. Reaction time improved 22%. And weekly training volume tripled, without a single live round fired.
</p>
<div style="display:flex;gap:var(--space-8);margin-top:var(--space-2);">
<div class="av-stat av-stat--gold"><div class="av-stat__value">-35%</div><div class="av-stat__label"><?php esc_html_e('Tactical errors', 'asterion-bricks'); ?></div></div>
<div class="av-stat av-stat--gold"><div class="av-stat__value">+22%</div><div class="av-stat__label"><?php esc_html_e('Reaction time', 'asterion-bricks'); ?></div></div>
<div class="av-stat av-stat--gold"><div class="av-stat__value">×3</div><div class="av-stat__label"><?php esc_html_e('Training volume', 'asterion-bricks'); ?></div></div>
</div>
<div style="margin-top:var(--space-2);"><a href="<?php echo esc_url($home_url . 'customers/'); ?>" class="av-btn av-btn--secondary av-btn--md"><?php esc_html_e('Read the case study', 'asterion-bricks'); ?></a></div>
</div>
</div>
<style>
@media (min-width: 1024px) { .av-case-row { grid-template-columns: 1fr 1fr !important; } }
</style>
</div>
</section>
<!-- ============================================================== -->
<!-- 7. TESTIMONIALS -->
<!-- ============================================================== -->
<section class="av-section" style="background-color:var(--color-bg-subtle);" aria-labelledby="testimonials-title" data-av-reveal>
<div class="av-container">
<div class="av-section__head" style="text-align:center;align-items:center;margin-inline:auto;">
<span class="av-eyebrow"><?php esc_html_e('Voices from the field', 'asterion-bricks'); ?></span>
<h2 id="testimonials-title" class="av-section__title" style="text-align:center;margin-inline:auto;">Train how it really feels.</h2>
</div>
<div class="av-grid-3">
<article class="av-card av-card--quote">
<div class="av-card__quote">We've stopped asking how often we train. We've started measuring how well.</div>
<div class="av-card__attribution">— Tactical training officer, European federal police (anonymized)</div>
</article>
<article class="av-card av-card--quote">
<div class="av-card__quote">The first time my recruits saw their own AAR replay, three of them asked to redo the drill. That had never happened before.</div>
<div class="av-card__attribution">— Senior instructor, national police academy</div>
</article>
<article class="av-card av-card--quote">
<div class="av-card__quote">What used to take a half-day live exercise now takes 20 minutes between shifts.</div>
<div class="av-card__attribution">— Captain, urban response unit</div>
</article>
</div>
</div>
</section>
<!-- ============================================================== -->
<!-- 8. LATEST INSIGHTS -->
<!-- ============================================================== -->
<section class="av-section" aria-labelledby="insights-title" data-av-reveal>
<div class="av-container">
<div class="av-section__head">
<span class="av-eyebrow"><?php esc_html_e('Insights', 'asterion-bricks'); ?></span>
<h2 id="insights-title" class="av-section__title">Insights from the front of immersive training.</h2>
</div>
<?php
$insights = get_posts(['numberposts' => 3, 'post_status' => 'publish']);
if (! empty($insights)) :
?>
<div class="av-grid-3">
<?php foreach ($insights as $post) : setup_postdata($post); ?>
<article class="av-card av-card--blog">
<a href="<?php the_permalink(); ?>" class="av-card__media" aria-hidden="true" tabindex="-1" style="background:linear-gradient(135deg,#CCD3DC,#E5E9EE);"></a>
<div class="av-card__body">
<span class="av-card__eyebrow"><?php echo esc_html(get_the_date('M j, Y')); ?></span>
<h3 class="av-card__title"><a href="<?php the_permalink(); ?>" style="color:inherit;text-decoration:none;"><?php the_title(); ?></a></h3>
<p class="av-card__excerpt"><?php echo esc_html(wp_trim_words(get_the_excerpt(), 22)); ?></p>
<div class="av-card__cta"><a href="<?php the_permalink(); ?>" class="av-link av-link--with-arrow"><?php esc_html_e('Read more', 'asterion-bricks'); ?></a></div>
</div>
</article>
<?php endforeach; wp_reset_postdata(); ?>
</div>
<?php else : ?>
<p style="color:var(--color-text-muted);">
<?php esc_html_e('Editorial blog launches with the site go-live. The 12-month content calendar is in the Strategy & Content brief, section B.4.', 'asterion-bricks'); ?>
</p>
<?php endif; ?>
<div style="margin-top:var(--space-8);">
<a href="<?php echo esc_url($home_url . 'insights/'); ?>" class="av-link av-link--with-arrow"><?php esc_html_e('See all articles', 'asterion-bricks'); ?></a>
</div>
</div>
</section>
<!-- ============================================================== -->
<!-- 9. CONVERSION BANNER -->
<!-- ============================================================== -->
<section class="av-conversion-banner" aria-labelledby="conversion-title" data-av-reveal>
<div class="av-container">
<div class="av-conversion-banner__head">
<h2 id="conversion-title" class="av-conversion-banner__title">Train like the threat is now.</h2>
<p class="av-conversion-banner__lead"><?php esc_html_e('Three paths into the platform — pick the one that fits where you are.', 'asterion-bricks'); ?></p>
</div>
<div class="av-conversion-banner__grid">
<div class="av-conversion-banner__card">
<h3 class="av-conversion-banner__card-title"><?php esc_html_e('Request a Demo', 'asterion-bricks'); ?></h3>
<p class="av-conversion-banner__card-desc"><?php esc_html_e('Schedule a 30-minute tailored session with our team. Receive a video demo of the configuration that matches your needs.', 'asterion-bricks'); ?></p>
<a href="<?php echo esc_url($home_url . 'request-demo/'); ?>" class="av-btn av-btn--primary av-btn--md"><?php esc_html_e('Schedule a demo', 'asterion-bricks'); ?></a>
</div>
<div class="av-conversion-banner__card">
<h3 class="av-conversion-banner__card-title"><?php esc_html_e('Request a T&E Kit', 'asterion-bricks'); ?></h3>
<p class="av-conversion-banner__card-desc"><?php esc_html_e('Get a PROSERVE case on loan at your facility for 5 to 10 days, with on-site support from our team.', 'asterion-bricks'); ?></p>
<a href="<?php echo esc_url($home_url . 'request-te-kit/'); ?>" class="av-btn av-btn--on-dark av-btn--secondary av-btn--md"><?php esc_html_e('Request the kit', 'asterion-bricks'); ?></a>
</div>
<div class="av-conversion-banner__card">
<h3 class="av-conversion-banner__card-title"><?php esc_html_e('Request a Quote', 'asterion-bricks'); ?></h3>
<p class="av-conversion-banner__card-desc"><?php esc_html_e('Get a detailed quote and deployment plan within 5 working days.', 'asterion-bricks'); ?></p>
<a href="<?php echo esc_url($home_url . 'request-quote/'); ?>" class="av-btn av-btn--on-dark av-btn--secondary av-btn--md"><?php esc_html_e('Get a quote', 'asterion-bricks'); ?></a>
</div>
</div>
</div>
</section>
<?php get_footer(); ?>

View File

@@ -1,35 +1,38 @@
<?php
/**
* Asterion Bricks — child theme bootstrap
* Asterion Bricks — Child theme bootstrap (minimalist, Bricks-first)
*
* Loads design tokens, utilities, components, and main JS in a deterministic
* order. Registers theme support, image sizes, and includes modular pieces
* (custom post types, SEO schemas).
* Goals
* -----
* 1. Provide the design system to Bricks Builder via tokens.css.
* 2. Self-host Inter fonts so Bricks-rendered pages stay GDPR-compliant.
* 3. Register Custom Post Types (case_study, scenario) used across the site.
*
* Non-goals (intentionally NOT here, on purpose)
* ----------------------------------------------
* ✗ No header.php / footer.php / page.php / front-page.php / index.php.
* Bricks Templates (Header / Footer / Single — Page) drive the chrome.
* ✗ No Custom Page Templates. Pages render through Bricks data.
* ✗ No PHP renderers. Content lives in Bricks data + WPML translations.
* ✗ No Gutenberg block libraries dequeued. Bricks needs them in the builder.
*
* @package AsterionBricks
* @since 0.1.0
* @since 0.2.0
*/
defined('ABSPATH') || exit;
define('AV_THEME_VERSION', '0.1.0');
define('AV_THEME_VERSION', '0.2.0');
define('AV_THEME_DIR', get_stylesheet_directory());
define('AV_THEME_URI', get_stylesheet_directory_uri());
/**
* Enqueue child theme styles and scripts.
*
* Bricks parent stylesheet is loaded by the parent theme itself — no need to
* re-enqueue. We layer our tokens → utilities → components on top.
*
* Cache busting via `filemtime()` so dev changes are picked up without manual
* version bumps. In production, swap to AV_THEME_VERSION + a build step.
* Enqueue tokens.css + utilities.css on the public frontend AND inside the
* Bricks Builder iframe — the builder loads the front-end stylesheet so we
* see our `--color-*`, `--space-*`, `--font-*` variables as we work.
*/
add_action('wp_enqueue_scripts', function () {
$css_dir = AV_THEME_DIR . '/assets/css';
$css_uri = AV_THEME_URI . '/assets/css';
$js_dir = AV_THEME_DIR . '/assets/js';
$js_uri = AV_THEME_URI . '/assets/js';
if (file_exists($css_dir . '/tokens.css')) {
wp_enqueue_style(
@@ -39,7 +42,6 @@ add_action('wp_enqueue_scripts', function () {
filemtime($css_dir . '/tokens.css')
);
}
if (file_exists($css_dir . '/utilities.css')) {
wp_enqueue_style(
'av-utilities',
@@ -48,82 +50,25 @@ add_action('wp_enqueue_scripts', function () {
filemtime($css_dir . '/utilities.css')
);
}
if (file_exists($css_dir . '/components.css')) {
wp_enqueue_style(
'av-components',
$css_uri . '/components.css',
['av-tokens', 'av-utilities'],
filemtime($css_dir . '/components.css')
);
}
if (file_exists($js_dir . '/main.js')) {
wp_enqueue_script(
'av-main',
$js_uri . '/main.js',
[],
filemtime($js_dir . '/main.js'),
['strategy' => 'defer', 'in_footer' => true]
);
}
}, 20);
/**
* Preload self-hosted Inter fonts to reduce LCP.
*
* Only emits <link rel="preload"> for fonts that actually exist on disk —
* silent during early dev when the WOFF2 files have not been dropped yet.
* Preload the Inter Variable WOFF2 to reduce LCP. Conditional on the file
* existing so partial deploys don't 404.
*/
add_action('wp_head', function () {
$fonts = [
'Inter-Variable.woff2',
// 'InterDisplay-Variable.woff2',
];
foreach ($fonts as $font) {
$path = AV_THEME_DIR . '/assets/fonts/' . $font;
if (file_exists($path)) {
$font_path = AV_THEME_DIR . '/assets/fonts/Inter-Variable.woff2';
if (file_exists($font_path)) {
printf(
'<link rel="preload" href="%s/assets/fonts/%s" as="font" type="font/woff2" crossorigin>' . "\n",
esc_url(AV_THEME_URI),
esc_attr($font)
'<link rel="preload" href="%s/assets/fonts/Inter-Variable.woff2" as="font" type="font/woff2" crossorigin>' . "\n",
esc_url(AV_THEME_URI)
);
}
}
}, 1);
/**
* Modular includes.
*
* Each file is opt-in via file_exists() so partial scaffolds don't fatal.
*/
$av_includes = [
'inc/i18n.php', // av_lang(), av_t() — load FIRST.
'inc/cpt.php', // Custom post types: case_study, scenario.
'inc/seo.php', // Schema.org JSON-LD overrides.
'inc/form-handler.php', // Conversion form POST handler.
'inc/render-blocks.php', // Data array → core/html migrator (legacy, kept for fallback).
'inc/render-blocks-native.php', // Data array → native Gutenberg blocks (legacy POC).
'inc/render-bricks.php', // Data array → Bricks Builder native elements (current target).
'inc/shortcodes.php', // [av_form slug="..."] etc.
];
foreach ($av_includes as $av_include) {
$path = AV_THEME_DIR . '/' . $av_include;
if (file_exists($path)) {
require_once $path;
}
}
unset($av_include, $path);
/**
* Theme support and custom image sizes.
*
* Image sizes per Design Handoff section 8.3:
* - hero 2400×1350 (16:9)
* - card 800×600 (4:3)
* - thumb 400×300 (4:3)
* Theme support + custom image sizes (used by Bricks media controls and
* Schema.org/OpenGraph emitters down the line).
*/
add_action('after_setup_theme', function () {
add_theme_support('post-thumbnails');
@@ -133,41 +78,25 @@ add_action('after_setup_theme', function () {
]);
add_theme_support('responsive-embeds');
add_theme_support('align-wide');
add_theme_support('editor-styles');
add_image_size('av-hero', 2400, 1350, true);
add_image_size('av-card', 800, 600, true);
add_image_size('av-thumb', 400, 300, true);
add_image_size('av-hero', 2400, 1350, true); // 16:9 hero
add_image_size('av-card', 800, 600, true); // 4:3 card
add_image_size('av-thumb', 400, 300, true); // 4:3 thumb
load_child_theme_textdomain('asterion-bricks', AV_THEME_DIR . '/languages');
});
/**
* Hide the WP admin bar on frontend for non-admins (cleaner preview screenshots).
* Modular includes — currently only CPT registration. SEO and other modules
* will be added as needed; the include loop is opt-in via file_exists().
*/
add_action('after_setup_theme', function () {
if (! current_user_can('administrator')) {
show_admin_bar(false);
$av_includes = [
'inc/cpt.php',
];
foreach ($av_includes as $av_include) {
$path = AV_THEME_DIR . '/' . $av_include;
if (file_exists($path)) {
require_once $path;
}
});
/**
* Disable Gutenberg block library default styles on the public frontend only —
* we own typography end-to-end through tokens.css.
*
* Skipped when:
* - inside the Bricks Builder canvas (?bricks=run, ?brickspreview=1) — those
* screens rely on the WP-core stylesheets for their own UI / preview.
* - in the WP admin itself.
*/
add_action('wp_enqueue_scripts', function () {
if (is_admin()) return;
if (function_exists('bricks_is_builder') && bricks_is_builder()) return;
if (function_exists('bricks_is_frontend_builder_iframe') && bricks_is_frontend_builder_iframe()) return;
if (! empty($_GET['bricks']) || ! empty($_GET['brickspreview'])) return;
wp_dequeue_style('wp-block-library');
wp_dequeue_style('wp-block-library-theme');
wp_dequeue_style('global-styles');
wp_dequeue_style('classic-theme-styles');
}, 100);
}
unset($av_include, $path);

View File

@@ -1,138 +0,0 @@
<?php
/**
* Asterion Bricks — Global header
*
* Sticky header per Design Handoff section 3.5 :
* - 60px on mobile, 72px on desktop
* - Logo left, main nav center (desktop), CTA + lang switch right
* - Hamburger drawer on mobile
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
?>
<!doctype html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#0B1F3A">
<link rel="profile" href="https://gmpg.org/xfn/11">
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<?php
// In Bricks Builder mode, the parent theme's `template-parts/builder.php`
// calls get_header() / get_footer() to inherit our enqueues — but the
// builder UI then mounts directly into <body>. Emitting our own chrome
// here covers it visually, so we bail early.
if (function_exists('bricks_is_builder') && bricks_is_builder()) {
return;
}
?>
<a class="av-skip-link" href="#main"><?php echo esc_html(av_t('Skip to main content', 'asterion-bricks')); ?></a>
<header class="av-header" role="banner">
<div class="av-container av-header__inner">
<a href="<?php echo esc_url(home_url('/')); ?>" class="av-header__logo" aria-label="Asterion VR — home">
ASTERION&nbsp;VR
</a>
<nav class="av-nav" aria-label="<?php esc_attr_e('Primary', 'asterion-bricks'); ?>">
<ul class="av-nav__list" style="display:flex;gap:var(--space-6);list-style:none;margin:0;padding:0;">
<li class="av-nav__item">
<a href="<?php echo esc_url(home_url('/solutions/')); ?>" class="av-nav__link" aria-haspopup="true" aria-expanded="false">
<?php echo esc_html(av_t('Solutions', 'asterion-bricks')); ?>
</a>
</li>
<li class="av-nav__item">
<a href="<?php echo esc_url(home_url('/industries/')); ?>" class="av-nav__link" aria-haspopup="true" aria-expanded="false">
<?php echo esc_html(av_t('Industries', 'asterion-bricks')); ?>
</a>
</li>
<li class="av-nav__item">
<a href="<?php echo esc_url(home_url('/technology/')); ?>" class="av-nav__link" aria-haspopup="true" aria-expanded="false">
<?php echo esc_html(av_t('Technology', 'asterion-bricks')); ?>
</a>
</li>
<li class="av-nav__item">
<a href="<?php echo esc_url(home_url('/why-asterion/')); ?>" class="av-nav__link">
<?php echo esc_html(av_t('Why Asterion', 'asterion-bricks')); ?>
</a>
</li>
<li class="av-nav__item">
<a href="<?php echo esc_url(home_url('/insights/')); ?>" class="av-nav__link">
<?php echo esc_html(av_t('Insights', 'asterion-bricks')); ?>
</a>
</li>
</ul>
</nav>
<div class="av-header__right">
<?php
// Render WPML language switcher when WPML is active and exposes languages.
$av_languages = apply_filters('wpml_active_languages', null, ['skip_missing' => 0]);
if (! empty($av_languages) && is_array($av_languages) && count($av_languages) > 1) :
?>
<div class="av-lang-switch" aria-label="<?php echo esc_attr(av_t('Language')); ?>">
<?php $av_first = true; foreach ($av_languages as $av_lang_data) :
if (! $av_first) : ?><span aria-hidden="true">·</span><?php endif; $av_first = false;
$av_code = strtoupper($av_lang_data['language_code']);
$av_url = esc_url($av_lang_data['url']);
$av_label = esc_html($av_lang_data['translated_name'] ?? $av_lang_data['native_name'] ?? $av_code);
if (! empty($av_lang_data['active'])) : ?>
<span class="av-lang-switch__current" aria-current="true"><?php echo esc_html($av_code); ?></span>
<?php else : ?>
<a href="<?php echo $av_url; ?>" class="av-lang-switch__other" hreflang="<?php echo esc_attr($av_lang_data['language_code']); ?>" lang="<?php echo esc_attr($av_lang_data['language_code']); ?>" aria-label="<?php echo esc_attr($av_label); ?>"><?php echo esc_html($av_code); ?></a>
<?php endif;
endforeach; ?>
</div>
<?php endif; ?>
<a href="<?php echo esc_url(home_url('/request-demo/')); ?>" class="av-btn av-btn--primary av-btn--md">
<?php echo esc_html(av_t('Request a Demo', 'asterion-bricks')); ?>
</a>
<button type="button" class="av-drawer-toggle" aria-controls="av-drawer" aria-expanded="false" aria-label="<?php esc_attr_e('Open menu', 'asterion-bricks'); ?>">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
</div>
</div>
</header>
<aside id="av-drawer" class="av-drawer" aria-hidden="true">
<button type="button" class="av-drawer__close" aria-label="<?php esc_attr_e('Close menu', 'asterion-bricks'); ?>">×</button>
<nav class="av-drawer__nav" aria-label="<?php esc_attr_e('Mobile primary', 'asterion-bricks'); ?>">
<div class="av-drawer__group">
<a href="<?php echo esc_url(home_url('/solutions/')); ?>" class="av-drawer__group-title"><?php echo esc_html(av_t('Solutions', 'asterion-bricks')); ?></a>
</div>
<div class="av-drawer__group">
<a href="<?php echo esc_url(home_url('/industries/')); ?>" class="av-drawer__group-title"><?php echo esc_html(av_t('Industries', 'asterion-bricks')); ?></a>
</div>
<div class="av-drawer__group">
<a href="<?php echo esc_url(home_url('/technology/')); ?>" class="av-drawer__group-title"><?php echo esc_html(av_t('Technology', 'asterion-bricks')); ?></a>
</div>
<div class="av-drawer__group">
<a href="<?php echo esc_url(home_url('/why-asterion/')); ?>" class="av-drawer__group-title"><?php echo esc_html(av_t('Why Asterion', 'asterion-bricks')); ?></a>
</div>
<div class="av-drawer__group">
<a href="<?php echo esc_url(home_url('/insights/')); ?>" class="av-drawer__group-title"><?php echo esc_html(av_t('Insights', 'asterion-bricks')); ?></a>
</div>
</nav>
<div class="av-drawer__cta-area">
<a href="<?php echo esc_url(home_url('/request-demo/')); ?>" class="av-btn av-btn--primary av-btn--lg" style="width:100%;">
<?php echo esc_html(av_t('Request a Demo', 'asterion-bricks')); ?>
</a>
</div>
</aside>
<main id="main" class="av-main">

View File

@@ -1,108 +0,0 @@
<?php
/**
* Asterion Bricks — Conversion form handler
*
* Receives POSTs from /request-demo/, /request-te-kit/, /request-quote/,
* /contact/. Validates the WordPress nonce, screens the honeypot, sanitizes
* the payload, and emails the lead to the configured recipient.
*
* Hook : admin_post_av_form_submit (logged-out users use admin_post_nopriv).
*
* Lead destination defaults to AV_LEADS_RECIPIENT (override in wp-config.php),
* falling back to the WP admin email.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
if (! defined('AV_LEADS_RECIPIENT')) {
define('AV_LEADS_RECIPIENT', 'jerome.foucher@asterionvr.com');
}
function av_handle_form_submit() {
// 1. Nonce
if (! isset($_POST['av_nonce']) || ! wp_verify_nonce(wp_unslash($_POST['av_nonce']), 'av_form_submit')) {
wp_safe_redirect(home_url('/?av_status=error'));
exit;
}
// 2. Honeypot — bots fill anything
if (! empty($_POST['av_hp'])) {
// Pretend success so bots don't retry.
$redirect = isset($_POST['redirect_to']) ? esc_url_raw(wp_unslash($_POST['redirect_to'])) : home_url('/');
wp_safe_redirect(add_query_arg('av_status', 'success', $redirect));
exit;
}
// 3. Form data
$slug = isset($_POST['form_slug']) ? sanitize_key(wp_unslash($_POST['form_slug'])) : '';
$forms_data = include AV_THEME_DIR . '/inc/forms-data.php';
if (! isset($forms_data[$slug])) {
wp_safe_redirect(home_url('/?av_status=error'));
exit;
}
$form = $forms_data[$slug];
// 4. Sanitize fields
$payload = [];
foreach ($form['fields'] as $field) {
$name = $field['name'];
if (! isset($_POST[$name])) {
$payload[$name] = '';
continue;
}
$raw = wp_unslash($_POST[$name]);
if ($field['type'] === 'email') {
$payload[$name] = sanitize_email($raw);
} elseif ($field['type'] === 'textarea') {
$payload[$name] = sanitize_textarea_field($raw);
} else {
$payload[$name] = sanitize_text_field($raw);
}
}
// 5. Required-field validation
foreach ($form['fields'] as $field) {
if (! empty($field['required']) && empty($payload[$field['name']])) {
$redirect = isset($_POST['redirect_to']) ? esc_url_raw(wp_unslash($_POST['redirect_to'])) : home_url('/');
wp_safe_redirect(add_query_arg('av_status', 'error', $redirect));
exit;
}
}
// 6. Compose mail
$subject_prefix = '[Asterion VR · ' . $form['title'] . ']';
$subject = $subject_prefix . ' ' . ($payload['organization'] ?? $payload['full_name'] ?? '(unidentified lead)');
$body = "New lead from " . get_bloginfo('name') . " — form: " . $form['title'] . "\n";
$body .= "URL : " . (isset($_POST['redirect_to']) ? esc_url_raw(wp_unslash($_POST['redirect_to'])) : home_url('/')) . "\n";
$body .= str_repeat('-', 60) . "\n";
foreach ($form['fields'] as $field) {
$value = $payload[$field['name']] ?? '';
if ($value === '') continue;
$body .= str_pad($field['label'], 28) . ': ' . $value . "\n";
}
$body .= str_repeat('-', 60) . "\n";
$body .= "Submitted : " . current_time('mysql') . "\n";
$body .= "IP : " . (isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field($_SERVER['REMOTE_ADDR']) : '?') . "\n";
$body .= "User Agent: " . (isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '?') . "\n";
$headers = ['Content-Type: text/plain; charset=UTF-8'];
if (! empty($payload['email'])) {
$headers[] = 'Reply-To: ' . $payload['email'];
}
$recipient = apply_filters('av_form_recipient', AV_LEADS_RECIPIENT, $slug, $payload);
$sent = wp_mail($recipient, $subject, $body, $headers);
// 7. Redirect
$redirect = isset($_POST['redirect_to']) ? esc_url_raw(wp_unslash($_POST['redirect_to'])) : home_url('/');
$status = $sent ? 'success' : 'error';
wp_safe_redirect(add_query_arg('av_status', $status, $redirect));
exit;
}
add_action('admin_post_av_form_submit', 'av_handle_form_submit');
add_action('admin_post_nopriv_av_form_submit', 'av_handle_form_submit');

View File

@@ -1,200 +0,0 @@
<?php
/**
* Asterion Bricks — i18n helpers
*
* Lightweight WPML-aware language helpers used by Custom Templates to pick
* the right data file (e.g. solutions-data.php vs solutions-data-fr.php) and
* by header.php / footer.php to translate UI strings.
*
* We don't use WordPress .po/.mo loaders — WPML String Translation handles
* those — but we keep a small in-PHP map for the strings that aren't tied
* to data files (nav labels, generic CTAs, footer columns).
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
/**
* Active language code, normalised to 'en' or 'fr'.
* Falls back to 'en' when WPML isn't active.
*/
function av_lang() {
$lang = apply_filters('wpml_current_language', null);
if (! $lang) {
$lang = function_exists('get_locale') ? substr(get_locale(), 0, 2) : 'en';
}
return ($lang === 'fr') ? 'fr' : 'en';
}
/**
* Build the path to a localised data file.
* Example : av_data_file('solutions-data') returns
* '<theme>/inc/solutions-data.php' when EN
* '<theme>/inc/solutions-data-fr.php' when FR (if it exists)
*/
function av_data_file($basename) {
$base = AV_THEME_DIR . '/inc/' . $basename;
$localised = $base . '-' . av_lang() . '.php';
return file_exists($localised) ? $localised : $base . '.php';
}
/**
* Translate a UI string. Falls back to the original on EN or when no entry
* matches in FR. Strings are kept in this file rather than .po/.mo so the
* theme stays self-contained.
*/
function av_t($string, $textdomain = 'asterion-bricks') {
if (av_lang() !== 'fr') {
return $string;
}
static $map = null;
if ($map === null) {
$map = [
// Nav + header
'Skip to main content' => 'Aller au contenu principal',
'Primary' => 'Principal',
'Mobile primary' => 'Principal mobile',
'Solutions' => 'Solutions',
'Industries' => 'Secteurs',
'Technology' => 'Technologie',
'Why Asterion' => 'Pourquoi Asterion',
'Insights' => 'Insights',
'Open menu' => 'Ouvrir le menu',
'Close menu' => 'Fermer le menu',
'Language' => 'Langue',
'Switch to French' => 'Passer en français',
'Switch to English' => 'Switch to English',
'Request a Demo' => 'Demander une démo',
// Footer
'Newsletter' => 'Newsletter',
'Our story' => 'Notre histoire',
'Customers' => 'Clients',
'Trust & Compliance' => 'Confiance et conformité',
'Partners' => 'Partenaires',
'Police' => 'Police',
'Special Forces' => 'Forces spéciales',
'Military' => 'Militaires',
'Firefighters' => 'Pompiers',
'Privacy' => 'Confidentialité',
'Terms' => 'Conditions',
'Cookies' => 'Cookies',
'Legal notice' => 'Mentions légales',
'All rights reserved.' => 'Tous droits réservés.',
'Insights from the front of immersive training. One email per month. No spam.'
=> 'Les enseignements du terrain de la formation immersive. Un e-mail par mois. Pas de spam.',
'Subscribe' => 'Sabonner',
'your@email.com' => 'vous@email.com',
'The modular, sovereign XR platform for European security forces. Train like the threat is now.'
=> 'La plateforme XR modulaire et souveraine pour les forces de sécurité européennes. Entraînez-vous comme si la menace était maintenant.',
// Generic CTAs / labels
'Read more' => 'Lire la suite',
'See all articles' => 'Voir tous les articles',
'Watch the 90-second film' => 'Voir le film de 90 secondes',
'Schedule a demo' => 'Planifier une démo',
'Request the kit' => 'Demander le kit',
'Get a quote' => 'Demander un devis',
'Send' => 'Envoyer',
'Send the message' => 'Envoyer le message',
'Apply for a T&E Kit' => 'Postuler pour un kit T&E',
'Schedule the demo' => 'Planifier la démo',
'Request the quote' => 'Demander le devis',
'Read the case study' => 'Lire létude de cas',
'Discuss a custom scenario' => 'Discuter dun scénario sur mesure',
'Schedule a discovery call' => 'Planifier un appel de cadrage',
'Discuss your custom request' => 'Parler de votre besoin sur mesure',
'Become the next case study' => 'Devenez la prochaine étude de cas',
'Request a Trust Pack' => 'Demander un Trust Pack',
'Become a partner — apply' => 'Devenir partenaire — candidater',
'Compare with PROSERVE FLEX' => 'Comparer avec PROSERVE FLEX',
'Compare with PROSERVE ACADEMY' => 'Comparer avec PROSERVE ACADEMY',
'Comparison and upgrade' => 'Comparaison et évolution',
'Eligibility' => 'Éligibilité',
'Frequently asked' => 'Questions fréquentes',
'Quick answers.' => 'Réponses brèves.',
// Section eyebrows used on home / hubs / forms
'Modular XR training for security forces' => 'Formation XR modulaire pour les forces de sécurité',
'Trusted by' => 'Reconnu par',
'Trusted by security forces across Europe' => 'Adopté par les forces de sécurité en Europe',
'Voices from the front of immersive training.' => 'La parole des opérateurs.',
'Compare' => 'Comparer',
'The six pillars' => 'Les six piliers',
'Tell us about your unit.' => 'Parlez-nous de votre unité.',
'What you receive' => 'Ce que vous recevez',
'Other inquiries' => 'Autres demandes',
'How can we help?' => 'Comment pouvons-nous vous aider ?',
// Form widgets
'— select —' => '— choisir —',
'Your email' => 'Votre e-mail',
'Newsletter signup' => 'Inscription newsletter',
'Mobile drawer' => 'Tiroir mobile',
'Voices from the field' => 'La parole du terrain',
'Train how it really feels.' => 'Entraînez-vous au plus près du réel.',
'Train like the threat is now.' => 'Entraînez-vous comme si la menace était maintenant.',
'Three paths into the platform — pick the one that fits where you are.' => 'Trois portes dentrée dans la plateforme — choisissez celle qui correspond à votre besoin.',
'A 30-minute tailored session with our team. Walk through the configuration that matches your needs.' => 'Une session sur mesure de 30 minutes avec notre équipe. Démonstration de la configuration adaptée à vos besoins.',
'A 30-minute tailored session with our team.' => 'Une session de 30 minutes avec notre équipe.',
'A PROSERVE case on loan at your facility for 5 to 10 days, with on-site support from our team.' => 'Un kit PROSERVE en prêt dans vos locaux pendant 5 à 10 jours, avec accompagnement sur site.',
'A PROSERVE case on loan at your facility for 5 to 10 days.' => 'Un kit PROSERVE en prêt dans vos locaux pendant 5 à 10 jours.',
'A detailed quote and deployment plan within 5 working days.' => 'Un devis détaillé et un plan de déploiement sous 5 jours ouvrés.',
'Ready to get hands-on?' => 'Prêt à passer à la pratique ?',
'Last updated:' => 'Dernière mise à jour :',
'Legal' => 'Légal',
'Placeholder.' => 'À compléter.',
'This page is to be drafted with legal counsel. The structure below outlines the sections expected.' =>
'Cette page sera rédigée avec votre conseil juridique. La structure ci-dessous liste les sections attendues.',
'Sections to include' => 'Sections à inclure',
'Police training' => 'Formation police',
'Special forces training' => 'Formation forces spéciales',
'Military training' => 'Formation militaire',
'Firefighter training' => 'Formation pompier',
'Insights from the front of immersive training.' => 'Les enseignements du terrain de la formation immersive.',
'See the full scenario library' => 'Voir la bibliothèque complète de scénarios',
'Inside the hardware' => 'Inside the hardware',
'Inside the software' => 'Inside the software',
'Inside weapons & tracking' => 'Inside weapons & tracking',
'Browse the scenario library' => 'Parcourir la bibliothèque',
'Inside the instructor cockpit' => 'Inside the instructor cockpit',
'Inside after-action review' => 'Inside after-action review',
'See how UWeD works' => 'Voir comment fonctionne UWeD',
'Explore After-Action Review' => 'Découvrir lAfter-Action Review',
'Explore PROSERVE+ customization' => 'Découvrir la personnalisation PROSERVE+',
'Discover MY PROSERVE' => 'Découvrir MY PROSERVE',
'Discover PROSERVE FLEX' => 'Découvrir PROSERVE FLEX',
'Discover PROSERVE ACADEMY' => 'Découvrir PROSERVE ACADEMY',
'Request access' => 'Demander laccès',
'Request a T&E Kit' => 'Demander un kit T&E',
'Request a Quote' => 'Demander un devis',
'Request a Demo for police use' => 'Demander une démo pour la police',
'Request a Demo for fire & emergency' => 'Demander une démo pour les pompiers',
'Thank you — your request was sent.' => 'Merci — votre demande a bien été envoyée.',
'We will reply within the timeframe stated above. Check your inbox (and spam) for confirmation.' =>
'Nous reviendrons vers vous dans les délais indiqués. Vérifiez votre boîte de réception (et vos spams) pour la confirmation.',
'Something went wrong.' => 'Une erreur est survenue.',
' Please try again, or email us directly at hello@asterionvr.com.' =>
' Veuillez réessayer, ou écrivez-nous directement à hello@asterionvr.com.',
'Editorial blog launches with the site go-live. The 12-month content calendar is in the Strategy & Content brief, section B.4.' =>
'Le blog éditorial démarrera avec la mise en ligne du site. Le calendrier sur 12 mois figure dans le livrable Stratégie & Contenu, partie B.4.',
'Editorial blog launches with the site go-live. The 12-month content calendar is in the Strategy & Content brief, section B.4. New articles will appear here as soon as they ship.' =>
'Le blog éditorial démarrera avec la mise en ligne du site. Le calendrier sur 12 mois figure dans le livrable Stratégie & Contenu, partie B.4. Les articles apparaîtront ici dès leur publication.',
'Subscribe — one article per month.' => 'Abonnez-vous — un article par mois.',
'Notes from the front of immersive training.' => 'Les enseignements du terrain de la formation immersive.',
'Doctrine. Training science. Instructor stories. AAR data. Comparisons of VR vs live training. Found something useful? It probably came from a customer conversation. We publish what we learn.' =>
'Doctrine. Science de la formation. Récits dinstructeurs. Données AAR. Comparaisons VR vs entraînement réel. Quelque chose dutile ? Cela vient probablement dun échange client. Nous publions ce que nous apprenons.',
'Everything to evaluate, justify, and procure.' => 'Tout pour évaluer, justifier, acheter.',
'Whether you are building an internal pitch, drafting a procurement specification, or briefing your team — these resources are designed to help.' =>
'Que vous prépariez un argumentaire interne, rédigiez un cahier des charges ou briefiez vos équipes — ces ressources sont là pour vous aider.',
];
}
return $map[$string] ?? $string;
}
/**
* Convenience: echo a translated string.
*/
function av_te($string) {
echo esc_html(av_t($string));
}

View File

@@ -1,228 +0,0 @@
<?php
/**
* Asterion Bricks — Native Gutenberg block renderer (POC for Industries)
*
* Generates strictly-standard Gutenberg blocks (core/group, core/heading,
* core/paragraph, core/columns, core/column, core/buttons, core/button,
* core/list) so that:
*
* 1. Pages are editable in the WP admin block editor — every text node
* is a real block, not a raw HTML wrapper.
* 2. "Edit with Bricks" converts these to Bricks elements (Container,
* Heading, Text-Basic, Button, …) per Bricks Builder docs.
* 3. WPML's Translation Editor exposes one entry per block.
*
* Convention: every block carries a `className` attribute matching our
* existing `av-*` CSS classes so the rendered HTML inherits the design
* system styles.
*
* Public API (POC, will replace inc/render-blocks.php once validated):
* av_n_render_industries_overview() → string (single hub page)
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
/**
* Build a Gutenberg block array (the shape `serialize_blocks()` expects).
* `innerHTML` is the wrapper template — Gutenberg uses it as a delimiter
* map, with NULL entries marking where children get inlined.
*/
function av_n_block($name, $attrs = [], $inner_blocks = [], $inner_html = '') {
return [
'blockName' => $name,
'attrs' => $attrs,
'innerBlocks' => $inner_blocks,
'innerHTML' => $inner_html,
'innerContent' => [$inner_html],
];
}
/**
* Group block (tagName configurable). Used for sections, containers, cards.
* Children are kept as innerBlocks; innerContent uses NULL placeholders so
* Gutenberg slots them in at render time.
*/
function av_n_group($attrs, $children, $tag = 'div') {
$attrs = array_merge(['tagName' => $tag], $attrs);
$class = trim('wp-block-group ' . ($attrs['className'] ?? ''));
// Build innerContent: opening tag, then NULL per child, then closing tag.
$inner_content = ['<' . $tag . ' class="' . esc_attr($class) . '">'];
foreach ($children as $child) {
$inner_content[] = null;
}
$inner_content[] = '</' . $tag . '>';
return [
'blockName' => 'core/group',
'attrs' => $attrs,
'innerBlocks' => $children,
'innerHTML' => '<' . $tag . ' class="' . esc_attr($class) . '"></' . $tag . '>',
'innerContent' => $inner_content,
];
}
function av_n_heading($text, $level = 2, $class = '') {
$tag = 'h' . max(1, min(6, (int) $level));
$cls = trim('wp-block-heading ' . $class);
$html = '<' . $tag . ' class="' . esc_attr($cls) . '">' . esc_html($text) . '</' . $tag . '>';
return [
'blockName' => 'core/heading',
'attrs' => array_filter([
'level' => (int) $level,
'className' => $class ?: null,
]),
'innerBlocks' => [],
'innerHTML' => $html,
'innerContent' => [$html],
];
}
function av_n_paragraph($text, $class = '') {
$html = $class
? '<p class="' . esc_attr($class) . '">' . esc_html($text) . '</p>'
: '<p>' . esc_html($text) . '</p>';
return [
'blockName' => 'core/paragraph',
'attrs' => $class ? ['className' => $class] : [],
'innerBlocks' => [],
'innerHTML' => $html,
'innerContent' => [$html],
];
}
function av_n_button($label, $href, $variants = 'av-btn av-btn--primary av-btn--lg') {
$btn_html = '<div class="wp-block-button ' . esc_attr($variants) . '"><a class="wp-block-button__link wp-element-button" href="' . esc_url($href) . '">' . esc_html($label) . '</a></div>';
return [
'blockName' => 'core/button',
'attrs' => ['className' => $variants],
'innerBlocks' => [],
'innerHTML' => $btn_html,
'innerContent' => [$btn_html],
];
}
function av_n_buttons_row($buttons, $class = 'av-hero__ctas') {
$opening = '<div class="wp-block-buttons ' . esc_attr($class) . '">';
$closing = '</div>';
$inner_content = [$opening];
foreach ($buttons as $b) {
$inner_content[] = null;
}
$inner_content[] = $closing;
return [
'blockName' => 'core/buttons',
'attrs' => ['className' => $class],
'innerBlocks' => $buttons,
'innerHTML' => $opening . $closing,
'innerContent' => $inner_content,
];
}
function av_n_column($children, $width = null) {
$attrs = $width ? ['width' => $width] : [];
$opening = '<div class="wp-block-column"' . ($width ? ' style="flex-basis:' . esc_attr($width) . '"' : '') . '>';
$closing = '</div>';
$inner_content = [$opening];
foreach ($children as $c) $inner_content[] = null;
$inner_content[] = $closing;
return [
'blockName' => 'core/column',
'attrs' => $attrs,
'innerBlocks' => $children,
'innerHTML' => $opening . $closing,
'innerContent' => $inner_content,
];
}
function av_n_columns($columns, $class = 'av-grid-4') {
$opening = '<div class="wp-block-columns ' . esc_attr($class) . '">';
$closing = '</div>';
$inner_content = [$opening];
foreach ($columns as $c) $inner_content[] = null;
$inner_content[] = $closing;
return [
'blockName' => 'core/columns',
'attrs' => array_filter(['className' => $class, 'isStackedOnMobile' => true]),
'innerBlocks' => $columns,
'innerHTML' => $opening . $closing,
'innerContent' => $inner_content,
];
}
/* ========================================================================== */
/* Industries overview hub — proof of concept */
/* ========================================================================== */
function av_n_render_industries_overview() {
$home = home_url('/');
$industries = [
['eyebrow' => 'National & Municipal', 'title' => 'Police', 'excerpt' => 'Vehicle stops, identity checks, urban CQB, de-escalation, riot containment. Built for the realities of contemporary policing.', 'href' => '/industries/police/', 'cta' => 'Police training'],
['eyebrow' => 'Tactical units', 'title' => 'Special Forces', 'excerpt' => 'Hostage rescue, dynamic entries, hostile crowd extraction, sniper-spotter coordination. Built for the units where every error has a name.', 'href' => '/industries/special-forces/', 'cta' => 'Special forces training'],
['eyebrow' => 'Armed forces', 'title' => 'Military', 'excerpt' => 'MOUT, convoy escort, FOB defense, IED awareness, joint operation drills. Built for armed forces that deploy fast and adapt faster.', 'href' => '/industries/military/', 'cta' => 'Military training'],
['eyebrow' => 'Emergency services', 'title' => 'Firefighters', 'excerpt' => "Structure fires, hazmat, multi-casualty triage, confined-space rescue, industrial accidents. Built for first responders who can't rehearse the worst day at full scale.", 'href' => '/industries/firefighters/', 'cta' => 'Firefighter training'],
];
/* ----- Hero (section / container / inner) ----- */
$hero_inner = av_n_group(
['className' => 'av-container av-hero__inner', 'layout' => ['type' => 'constrained']],
[
av_n_paragraph('Industries', 'av-hero__eyebrow'),
av_n_heading('Built with operators, for operators.', 1, 'av-hero__headline'),
av_n_paragraph(
'PROSERVE™ is not a generic VR platform repackaged for the public sector. Every scenario, every weapon profile, every instructor metric was specified by serving and former operators in close partnership with our engineering team.',
'av-hero__sub'
),
],
'div'
);
$hero = av_n_group(
['className' => 'av-hero'],
[$hero_inner],
'section'
);
/* ----- Cards section ----- */
$card_columns = [];
foreach ($industries as $ind) {
$card_inner = av_n_group(
['className' => 'av-card__body'],
[
av_n_paragraph($ind['eyebrow'], 'av-card__eyebrow'),
av_n_heading($ind['title'], 2, 'av-card__title'),
av_n_paragraph($ind['excerpt'], 'av-card__excerpt'),
av_n_buttons_row(
[av_n_button($ind['cta'], $home . ltrim($ind['href'], '/'), 'av-btn av-btn--secondary av-btn--md')],
'av-card__cta'
),
],
'div'
);
$card = av_n_group(
['className' => 'av-card'],
[$card_inner],
'article'
);
$card_columns[] = av_n_column([$card]);
}
$cards_grid = av_n_columns($card_columns, 'av-grid-4');
$section_container = av_n_group(
['className' => 'av-container', 'layout' => ['type' => 'constrained']],
[$cards_grid],
'div'
);
$section = av_n_group(
['className' => 'av-section'],
[$section_container],
'section'
);
return serialize_blocks([$hero, $section]);
}

View File

@@ -1,376 +0,0 @@
<?php
/**
* Asterion Bricks — Data → Gutenberg blocks renderer
*
* Converts the structured PHP arrays in inc/<section>-data.php into a
* Gutenberg block payload (`post_content`) so that :
*
* - Pages are editable in the WP admin block editor
* - Bricks Builder can pick them up via « Edit with Bricks » (Bricks reads
* post_content and converts core blocks → Bricks elements on save)
* - WPML's Translation Editor exposes every block for translation
*
* Strategy
* --------
* We generate Gutenberg-valid HTML using a mix of native blocks
* (`core/heading`, `core/paragraph`, `core/list`, `core/buttons`,
* `core/columns`) wrapped in `core/group` containers carrying our
* `av-*` classes. Visually-heavy items (hero, conversion banner,
* complex cards) fall back to `core/html` blocks so the rendering
* stays pixel-identical to the Custom Template version, while still
* being individually translatable in WPML.
*
* Public API
* ----------
* av_render_solution_blocks($data) → string (post_content)
* av_render_industry_blocks($data) → string
* av_render_technology_blocks($data) → string (alias of industry)
* av_render_editorial_blocks($data) → string (alias of industry)
* av_render_form_blocks($data, $slug) → string
* av_render_overview_blocks($cards, $hero, $bg) → string (hubs)
* av_render_home_blocks() → string
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
/* =================================================================== */
/* Block helpers */
/* =================================================================== */
function av_block_html($html) {
$html = trim($html);
return "<!-- wp:html -->\n{$html}\n<!-- /wp:html -->\n\n";
}
function av_block_heading($text, $level = 2, $class = '') {
$tag = 'h' . max(1, min(6, (int) $level));
$class = $class ? ' ' . esc_attr($class) : '';
$args = json_encode(['level' => (int) $level, 'className' => trim($class)]);
return "<!-- wp:heading {$args} -->\n<{$tag} class=\"wp-block-heading{$class}\">" . esc_html($text) . "</{$tag}>\n<!-- /wp:heading -->\n\n";
}
function av_block_paragraph($text, $class = '') {
$args = $class ? json_encode(['className' => $class]) . ' ' : '';
$cls = $class ? ' class="' . esc_attr($class) . '"' : '';
return "<!-- wp:paragraph {$args}-->\n<p{$cls}>" . esc_html($text) . "</p>\n<!-- /wp:paragraph -->\n\n";
}
function av_block_list($items) {
$out = "<!-- wp:list -->\n<ul>\n";
foreach ($items as $item) {
$out .= "<!-- wp:list-item -->\n<li>" . esc_html($item) . "</li>\n<!-- /wp:list-item -->\n";
}
$out .= "</ul>\n<!-- /wp:list -->\n\n";
return $out;
}
function av_block_button($label, $href, $variant = 'primary', $on_dark = false) {
$href = esc_url($href);
$label = esc_html($label);
$cls = 'av-btn av-btn--' . $variant . ($on_dark ? ' av-btn--on-dark' : '') . ' av-btn--lg';
return "<!-- wp:html -->\n<a class=\"{$cls}\" href=\"{$href}\">{$label}</a>\n<!-- /wp:html -->\n\n";
}
function av_block_buttons_row($ctas, $on_dark = false, $home_url = '') {
if (empty($ctas)) return '';
$out = "<!-- wp:html -->\n<div style=\"display:flex;flex-wrap:wrap;gap:var(--space-4);\">\n";
foreach ($ctas as $cta) {
$href = $home_url . ltrim($cta['href'], '/');
$variant = ($cta['variant'] === 'primary') ? 'av-btn--primary' : ($on_dark ? 'av-btn--on-dark av-btn--secondary' : 'av-btn--secondary');
$out .= '<a class="av-btn ' . esc_attr($variant) . ' av-btn--lg" href="' . esc_url($href) . '">' . esc_html($cta['label']) . "</a>\n";
}
$out .= "</div>\n<!-- /wp:html -->\n\n";
return $out;
}
/* =================================================================== */
/* Hero blocks */
/* =================================================================== */
function av_render_solution_hero($data, $home_url) {
$eyebrow = esc_html($data['eyebrow']);
$title = esc_html($data['title']);
$tagline = esc_html($data['tagline']);
$lead = esc_html($data['hero_lead']);
$ctas = '';
foreach ($data['ctas'] as $cta) {
$href = $home_url . ltrim($cta['href'], '/');
$variant = ($cta['variant'] === 'primary') ? 'av-btn--primary' : 'av-btn--secondary av-btn--on-dark';
$ctas .= '<a class="av-btn ' . $variant . ' av-btn--lg" href="' . esc_url($href) . '">' . esc_html($cta['label']) . "</a>\n";
}
$html = <<<HTML
<section class="av-hero">
<div class="av-hero__media" aria-hidden="true">
<div style="width:100%;height:100%;background:radial-gradient(ellipse at 70% 30%, rgba(201,164,90,0.15), transparent 60%),linear-gradient(135deg, #081427 0%, #0B1F3A 50%, #13294B 100%);"></div>
</div>
<div class="av-container">
<div class="av-hero__inner">
<p class="av-hero__eyebrow">{$eyebrow}</p>
<h1 class="av-hero__headline" style="font-size:clamp(2.25rem,5vw,var(--text-display-lg));line-height:var(--leading-display-lg);">{$title}&trade; — {$tagline}</h1>
<p class="av-hero__sub">{$lead}</p>
<div class="av-hero__ctas">{$ctas}</div>
</div>
</div>
</section>
HTML;
return av_block_html($html);
}
function av_render_industry_hero($data, $home_url) {
$eyebrow = esc_html($data['eyebrow']);
$tagline = esc_html($data['tagline']);
$lead = esc_html($data['hero_lead']);
$ctas = '';
foreach ($data['ctas'] as $cta) {
$href = $home_url . ltrim($cta['href'], '/');
$variant = ($cta['variant'] === 'primary') ? 'av-btn--primary' : 'av-btn--secondary av-btn--on-dark';
$ctas .= '<a class="av-btn ' . $variant . ' av-btn--lg" href="' . esc_url($href) . '">' . esc_html($cta['label']) . "</a>\n";
}
$html = <<<HTML
<section class="av-hero">
<div class="av-hero__media" aria-hidden="true">
<div style="width:100%;height:100%;background:radial-gradient(ellipse at 50% 40%, rgba(11,31,58,0.4), transparent 65%),linear-gradient(135deg, #081427 0%, #0B1F3A 50%, #1F3252 100%);"></div>
</div>
<div class="av-container">
<div class="av-hero__inner">
<p class="av-hero__eyebrow">{$eyebrow}</p>
<h1 class="av-hero__headline">{$tagline}</h1>
<p class="av-hero__sub">{$lead}</p>
<div class="av-hero__ctas">{$ctas}</div>
</div>
</div>
</section>
HTML;
return av_block_html($html);
}
/* =================================================================== */
/* Section renderer (shared by solution / industry / technology / */
/* editorial detail pages) */
/* =================================================================== */
function av_render_data_section($section, $is_dark, $home_url) {
$section_class = 'av-section' . ($is_dark ? ' av-section--dark' : '');
$eye_class = 'av-eyebrow' . ($is_dark ? ' av-eyebrow--gold' : '');
$eyebrow = esc_html($section['eyebrow'] ?? '');
$title = esc_html($section['title'] ?? '');
$lead = ! empty($section['lead']) ? '<p class="av-section__lead">' . esc_html($section['lead']) . '</p>' : '';
$head_html = <<<HTML
<section class="{$section_class}">
<div class="av-container">
<div class="av-section__head">
<span class="{$eye_class}">{$eyebrow}</span>
<h2 class="av-section__title">{$title}</h2>
{$lead}
</div>
HTML;
$body = '';
switch ($section['type']) {
case 'prose':
$body = '<p class="av-section__lead" style="max-width:72ch;font-size:var(--text-body-lg);">' . esc_html($section['body'] ?? '') . '</p>';
break;
case 'cards':
$body = '<div class="av-grid-3">';
foreach ($section['items'] as $card) {
$card_class = 'av-card' . ($is_dark ? ' is-on-dark' : '') . ' av-card--feature';
$body .= '<article class="' . $card_class . '"><div class="av-card__body">';
$body .= '<h3 class="av-card__title" style="font-size:var(--text-h4);">' . esc_html($card['title']) . '</h3>';
$body .= '<p class="av-card__excerpt">' . esc_html($card['body']) . '</p>';
$body .= '</div></article>';
}
$body .= '</div>';
break;
case 'list':
$color = $is_dark ? 'color:var(--color-text-on-dark-muted);' : 'color:var(--color-text-secondary);';
$body = '<ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:var(--space-3);max-width:72ch;font-size:var(--text-body-lg);line-height:var(--leading-body-lg);' . $color . '">';
foreach ($section['items'] as $item) {
$body .= '<li style="padding-left:var(--space-6);position:relative;"><span aria-hidden="true" style="position:absolute;left:0;top:0.6em;width:8px;height:8px;background:var(--color-brand-gold);border-radius:50%;"></span>' . esc_html($item) . '</li>';
}
$body .= '</ul>';
if (! empty($section['cta'])) {
$href = $home_url . ltrim($section['cta']['href'], '/');
$linkcls = 'av-link' . ($is_dark ? ' av-link--on-dark' : '') . ' av-link--with-arrow';
$body .= '<p style="margin-top:var(--space-8);"><a class="' . $linkcls . '" href="' . esc_url($href) . '">' . esc_html($section['cta']['label']) . '</a></p>';
}
break;
case 'cta':
$body = '<p class="av-section__lead" style="max-width:72ch;margin-bottom:var(--space-6);">' . esc_html($section['body'] ?? '') . '</p>';
if (! empty($section['ctas'])) {
$body .= '<div style="display:flex;flex-wrap:wrap;gap:var(--space-4);">';
foreach ($section['ctas'] as $cta) {
$href = $home_url . ltrim($cta['href'], '/');
$variant = ($cta['variant'] === 'primary') ? 'av-btn--primary' : ($is_dark ? 'av-btn--on-dark av-btn--secondary' : 'av-btn--secondary');
$body .= '<a class="av-btn ' . $variant . ' av-btn--lg" href="' . esc_url($href) . '">' . esc_html($cta['label']) . "</a>";
}
$body .= '</div>';
}
break;
}
$foot_html = "</div></section>";
return av_block_html($head_html . $body . $foot_html);
}
/* =================================================================== */
/* Conversion banner (shared) */
/* =================================================================== */
function av_render_conversion_banner($home_url) {
$html = <<<HTML
<section class="av-conversion-banner">
<div class="av-container">
<div class="av-conversion-banner__head">
<h2 class="av-conversion-banner__title">Train like the threat is now.</h2>
<p class="av-conversion-banner__lead">Three paths into the platform — pick the one that fits where you are.</p>
</div>
<div class="av-conversion-banner__grid">
<div class="av-conversion-banner__card">
<h3 class="av-conversion-banner__card-title">Request a Demo</h3>
<p class="av-conversion-banner__card-desc">A 30-minute tailored session with our team.</p>
<a href="{$home_url}request-demo/" class="av-btn av-btn--primary av-btn--md">Schedule a demo</a>
</div>
<div class="av-conversion-banner__card">
<h3 class="av-conversion-banner__card-title">Request a T&amp;E Kit</h3>
<p class="av-conversion-banner__card-desc">A PROSERVE case on loan at your facility for 5 to 10 days.</p>
<a href="{$home_url}request-te-kit/" class="av-btn av-btn--on-dark av-btn--secondary av-btn--md">Request the kit</a>
</div>
<div class="av-conversion-banner__card">
<h3 class="av-conversion-banner__card-title">Request a Quote</h3>
<p class="av-conversion-banner__card-desc">A detailed quote and deployment plan within 5 working days.</p>
<a href="{$home_url}request-quote/" class="av-btn av-btn--on-dark av-btn--secondary av-btn--md">Get a quote</a>
</div>
</div>
</div>
</section>
HTML;
return av_block_html($html);
}
/* =================================================================== */
/* Top-level renderers */
/* =================================================================== */
function av_render_solution_blocks($data) {
$home_url = home_url('/');
$out = av_render_solution_hero($data, $home_url);
foreach ($data['sections'] as $idx => $section) {
$out .= av_render_data_section($section, ($idx % 2 === 1), $home_url);
}
if (! empty($data['upsell'])) {
$upsell_html = '<section class="av-section" style="background-color:var(--color-bg-subtle);"><div class="av-container">';
$upsell_html .= '<div class="av-section__head">';
$upsell_html .= '<span class="av-eyebrow">Comparison and upgrade</span>';
$upsell_html .= '<h2 class="av-section__title">' . esc_html($data['upsell']['title']) . '</h2>';
$upsell_html .= '<p class="av-section__lead">' . esc_html($data['upsell']['body']) . '</p>';
$upsell_html .= '</div><div style="display:flex;flex-wrap:wrap;gap:var(--space-4);">';
foreach ($data['upsell']['links'] as $link) {
$upsell_html .= '<a class="av-btn av-btn--secondary av-btn--md" href="' . esc_url($home_url . ltrim($link['href'], '/')) . '">' . esc_html($link['label']) . "</a>";
}
$upsell_html .= '</div></div></section>';
$out .= av_block_html($upsell_html);
}
$out .= av_render_conversion_banner($home_url);
return $out;
}
function av_render_industry_blocks($data) {
$home_url = home_url('/');
$out = av_render_industry_hero($data, $home_url);
foreach ($data['sections'] as $idx => $section) {
$out .= av_render_data_section($section, ($idx % 2 === 1), $home_url);
}
$out .= av_render_conversion_banner($home_url);
return $out;
}
function av_render_technology_blocks($data) {
return av_render_industry_blocks($data); // identical schema
}
function av_render_editorial_blocks($data) {
return av_render_industry_blocks($data);
}
function av_render_form_blocks($data, $slug) {
$home_url = home_url('/');
// Hero
$eyebrow = esc_html($data['eyebrow']);
$tagline = esc_html($data['tagline']);
$lead = esc_html($data['hero_lead']);
$shortcuts_html = '';
if (! empty($data['shortcuts'])) {
$shortcuts_html .= '<div class="av-hero__ctas">';
foreach ($data['shortcuts'] as $sc) {
$variant = ($sc['variant'] === 'primary') ? 'av-btn--primary' : 'av-btn--on-dark av-btn--secondary';
$shortcuts_html .= '<a class="av-btn ' . $variant . ' av-btn--md" href="' . esc_url($home_url . ltrim($sc['href'], '/')) . '">' . esc_html($sc['label']) . '</a>';
}
$shortcuts_html .= '</div>';
}
$hero_html = <<<HTML
<section class="av-hero">
<div class="av-hero__media" aria-hidden="true">
<div style="width:100%;height:100%;background:radial-gradient(ellipse at 50% 30%, rgba(201,164,90,0.12), transparent 60%),linear-gradient(135deg, #081427 0%, #0B1F3A 50%, #13294B 100%);"></div>
</div>
<div class="av-container">
<div class="av-hero__inner">
<p class="av-hero__eyebrow">{$eyebrow}</p>
<h1 class="av-hero__headline" style="font-size:clamp(2.25rem,5vw,var(--text-display-lg));line-height:var(--leading-display-lg);">{$tagline}</h1>
<p class="av-hero__sub">{$lead}</p>
{$shortcuts_html}
</div>
</div>
</section>
HTML;
$out = av_block_html($hero_html);
// The form itself stays a shortcode — added by template wrapper instead.
// To keep the page editable, we drop a placeholder shortcode that the
// child theme's `av_form` shortcode handler renders on the front-end.
$out .= av_block_html('<div class="av-form-placeholder">[av_form slug="' . esc_attr($slug) . '"]</div>');
return $out;
}
function av_render_overview_blocks($cards, $hero_eyebrow, $hero_title, $hero_sub) {
$home_url = home_url('/');
$hero_html = <<<HTML
<section class="av-hero">
<div class="av-hero__media" aria-hidden="true">
<div style="width:100%;height:100%;background:linear-gradient(135deg, #081427 0%, #0B1F3A 50%, #13294B 100%);"></div>
</div>
<div class="av-container">
<div class="av-hero__inner">
<p class="av-hero__eyebrow">{$hero_eyebrow}</p>
<h1 class="av-hero__headline">{$hero_title}</h1>
<p class="av-hero__sub">{$hero_sub}</p>
</div>
</div>
</section>
HTML;
$out = av_block_html($hero_html);
$cards_html = '<section class="av-section"><div class="av-container"><div class="av-grid-3">';
foreach ($cards as $c) {
$href = $home_url . ltrim($c['href'], '/');
$cards_html .= '<article class="av-card"><a href="' . esc_url($href) . '" class="av-card__media" tabindex="-1" style="background:linear-gradient(135deg,#1F3252,#0B1F3A);display:block;"></a>';
$cards_html .= '<div class="av-card__body">';
$cards_html .= '<span class="av-card__eyebrow">' . esc_html($c['eyebrow'] ?? '') . '</span>';
$cards_html .= '<h2 class="av-card__title"><a href="' . esc_url($href) . '" style="color:inherit;text-decoration:none;">' . esc_html($c['title']) . '</a></h2>';
$cards_html .= '<p class="av-card__excerpt">' . esc_html($c['excerpt']) . '</p>';
$cards_html .= '<div class="av-card__cta"><a href="' . esc_url($href) . '" class="av-link av-link--with-arrow">' . esc_html($c['cta']) . '</a></div>';
$cards_html .= '</div></article>';
}
$cards_html .= '</div></div></section>';
$out .= av_block_html($cards_html);
return $out;
}

View File

@@ -1,258 +0,0 @@
<?php
/**
* Asterion Bricks — Bricks Builder native data renderer (POC: Industries)
*
* Generates the array of element descriptors that Bricks Builder expects in
* the post meta `_bricks_page_content_2` (constant `BRICKS_DB_PAGE_CONTENT`).
*
* Schema, confirmed by reading Bricks parent theme source
* (`includes/password-protection.php` and `includes/elements/base.php`):
*
* [
* [
* 'id' => string, // unique, via Bricks\Helpers::generate_random_id(false)
* 'name' => 'section' | 'container' | 'block' | 'heading' | 'text-basic' | 'button' | …,
* 'parent' => 0 | string, // 0 = page root, else parent's id
* 'children' => [string, …], // optional, for nestable elements
* 'settings' => [
* 'text' => 'Hello',
* 'tag' => 'h1' | 'h2' | …,
* 'link' => ['type' => 'external', 'url' => 'https://…'],
* '_cssClasses' => 'av-hero',
* …
* ],
* ],
* …
* ]
*
* Once stored, opening the page in WP admin and clicking "Edit with Bricks"
* surfaces every element in the Bricks builder canvas, fully editable.
*
* Frontend rendering is handled by Bricks itself — our `page.php` simply
* lets Bricks take over via its `template_include` filter.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
/**
* Generate a Bricks-compatible random element ID.
* Falls back to a local md5 prefix if the Bricks namespace isn't loaded yet.
*/
function av_brx_id() {
if (class_exists('\\Bricks\\Helpers') && method_exists('\\Bricks\\Helpers', 'generate_random_id')) {
return \Bricks\Helpers::generate_random_id(false);
}
return substr(md5(uniqid((string) mt_rand(), true)), 0, 6);
}
/**
* Tiny element factory.
*/
function av_brx_el($name, $parent, $settings = [], $children = null) {
$el = [
'id' => av_brx_id(),
'name' => $name,
'parent' => $parent,
'settings' => $settings,
];
if ($children !== null) {
$el['children'] = $children;
}
return $el;
}
/* =================================================================== */
/* Industries overview — proof of concept */
/* =================================================================== */
function av_brx_render_industries_overview() {
$home_url = home_url('/');
/* ---- pre-allocate IDs so we can hand them to parents/children ---- */
$hero_section_id = av_brx_id();
$hero_container_id = av_brx_id();
$hero_eyebrow_id = av_brx_id();
$hero_h1_id = av_brx_id();
$hero_sub_id = av_brx_id();
$cards_section_id = av_brx_id();
$cards_container_id = av_brx_id();
$cards_grid_id = av_brx_id();
$industries = [
[
'eyebrow' => 'National & Municipal',
'title' => 'Police',
'excerpt' => 'Vehicle stops, identity checks, urban CQB, de-escalation, riot containment. Built for the realities of contemporary policing.',
'href' => '/industries/police/',
'cta' => 'Police training',
],
[
'eyebrow' => 'Tactical units',
'title' => 'Special Forces',
'excerpt' => 'Hostage rescue, dynamic entries, hostile crowd extraction, sniper-spotter coordination. Built for the units where every error has a name.',
'href' => '/industries/special-forces/',
'cta' => 'Special forces training',
],
[
'eyebrow' => 'Armed forces',
'title' => 'Military',
'excerpt' => 'MOUT, convoy escort, FOB defense, IED awareness, joint operation drills. Built for armed forces that deploy fast and adapt faster.',
'href' => '/industries/military/',
'cta' => 'Military training',
],
[
'eyebrow' => 'Emergency services',
'title' => 'Firefighters',
'excerpt' => "Structure fires, hazmat, multi-casualty triage, confined-space rescue, industrial accidents. Built for first responders who can't rehearse the worst day at full scale.",
'href' => '/industries/firefighters/',
'cta' => 'Firefighter training',
],
];
$card_ids = [];
foreach ($industries as $i => $_) {
$card_ids[$i] = [
'card' => av_brx_id(),
'eyebrow' => av_brx_id(),
'title' => av_brx_id(),
'excerpt' => av_brx_id(),
'btn' => av_brx_id(),
];
}
$cards_children = array_map(fn($ids) => $ids['card'], $card_ids);
/* ---- assemble flat element array ---- */
$elements = [];
// 1. Hero section ----------------------------------------------------
$elements[] = [
'id' => $hero_section_id,
'name' => 'section',
'parent' => 0,
'children' => [$hero_container_id],
'settings' => ['_cssClasses' => 'av-hero'],
'label' => 'Hero',
];
$elements[] = [
'id' => $hero_container_id,
'name' => 'container',
'parent' => $hero_section_id,
'children' => [$hero_eyebrow_id, $hero_h1_id, $hero_sub_id],
'settings' => ['_cssClasses' => 'av-container av-hero__inner'],
];
$elements[] = [
'id' => $hero_eyebrow_id,
'name' => 'text-basic',
'parent' => $hero_container_id,
'settings' => [
'text' => 'Industries',
'tag' => 'p',
'_cssClasses' => 'av-hero__eyebrow',
],
];
$elements[] = [
'id' => $hero_h1_id,
'name' => 'heading',
'parent' => $hero_container_id,
'settings' => [
'tag' => 'h1',
'text' => 'Built with operators, for operators.',
'_cssClasses' => 'av-hero__headline',
],
];
$elements[] = [
'id' => $hero_sub_id,
'name' => 'text-basic',
'parent' => $hero_container_id,
'settings' => [
'text' => 'PROSERVE™ is not a generic VR platform repackaged for the public sector. Every scenario, every weapon profile, every instructor metric was specified by serving and former operators in close partnership with our engineering team.',
'tag' => 'p',
'_cssClasses' => 'av-hero__sub',
],
];
// 2. Cards section ---------------------------------------------------
$elements[] = [
'id' => $cards_section_id,
'name' => 'section',
'parent' => 0,
'children' => [$cards_container_id],
'settings' => ['_cssClasses' => 'av-section'],
'label' => 'Industries grid',
];
$elements[] = [
'id' => $cards_container_id,
'name' => 'container',
'parent' => $cards_section_id,
'children' => [$cards_grid_id],
'settings' => ['_cssClasses' => 'av-container'],
];
$elements[] = [
'id' => $cards_grid_id,
'name' => 'block',
'parent' => $cards_container_id,
'children' => $cards_children,
'settings' => ['_cssClasses' => 'av-grid-4'],
];
foreach ($industries as $i => $ind) {
$ids = $card_ids[$i];
$elements[] = [
'id' => $ids['card'],
'name' => 'block',
'parent' => $cards_grid_id,
'children' => [$ids['eyebrow'], $ids['title'], $ids['excerpt'], $ids['btn']],
'settings' => ['_cssClasses' => 'av-card'],
'label' => $ind['title'],
];
$elements[] = [
'id' => $ids['eyebrow'],
'name' => 'text-basic',
'parent' => $ids['card'],
'settings' => [
'text' => $ind['eyebrow'],
'tag' => 'p',
'_cssClasses' => 'av-card__eyebrow',
],
];
$elements[] = [
'id' => $ids['title'],
'name' => 'heading',
'parent' => $ids['card'],
'settings' => [
'tag' => 'h2',
'text' => $ind['title'],
'_cssClasses' => 'av-card__title',
],
];
$elements[] = [
'id' => $ids['excerpt'],
'name' => 'text-basic',
'parent' => $ids['card'],
'settings' => [
'text' => $ind['excerpt'],
'tag' => 'p',
'_cssClasses' => 'av-card__excerpt',
],
];
$elements[] = [
'id' => $ids['btn'],
'name' => 'button',
'parent' => $ids['card'],
'settings' => [
'text' => $ind['cta'],
'tag' => 'a',
'link' => [
'type' => 'external',
'url' => $home_url . ltrim($ind['href'], '/'),
],
'_cssClasses' => 'av-btn av-btn--secondary av-btn--md',
],
];
}
return $elements;
}

View File

@@ -1,60 +0,0 @@
<?php
/**
* Asterion Bricks — SEO module
*
* Hosts custom Schema.org JSON-LD and any meta-tag overrides specific to
* Asterion VR. Most generic SEO (title tags, OG, sitemap) is delegated to
* Rank Math or Yoast (per BRIEF section 4).
*
* Planned :
* - Organization schema on home + footer-rendered pages
* - Product schema on /solutions/* pages (each tier = a Product)
* - Article schema on /insights/<slug>/
* - FAQPage schema on /trust/ and conversion templates
* - BreadcrumbList schema site-wide
*
* @package AsterionBricks
* @since 0.1.0
*/
defined('ABSPATH') || exit;
/**
* Inject Organization JSON-LD on the home page.
* Safe minimum — extended once logos/SameAs URLs are confirmed.
*/
add_action('wp_head', function () {
if (! is_front_page()) {
return;
}
$org = [
'@context' => 'https://schema.org',
'@type' => 'Organization',
'name' => 'Asterion VR',
'url' => home_url('/'),
'logo' => get_stylesheet_directory_uri() . '/assets/img/logo-asterion-vr.svg',
'foundingDate' => '2016',
'address' => [
'@type' => 'PostalAddress',
'addressLocality' => 'Montgermont',
'addressRegion' => 'Bretagne',
'addressCountry' => 'FR',
],
'sameAs' => [
// TODO : confirm public profiles with client.
// 'https://www.linkedin.com/company/asterion-vr/',
// 'https://www.youtube.com/@asterionvr',
],
];
printf(
"<script type=\"application/ld+json\">%s</script>\n",
wp_json_encode($org, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
);
}, 5);
// TODO : Product schema for /solutions/<tier>/ pages.
// TODO : Article schema for /insights/<slug>/ pages.
// TODO : FAQPage schema for /trust/ and conversion pages.
// TODO : BreadcrumbList schema site-wide.

View File

@@ -1,35 +0,0 @@
<?php
/**
* Asterion Bricks — Shortcodes
*
* Lets the migrated post_content reference dynamic blocks (form, recent
* posts, etc.) without hardcoding them in the Gutenberg payload.
*
* [av_form slug="request-demo"] — renders one of the 4 conversion forms
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
add_shortcode('av_form', function ($atts) {
$atts = shortcode_atts([
'slug' => '',
], $atts, 'av_form');
$slug = sanitize_key($atts['slug']);
if (! $slug) {
return '';
}
$data = include AV_THEME_DIR . '/inc/forms-data.php';
if (! isset($data[$slug])) {
return '<p style="color:var(--color-error);">[av_form] : unknown slug "' . esc_html($slug) . '"</p>';
}
set_query_var('av_form', $data[$slug]);
set_query_var('av_form_slug_override', $slug);
ob_start();
get_template_part('template-parts/conversion-form');
return ob_get_clean();
});

View File

@@ -1,70 +0,0 @@
<?php
/**
* Asterion Bricks — Default index template
*
* Fallback used by WordPress when no more specific template matches.
* Renders standard post lists for archives and the blog hub.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
get_header();
?>
<section class="av-section">
<div class="av-container av-container--narrow">
<?php if (have_posts()) : ?>
<header class="av-section__head">
<?php if (is_archive()) : ?>
<h1 class="av-section__title"><?php the_archive_title(); ?></h1>
<?php $desc = get_the_archive_description(); if ($desc) : ?>
<div class="av-section__lead"><?php echo wp_kses_post($desc); ?></div>
<?php endif; ?>
<?php elseif (is_search()) : ?>
<h1 class="av-section__title"><?php printf(esc_html__('Search results for « %s »', 'asterion-bricks'), get_search_query()); ?></h1>
<?php elseif (is_home()) : ?>
<h1 class="av-section__title"><?php esc_html_e('Insights', 'asterion-bricks'); ?></h1>
<?php endif; ?>
</header>
<div class="av-stack av-stack--xl">
<?php while (have_posts()) : the_post(); ?>
<article <?php post_class('av-card av-card--blog'); ?> style="display:block;background:transparent;box-shadow:none;border-bottom:1px solid var(--color-border-subtle);padding-bottom:var(--space-8);">
<header style="display:flex;flex-direction:column;gap:var(--space-2);margin-bottom:var(--space-3);">
<span class="av-eyebrow"><?php echo esc_html(get_the_date()); ?></span>
<h2 style="font-family:var(--font-display);font-size:var(--text-h2);line-height:var(--leading-h2);font-weight:var(--font-weight-bold);">
<a href="<?php the_permalink(); ?>" style="color:inherit;text-decoration:none;"><?php the_title(); ?></a>
</h2>
</header>
<div style="color:var(--color-text-secondary);font-size:var(--text-body-md);line-height:var(--leading-body-md);">
<?php the_excerpt(); ?>
</div>
<p style="margin-top:var(--space-3);">
<a href="<?php the_permalink(); ?>" class="av-link av-link--with-arrow"><?php esc_html_e('Read more', 'asterion-bricks'); ?></a>
</p>
</article>
<?php endwhile; ?>
</div>
<nav class="av-pagination" style="margin-top:var(--space-12);">
<?php the_posts_pagination([
'mid_size' => 1,
'prev_text' => esc_html__('← Previous', 'asterion-bricks'),
'next_text' => esc_html__('Next →', 'asterion-bricks'),
]); ?>
</nav>
<?php else : ?>
<header class="av-section__head">
<h1 class="av-section__title"><?php esc_html_e('Nothing here yet.', 'asterion-bricks'); ?></h1>
<p class="av-section__lead"><?php esc_html_e('No content matches your request. Try a different search, or head back home.', 'asterion-bricks'); ?></p>
</header>
<p><a class="av-btn av-btn--primary av-btn--md" href="<?php echo esc_url(home_url('/')); ?>"><?php esc_html_e('Back to home', 'asterion-bricks'); ?></a></p>
<?php endif; ?>
</div>
</section>
<?php get_footer(); ?>

View File

@@ -1,38 +0,0 @@
<?php
/**
* Asterion Bricks — Default page template
*
* If the current page has Bricks data in its `_bricks_page_content_2` post
* meta, defer to Bricks' own renderer (Bricks\Frontend::render_content). This
* is the same pattern Bricks parent's page.php uses; we keep it here so the
* child theme stays in charge of the markup wrapping (header, footer).
*
* Otherwise fall back to the standard the_content() loop — covers pages
* still rendered from post_content (Gutenberg) and any temporarily-empty
* page that just hits a non-Bricks Custom Template.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
get_header();
$av_bricks_data = class_exists('\\Bricks\\Helpers')
? \Bricks\Helpers::get_bricks_data(get_the_ID(), 'content')
: null;
if (! empty($av_bricks_data) && class_exists('\\Bricks\\Frontend')) {
// Bricks emits its own <main id="brx-content"> wrapper — render with `div`
// tag so we don't end up with double <main> (the global one comes from
// header.php / footer.php).
\Bricks\Frontend::render_content($av_bricks_data, ['id' => 'brx-content'], '', '', 'div');
} else {
if (have_posts()) {
while (have_posts()) {
the_post();
the_content();
}
}
}
get_footer();

View File

@@ -1,209 +0,0 @@
<?php
/**
* Asterion Bricks — Conversion form partial
*
* Renders one conversion page (Demo / T&E / Quote / Contact). Two-column
* hero (form left, promise right) + optional eligibility list + FAQ.
*
* Data passed via `set_query_var('av_form', $form_data)`.
*
* Form posts to admin-post.php with action=av_form_submit; the handler in
* inc/form-handler.php validates the nonce, sanitizes, and emails the lead
* to jerome.foucher@asterionvr.com.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
$form = get_query_var('av_form');
if (! is_array($form)) {
return;
}
$home_url = home_url('/');
$slug_override = get_query_var('av_form_slug_override');
$slug = $slug_override ?: get_post_field('post_name', get_queried_object_id());
// Surface success/error from query string (set by the handler on redirect).
$av_status = isset($_GET['av_status']) ? sanitize_key($_GET['av_status']) : '';
?>
<!-- HERO -->
<section class="av-hero" aria-labelledby="form-headline">
<div class="av-hero__media" aria-hidden="true">
<div style="width:100%;height:100%;background:
radial-gradient(ellipse at 50% 30%, rgba(201,164,90,0.12), transparent 60%),
linear-gradient(135deg, #081427 0%, #0B1F3A 50%, #13294B 100%);"></div>
</div>
<div class="av-container">
<div class="av-hero__inner">
<p class="av-hero__eyebrow"><?php echo esc_html($form['eyebrow']); ?></p>
<h1 id="form-headline" class="av-hero__headline" style="font-size:clamp(2.25rem,5vw,var(--text-display-lg));line-height:var(--leading-display-lg);"><?php echo esc_html($form['tagline']); ?></h1>
<p class="av-hero__sub"><?php echo esc_html($form['hero_lead']); ?></p>
<?php if (! empty($form['shortcuts'])) : ?>
<div class="av-hero__ctas">
<?php foreach ($form['shortcuts'] as $sc) :
$variant = ($sc['variant'] === 'primary') ? 'av-btn--primary' : 'av-btn--on-dark av-btn--secondary';
?>
<a href="<?php echo esc_url($home_url . ltrim($sc['href'], '/')); ?>" class="av-btn <?php echo esc_attr($variant); ?> av-btn--md">
<?php echo esc_html($sc['label']); ?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</section>
<!-- TWO-COLUMN: form (left) + promise (right) -->
<section class="av-section" aria-labelledby="form-title">
<div class="av-container">
<?php if ($av_status === 'success') : ?>
<div role="status" aria-live="polite" style="padding:var(--space-6);background:rgba(31,139,76,0.08);border-left:3px solid var(--color-success);border-radius:var(--radius-sm);margin-bottom:var(--space-8);">
<strong><?php esc_html_e('Thank you — your request was sent.', 'asterion-bricks'); ?></strong><br>
<?php esc_html_e('We will reply within the timeframe stated above. Check your inbox (and spam) for confirmation.', 'asterion-bricks'); ?>
</div>
<?php elseif ($av_status === 'error') : ?>
<div role="alert" style="padding:var(--space-6);background:rgba(200,16,46,0.06);border-left:3px solid var(--color-error);border-radius:var(--radius-sm);margin-bottom:var(--space-8);">
<strong><?php esc_html_e('Something went wrong.', 'asterion-bricks'); ?></strong>
<?php esc_html_e(' Please try again, or email us directly at hello@asterionvr.com.', 'asterion-bricks'); ?>
</div>
<?php endif; ?>
<div style="display:grid;grid-template-columns:1fr;gap:var(--space-12);" class="av-form-grid">
<!-- FORM -->
<div>
<h2 id="form-title" class="av-section__title" style="font-size:var(--text-h2);"><?php esc_html_e('Tell us about your unit.', 'asterion-bricks'); ?></h2>
<p class="av-section__lead" style="margin-bottom:var(--space-6);"><?php echo esc_html($form['reassurance']); ?></p>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="av-form">
<?php wp_nonce_field('av_form_submit', 'av_nonce'); ?>
<input type="hidden" name="action" value="av_form_submit">
<input type="hidden" name="form_slug" value="<?php echo esc_attr($slug); ?>">
<input type="hidden" name="redirect_to" value="<?php echo esc_attr(get_permalink()); ?>">
<input type="text" name="av_hp" value="" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px;" aria-hidden="true">
<div class="av-stack av-stack--lg">
<?php foreach ($form['fields'] as $field) :
$field_id = 'av-' . esc_attr($field['name']);
$required = ! empty($field['required']);
?>
<div>
<label for="<?php echo esc_attr($field_id); ?>" class="av-label">
<?php echo esc_html($field['label']); ?>
<?php if ($required) : ?><span class="av-label__required" aria-hidden="true">*</span><?php endif; ?>
</label>
<?php if ($field['type'] === 'textarea') : ?>
<textarea
id="<?php echo esc_attr($field_id); ?>"
name="<?php echo esc_attr($field['name']); ?>"
class="av-textarea"
rows="5"
<?php echo $required ? 'required aria-required="true"' : ''; ?>></textarea>
<?php elseif ($field['type'] === 'select') : ?>
<select
id="<?php echo esc_attr($field_id); ?>"
name="<?php echo esc_attr($field['name']); ?>"
class="av-select"
<?php echo $required ? 'required aria-required="true"' : ''; ?>>
<?php foreach ($field['options'] as $opt) : ?>
<option value="<?php echo esc_attr($opt); ?>"<?php echo $opt === '' ? ' disabled selected' : ''; ?>>
<?php echo $opt === '' ? esc_html__('— select —', 'asterion-bricks') : esc_html($opt); ?>
</option>
<?php endforeach; ?>
</select>
<?php else : ?>
<input
type="<?php echo esc_attr($field['type']); ?>"
id="<?php echo esc_attr($field_id); ?>"
name="<?php echo esc_attr($field['name']); ?>"
class="av-input"
<?php echo $required ? 'required aria-required="true"' : ''; ?>
autocomplete="<?php echo $field['type'] === 'email' ? 'email' : 'on'; ?>">
<?php endif; ?>
</div>
<?php endforeach; ?>
<button type="submit" class="av-btn av-btn--primary av-btn--lg" style="width:auto;align-self:flex-start;">
<?php echo esc_html($form['submit_label'] ?? __('Send', 'asterion-bricks')); ?>
</button>
</div>
</form>
</div>
<!-- PROMISE -->
<aside style="background:var(--color-bg-subtle);padding:var(--space-8);border-radius:var(--radius-md);align-self:start;">
<h2 style="font-family:var(--font-display);font-size:var(--text-h3);font-weight:var(--font-weight-semibold);margin-bottom:var(--space-4);">
<?php echo esc_html($form['promise_title']); ?>
</h2>
<ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:var(--space-3);font-size:var(--text-body-md);line-height:var(--leading-body-md);color:var(--color-text-secondary);">
<?php foreach ($form['promise_items'] as $item) : ?>
<li style="padding-left:var(--space-6);position:relative;">
<span aria-hidden="true" style="position:absolute;left:0;top:0.55em;width:8px;height:8px;background:var(--color-brand-gold);border-radius:50%;"></span>
<?php echo esc_html($item); ?>
</li>
<?php endforeach; ?>
</ul>
</aside>
</div>
<style>
@media (min-width: 1024px) {
.av-form-grid { grid-template-columns: 1.4fr 1fr !important; }
}
</style>
</div>
</section>
<!-- ELIGIBILITY (T&E only) -->
<?php if (! empty($form['eligibility'])) : ?>
<section class="av-section av-section--dark" data-av-reveal>
<div class="av-container">
<div class="av-section__head">
<span class="av-eyebrow av-eyebrow--gold"><?php esc_html_e('Eligibility', 'asterion-bricks'); ?></span>
<h2 class="av-section__title"><?php echo esc_html($form['eligibility']['title']); ?></h2>
<p class="av-section__lead"><?php echo esc_html($form['eligibility']['lead']); ?></p>
</div>
<ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:var(--space-3);max-width:72ch;color:var(--color-text-on-dark-muted);">
<?php foreach ($form['eligibility']['items'] as $item) : ?>
<li style="padding-left:var(--space-6);position:relative;">
<span aria-hidden="true" style="position:absolute;left:0;top:0.6em;width:8px;height:8px;background:var(--color-brand-gold);border-radius:50%;"></span>
<?php echo esc_html($item); ?>
</li>
<?php endforeach; ?>
</ul>
</div>
</section>
<?php endif; ?>
<!-- FAQ (Demo only) -->
<?php if (! empty($form['faq'])) : ?>
<section class="av-section" data-av-reveal>
<div class="av-container av-container--narrow">
<div class="av-section__head">
<span class="av-eyebrow"><?php esc_html_e('Frequently asked', 'asterion-bricks'); ?></span>
<h2 class="av-section__title"><?php esc_html_e('Quick answers.', 'asterion-bricks'); ?></h2>
</div>
<div class="av-stack av-stack--lg">
<?php foreach ($form['faq'] as $i => $qa) : ?>
<details style="border-bottom:1px solid var(--color-border-subtle);padding-block:var(--space-4);">
<summary style="font-family:var(--font-display);font-size:var(--text-h4);font-weight:var(--font-weight-semibold);cursor:pointer;list-style:none;display:flex;justify-content:space-between;align-items:center;">
<?php echo esc_html($qa['q']); ?>
<span aria-hidden="true">+</span>
</summary>
<p style="margin-top:var(--space-3);color:var(--color-text-secondary);font-size:var(--text-body-md);line-height:var(--leading-body-md);">
<?php echo esc_html($qa['a']); ?>
</p>
</details>
<?php endforeach; ?>
</div>
</div>
</section>
<?php endif; ?>

View File

@@ -1,130 +0,0 @@
<?php
/**
* Asterion Bricks — Industry detail partial
*
* Renders one industry page (Police / Special Forces / Military / Firefighters)
* from data passed via `set_query_var('av_industry', $industry_data)`.
*
* Section types : prose | cards | list | cta (same as solution-detail).
*
* Hero differs from solution-detail : eyebrow + H1 (tagline) only — no
* trademark concat, no upsell block.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
$industry = get_query_var('av_industry');
if (! is_array($industry)) {
return;
}
$home_url = home_url('/');
?>
<!-- HERO -->
<section class="av-hero" aria-labelledby="industry-headline">
<div class="av-hero__media" aria-hidden="true">
<div style="width:100%;height:100%;background:
radial-gradient(ellipse at 50% 40%, rgba(11,31,58,0.4), transparent 65%),
linear-gradient(135deg, #081427 0%, #0B1F3A 50%, #1F3252 100%);"></div>
</div>
<div class="av-container">
<div class="av-hero__inner">
<p class="av-hero__eyebrow"><?php echo esc_html($industry['eyebrow']); ?></p>
<h1 id="industry-headline" class="av-hero__headline"><?php echo esc_html($industry['tagline']); ?></h1>
<p class="av-hero__sub"><?php echo esc_html($industry['hero_lead']); ?></p>
<div class="av-hero__ctas">
<?php foreach ($industry['ctas'] as $cta) :
$variant = ($cta['variant'] === 'primary') ? 'av-btn--primary' : 'av-btn--secondary av-btn--on-dark';
?>
<a href="<?php echo esc_url($home_url . ltrim($cta['href'], '/')); ?>" class="av-btn <?php echo esc_attr($variant); ?> av-btn--lg">
<?php echo esc_html($cta['label']); ?>
</a>
<?php endforeach; ?>
</div>
</div>
</div>
</section>
<!-- SECTIONS -->
<?php
foreach ($industry['sections'] as $idx => $section) :
$is_dark = ($idx % 2 === 1);
$section_class = 'av-section' . ($is_dark ? ' av-section--dark' : '');
?>
<section class="<?php echo esc_attr($section_class); ?>" data-av-reveal>
<div class="av-container">
<div class="av-section__head">
<span class="av-eyebrow<?php echo $is_dark ? ' av-eyebrow--gold' : ''; ?>"><?php echo esc_html($section['eyebrow']); ?></span>
<h2 class="av-section__title"><?php echo esc_html($section['title']); ?></h2>
<?php if (! empty($section['lead'])) : ?>
<p class="av-section__lead"><?php echo esc_html($section['lead']); ?></p>
<?php endif; ?>
</div>
<?php if ($section['type'] === 'prose' && ! empty($section['body'])) : ?>
<p class="av-section__lead" style="max-width:72ch;font-size:var(--text-body-lg);">
<?php echo esc_html($section['body']); ?>
</p>
<?php elseif ($section['type'] === 'cards' && ! empty($section['items'])) : ?>
<div class="av-grid-3">
<?php foreach ($section['items'] as $card) : ?>
<article class="av-card<?php echo $is_dark ? ' is-on-dark' : ''; ?> av-card--feature">
<div class="av-card__body">
<h3 class="av-card__title" style="font-size:var(--text-h4);"><?php echo esc_html($card['title']); ?></h3>
<p class="av-card__excerpt"><?php echo esc_html($card['body']); ?></p>
</div>
</article>
<?php endforeach; ?>
</div>
<?php elseif ($section['type'] === 'list' && ! empty($section['items'])) : ?>
<ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:var(--space-3);max-width:72ch;font-size:var(--text-body-lg);line-height:var(--leading-body-lg);<?php echo $is_dark ? 'color:var(--color-text-on-dark-muted);' : 'color:var(--color-text-secondary);'; ?>">
<?php foreach ($section['items'] as $item) : ?>
<li style="padding-left:var(--space-6);position:relative;">
<span aria-hidden="true" style="position:absolute;left:0;top:0.6em;width:8px;height:8px;background:var(--color-brand-gold);border-radius:50%;"></span>
<?php echo esc_html($item); ?>
</li>
<?php endforeach; ?>
</ul>
<?php if (! empty($section['cta'])) : ?>
<p style="margin-top:var(--space-8);">
<a href="<?php echo esc_url($home_url . ltrim($section['cta']['href'], '/')); ?>" class="av-link<?php echo $is_dark ? ' av-link--on-dark' : ''; ?> av-link--with-arrow">
<?php echo esc_html($section['cta']['label']); ?>
</a>
</p>
<?php endif; ?>
<?php endif; ?>
</div>
</section>
<?php endforeach; ?>
<!-- CONVERSION BANNER (shared) -->
<section class="av-conversion-banner">
<div class="av-container">
<div class="av-conversion-banner__head">
<h2 class="av-conversion-banner__title">Train like the threat is now.</h2>
<p class="av-conversion-banner__lead">Three paths into the platform — pick the one that fits where you are.</p>
</div>
<div class="av-conversion-banner__grid">
<div class="av-conversion-banner__card">
<h3 class="av-conversion-banner__card-title">Request a Demo</h3>
<p class="av-conversion-banner__card-desc">A 30-minute tailored session with our team.</p>
<a href="<?php echo esc_url($home_url . 'request-demo/'); ?>" class="av-btn av-btn--primary av-btn--md">Schedule a demo</a>
</div>
<div class="av-conversion-banner__card">
<h3 class="av-conversion-banner__card-title">Request a T&E Kit</h3>
<p class="av-conversion-banner__card-desc">A PROSERVE case on loan at your facility for 5 to 10 days.</p>
<a href="<?php echo esc_url($home_url . 'request-te-kit/'); ?>" class="av-btn av-btn--on-dark av-btn--secondary av-btn--md">Request the kit</a>
</div>
<div class="av-conversion-banner__card">
<h3 class="av-conversion-banner__card-title">Request a Quote</h3>
<p class="av-conversion-banner__card-desc">A detailed quote and deployment plan within 5 working days.</p>
<a href="<?php echo esc_url($home_url . 'request-quote/'); ?>" class="av-btn av-btn--on-dark av-btn--secondary av-btn--md">Get a quote</a>
</div>
</div>
</div>
</section>

View File

@@ -1,173 +0,0 @@
<?php
/**
* Asterion Bricks — Solution detail partial
*
* Renders one PROSERVE tier (MY / FLEX / ACADEMY / +) from data passed via
* `set_query_var('av_solution', $solution_data)` before include.
*
* Section types supported : prose | cards | list | cta
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
$solution = get_query_var('av_solution');
if (! is_array($solution)) {
return;
}
$home_url = home_url('/');
?>
<!-- ============================================================== -->
<!-- HERO -->
<!-- ============================================================== -->
<section class="av-hero" aria-labelledby="solution-headline">
<div class="av-hero__media" aria-hidden="true">
<div style="width:100%;height:100%;background:
radial-gradient(ellipse at 70% 30%, rgba(201,164,90,0.15), transparent 60%),
linear-gradient(135deg, #081427 0%, #0B1F3A 50%, #13294B 100%);"></div>
</div>
<div class="av-container">
<div class="av-hero__inner">
<p class="av-hero__eyebrow"><?php echo esc_html($solution['eyebrow']); ?></p>
<h1 id="solution-headline" class="av-hero__headline" style="font-size:clamp(2.25rem,5vw,var(--text-display-lg));line-height:var(--leading-display-lg);">
<?php echo esc_html($solution['title']); ?>&trade; — <?php echo esc_html($solution['tagline']); ?>
</h1>
<p class="av-hero__sub"><?php echo esc_html($solution['hero_lead']); ?></p>
<div class="av-hero__ctas">
<?php foreach ($solution['ctas'] as $i => $cta) :
$on_dark = ($cta['variant'] === 'secondary') ? ' av-btn--on-dark' : '';
$variant = ($cta['variant'] === 'primary') ? 'av-btn--primary' : 'av-btn--secondary';
?>
<a href="<?php echo esc_url($home_url . ltrim($cta['href'], '/')); ?>" class="av-btn <?php echo esc_attr($variant); ?><?php echo esc_attr($on_dark); ?> av-btn--lg">
<?php echo esc_html($cta['label']); ?>
</a>
<?php endforeach; ?>
</div>
</div>
</div>
</section>
<!-- ============================================================== -->
<!-- SECTIONS -->
<!-- ============================================================== -->
<?php
foreach ($solution['sections'] as $idx => $section) :
$is_dark = ($idx % 2 === 1);
$section_class = 'av-section' . ($is_dark ? ' av-section--dark' : '');
?>
<section class="<?php echo esc_attr($section_class); ?>" data-av-reveal>
<div class="av-container">
<div class="av-section__head">
<span class="av-eyebrow<?php echo $is_dark ? ' av-eyebrow--gold' : ''; ?>"><?php echo esc_html($section['eyebrow']); ?></span>
<h2 class="av-section__title"><?php echo esc_html($section['title']); ?></h2>
<?php if (! empty($section['lead'])) : ?>
<p class="av-section__lead"><?php echo esc_html($section['lead']); ?></p>
<?php endif; ?>
</div>
<?php if ($section['type'] === 'prose' && ! empty($section['body'])) : ?>
<p class="av-section__lead" style="max-width:72ch;font-size:var(--text-body-lg);">
<?php echo esc_html($section['body']); ?>
</p>
<?php elseif ($section['type'] === 'cards' && ! empty($section['items'])) : ?>
<div class="av-grid-3">
<?php foreach ($section['items'] as $card) : ?>
<article class="av-card<?php echo $is_dark ? ' is-on-dark' : ''; ?> av-card--feature">
<div class="av-card__body">
<h3 class="av-card__title" style="font-size:var(--text-h4);"><?php echo esc_html($card['title']); ?></h3>
<p class="av-card__excerpt"><?php echo esc_html($card['body']); ?></p>
</div>
</article>
<?php endforeach; ?>
</div>
<?php elseif ($section['type'] === 'list' && ! empty($section['items'])) : ?>
<ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:var(--space-3);max-width:72ch;font-size:var(--text-body-lg);line-height:var(--leading-body-lg);<?php echo $is_dark ? 'color:var(--color-text-on-dark-muted);' : 'color:var(--color-text-secondary);'; ?>">
<?php foreach ($section['items'] as $item) : ?>
<li style="padding-left:var(--space-6);position:relative;">
<span aria-hidden="true" style="position:absolute;left:0;top:0.6em;width:8px;height:8px;background:var(--color-brand-gold);border-radius:50%;"></span>
<?php echo esc_html($item); ?>
</li>
<?php endforeach; ?>
</ul>
<?php if (! empty($section['cta'])) : ?>
<p style="margin-top:var(--space-8);">
<a href="<?php echo esc_url($home_url . ltrim($section['cta']['href'], '/')); ?>" class="av-link<?php echo $is_dark ? ' av-link--on-dark' : ''; ?> av-link--with-arrow">
<?php echo esc_html($section['cta']['label']); ?>
</a>
</p>
<?php endif; ?>
<?php elseif ($section['type'] === 'cta' && ! empty($section['body'])) : ?>
<p class="av-section__lead" style="max-width:72ch;margin-bottom:var(--space-6);"><?php echo esc_html($section['body']); ?></p>
<?php if (! empty($section['ctas'])) : ?>
<div style="display:flex;flex-wrap:wrap;gap:var(--space-4);">
<?php foreach ($section['ctas'] as $cta) :
$on_dark = $is_dark ? ' av-btn--on-dark' : '';
$variant = ($cta['variant'] === 'primary') ? 'av-btn--primary' : 'av-btn--secondary';
?>
<a href="<?php echo esc_url($home_url . ltrim($cta['href'], '/')); ?>" class="av-btn <?php echo esc_attr($variant); ?><?php echo esc_attr($on_dark); ?> av-btn--lg">
<?php echo esc_html($cta['label']); ?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</section>
<?php endforeach; ?>
<!-- ============================================================== -->
<!-- UPSELL (when to step up to the next tier) -->
<!-- ============================================================== -->
<?php if (! empty($solution['upsell'])) : ?>
<section class="av-section" style="background-color:var(--color-bg-subtle);" data-av-reveal>
<div class="av-container">
<div class="av-section__head">
<span class="av-eyebrow"><?php esc_html_e('Comparison and upgrade', 'asterion-bricks'); ?></span>
<h2 class="av-section__title"><?php echo esc_html($solution['upsell']['title']); ?></h2>
<p class="av-section__lead"><?php echo esc_html($solution['upsell']['body']); ?></p>
</div>
<div style="display:flex;flex-wrap:wrap;gap:var(--space-4);">
<?php foreach ($solution['upsell']['links'] as $link) : ?>
<a href="<?php echo esc_url($home_url . ltrim($link['href'], '/')); ?>" class="av-btn av-btn--secondary av-btn--md">
<?php echo esc_html($link['label']); ?>
</a>
<?php endforeach; ?>
</div>
</div>
</section>
<?php endif; ?>
<!-- ============================================================== -->
<!-- CONVERSION BANNER (shared) -->
<!-- ============================================================== -->
<section class="av-conversion-banner">
<div class="av-container">
<div class="av-conversion-banner__head">
<h2 class="av-conversion-banner__title"><?php esc_html_e('Ready to get hands-on?', 'asterion-bricks'); ?></h2>
<p class="av-conversion-banner__lead"><?php esc_html_e('Three paths into the platform — pick the one that fits where you are.', 'asterion-bricks'); ?></p>
</div>
<div class="av-conversion-banner__grid">
<div class="av-conversion-banner__card">
<h3 class="av-conversion-banner__card-title"><?php esc_html_e('Request a Demo', 'asterion-bricks'); ?></h3>
<p class="av-conversion-banner__card-desc"><?php esc_html_e('A 30-minute tailored session with our team. Walk through the configuration that matches your needs.', 'asterion-bricks'); ?></p>
<a href="<?php echo esc_url($home_url . 'request-demo/'); ?>" class="av-btn av-btn--primary av-btn--md"><?php esc_html_e('Schedule a demo', 'asterion-bricks'); ?></a>
</div>
<div class="av-conversion-banner__card">
<h3 class="av-conversion-banner__card-title"><?php esc_html_e('Request a T&E Kit', 'asterion-bricks'); ?></h3>
<p class="av-conversion-banner__card-desc"><?php esc_html_e('A PROSERVE case on loan at your facility for 5 to 10 days, with on-site support from our team.', 'asterion-bricks'); ?></p>
<a href="<?php echo esc_url($home_url . 'request-te-kit/'); ?>" class="av-btn av-btn--on-dark av-btn--secondary av-btn--md"><?php esc_html_e('Request the kit', 'asterion-bricks'); ?></a>
</div>
<div class="av-conversion-banner__card">
<h3 class="av-conversion-banner__card-title"><?php esc_html_e('Request a Quote', 'asterion-bricks'); ?></h3>
<p class="av-conversion-banner__card-desc"><?php esc_html_e('A detailed quote and deployment plan within 5 working days.', 'asterion-bricks'); ?></p>
<a href="<?php echo esc_url($home_url . 'request-quote/'); ?>" class="av-btn av-btn--on-dark av-btn--secondary av-btn--md"><?php esc_html_e('Get a quote', 'asterion-bricks'); ?></a>
</div>
</div>
</div>
</section>

View File

@@ -1,27 +0,0 @@
<?php
/**
* Template Name: Conversion Form
*
* Custom page template for /request-demo/, /request-te-kit/, /request-quote/, /contact/.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
$slug = get_post_field('post_name', get_queried_object_id());
$data = include av_data_file('forms-data');
if (! isset($data[$slug])) {
get_header();
echo '<section class="av-section"><div class="av-container">';
echo '<h1>' . esc_html(get_the_title()) . '</h1>';
echo '<p>' . esc_html__('Form data not found for slug:', 'asterion-bricks') . ' ' . esc_html($slug) . '</p>';
echo '</div></section>';
get_footer();
return;
}
get_header();
set_query_var('av_form', $data[$slug]);
get_template_part('template-parts/conversion-form');
get_footer();

View File

@@ -1,28 +0,0 @@
<?php
/**
* Template Name: Editorial Detail
*
* Custom page template for editorial pages (Why Asterion, About, Customers
* hub, Trust, Partners). Reuses the industry-detail partial.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
$slug = get_post_field('post_name', get_queried_object_id());
$data = include av_data_file('editorial-data');
if (! isset($data[$slug])) {
get_header();
echo '<section class="av-section"><div class="av-container">';
echo '<h1>' . esc_html(get_the_title()) . '</h1>';
echo '<p>' . esc_html__('Editorial data not found for slug:', 'asterion-bricks') . ' ' . esc_html($slug) . '</p>';
echo '</div></section>';
get_footer();
return;
}
get_header();
set_query_var('av_industry', $data[$slug]);
get_template_part('template-parts/industry-detail');
get_footer();

View File

@@ -1,83 +0,0 @@
<?php
/**
* Template Name: Industries Overview
*
* Hub page /industries/ — lists the 4 industries as cards.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
get_header();
$home_url = home_url('/');
$industries = [
[
'eyebrow' => 'National & Municipal',
'title' => 'Police',
'excerpt' => 'Vehicle stops, identity checks, urban CQB, de-escalation, riot containment. Built for the realities of contemporary policing.',
'href' => '/industries/police/',
'gradient' => 'linear-gradient(135deg,#1F3252,#0B1F3A)',
],
[
'eyebrow' => 'Tactical units',
'title' => 'Special Forces',
'excerpt' => 'Hostage rescue, dynamic entries, hostile crowd extraction, sniper-spotter coordination. Built for the units where every error has a name.',
'href' => '/industries/special-forces/',
'gradient' => 'linear-gradient(135deg,#1F3252,#0B1F3A)',
],
[
'eyebrow' => 'Armed forces',
'title' => 'Military',
'excerpt' => 'MOUT, convoy escort, FOB defense, IED awareness, joint operation drills. Built for armed forces that deploy fast and adapt faster.',
'href' => '/industries/military/',
'gradient' => 'linear-gradient(135deg,#1F3252,#0B1F3A)',
],
[
'eyebrow' => 'Emergency services',
'title' => 'Firefighters',
'excerpt' => "Structure fires, hazmat, multi-casualty triage, confined-space rescue, industrial accidents. Built for first responders who can't rehearse the worst day at full scale.",
'href' => '/industries/firefighters/',
'gradient' => 'linear-gradient(135deg,#1F3252,#0B1F3A)',
],
];
?>
<section class="av-hero" aria-labelledby="industries-headline">
<div class="av-hero__media" aria-hidden="true">
<div style="width:100%;height:100%;background:
radial-gradient(ellipse at 50% 40%, rgba(11,31,58,0.4), transparent 65%),
linear-gradient(135deg, #081427 0%, #0B1F3A 50%, #1F3252 100%);"></div>
</div>
<div class="av-container">
<div class="av-hero__inner">
<p class="av-hero__eyebrow"><?php esc_html_e('Industries', 'asterion-bricks'); ?></p>
<h1 id="industries-headline" class="av-hero__headline">Built with operators, for operators.</h1>
<p class="av-hero__sub">
PROSERVE&trade; is not a generic VR platform repackaged for the public sector. Every scenario, every weapon profile, every instructor metric was specified by serving and former operators in close partnership with our engineering team.
</p>
</div>
</div>
</section>
<section class="av-section" data-av-reveal>
<div class="av-container">
<div class="av-grid-4" style="grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));">
<?php foreach ($industries as $i) : ?>
<article class="av-card">
<a href="<?php echo esc_url($home_url . ltrim($i['href'], '/')); ?>" class="av-card__media" aria-hidden="true" tabindex="-1" style="background:<?php echo esc_attr($i['gradient']); ?>;display:block;"></a>
<div class="av-card__body">
<span class="av-card__eyebrow"><?php echo esc_html($i['eyebrow']); ?></span>
<h2 class="av-card__title"><a href="<?php echo esc_url($home_url . ltrim($i['href'], '/')); ?>" style="color:inherit;text-decoration:none;"><?php echo esc_html($i['title']); ?></a></h2>
<p class="av-card__excerpt"><?php echo esc_html($i['excerpt']); ?></p>
<div class="av-card__cta">
<a href="<?php echo esc_url($home_url . ltrim($i['href'], '/')); ?>" class="av-link av-link--with-arrow"><?php echo esc_html($i['title']); ?> training</a>
</div>
</div>
</article>
<?php endforeach; ?>
</div>
</div>
</section>
<?php get_footer(); ?>

View File

@@ -1,31 +0,0 @@
<?php
/**
* Template Name: Industry Detail
*
* Custom page template for /industries/police/, /industries/special-forces/,
* /industries/military/, /industries/firefighters/.
*
* Looks up the page slug in inc/industries-data.php and hands the matched
* tier off to template-parts/industry-detail.php.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
$slug = get_post_field('post_name', get_queried_object_id());
$data = include av_data_file('industries-data');
if (! isset($data[$slug])) {
get_header();
echo '<section class="av-section"><div class="av-container">';
echo '<h1>' . esc_html(get_the_title()) . '</h1>';
echo '<p>' . esc_html__('Industry data not found for slug:', 'asterion-bricks') . ' ' . esc_html($slug) . '</p>';
echo '</div></section>';
get_footer();
return;
}
get_header();
set_query_var('av_industry', $data[$slug]);
get_template_part('template-parts/industry-detail');
get_footer();

View File

@@ -1,87 +0,0 @@
<?php
/**
* Template Name: Insights Hub
*
* /insights/ — blog hub. Lists native `post` items as blog cards.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
get_header();
$home_url = home_url('/');
?>
<section class="av-hero" aria-labelledby="insights-headline">
<div class="av-hero__media" aria-hidden="true">
<div style="width:100%;height:100%;background:
radial-gradient(ellipse at 60% 40%, rgba(201,164,90,0.12), transparent 60%),
linear-gradient(135deg, #081427 0%, #0B1F3A 70%, #13294B 100%);"></div>
</div>
<div class="av-container">
<div class="av-hero__inner">
<p class="av-hero__eyebrow"><?php esc_html_e('Insights', 'asterion-bricks'); ?></p>
<h1 id="insights-headline" class="av-hero__headline">Notes from the front of immersive training.</h1>
<p class="av-hero__sub">
Doctrine. Training science. Instructor stories. AAR data. Comparisons of VR vs live training. Found something useful? It probably came from a customer conversation. We publish what we learn.
</p>
</div>
</div>
</section>
<section class="av-section" data-av-reveal>
<div class="av-container">
<?php
// Categories filter chips (placeholders — wired to native `category` taxonomy if used).
$categories = ['Doctrine', 'Technology', 'Case Studies', 'Industry News', "Founder's Notes"];
?>
<div style="display:flex;flex-wrap:wrap;gap:var(--space-3);margin-bottom:var(--space-10);">
<?php foreach ($categories as $cat) : ?>
<span class="av-btn av-btn--ghost av-btn--sm" style="border:1px solid var(--color-border-default);"><?php echo esc_html($cat); ?></span>
<?php endforeach; ?>
</div>
<?php $posts = get_posts(['numberposts' => 12, 'post_status' => 'publish']); ?>
<?php if (! empty($posts)) : ?>
<div class="av-grid-3">
<?php foreach ($posts as $post) : setup_postdata($post); ?>
<article class="av-card av-card--blog">
<a href="<?php the_permalink(); ?>" class="av-card__media" aria-hidden="true" tabindex="-1" style="background:linear-gradient(135deg,#CCD3DC,#E5E9EE);"></a>
<div class="av-card__body">
<span class="av-card__eyebrow"><?php echo esc_html(get_the_date('M j, Y')); ?></span>
<h2 class="av-card__title"><a href="<?php the_permalink(); ?>" style="color:inherit;text-decoration:none;"><?php the_title(); ?></a></h2>
<p class="av-card__excerpt"><?php echo esc_html(wp_trim_words(get_the_excerpt(), 22)); ?></p>
<div class="av-card__cta"><a href="<?php the_permalink(); ?>" class="av-link av-link--with-arrow"><?php esc_html_e('Read more', 'asterion-bricks'); ?></a></div>
</div>
</article>
<?php endforeach; wp_reset_postdata(); ?>
</div>
<?php else : ?>
<p style="color:var(--color-text-muted);max-width:64ch;">
<?php esc_html_e('Editorial blog launches with the site go-live. The 12-month content calendar is in the Strategy & Content brief, section B.4. New articles will appear here as soon as they ship.', 'asterion-bricks'); ?>
</p>
<?php endif; ?>
</div>
</section>
<section class="av-section av-section--dark" data-av-reveal>
<div class="av-container">
<div style="display:grid;grid-template-columns:1fr;gap:var(--space-8);align-items:center;" class="av-newsletter-row">
<div>
<span class="av-eyebrow av-eyebrow--gold"><?php esc_html_e('Newsletter', 'asterion-bricks'); ?></span>
<h2 style="font-family:var(--font-display);font-size:var(--text-h1);line-height:var(--leading-h1);color:var(--color-text-on-dark);margin:var(--space-3) 0;">Subscribe — one article per month.</h2>
<p style="color:var(--color-text-on-dark-muted);max-width:60ch;">
Insights from the front of immersive training. One email per month. No spam. Unsubscribe in one click.
</p>
</div>
<form method="post" action="#" style="display:flex;gap:var(--space-3);flex-wrap:wrap;align-items:flex-start;" aria-label="<?php esc_attr_e('Newsletter signup', 'asterion-bricks'); ?>">
<label for="av-insights-newsletter" class="av-sr-only"><?php esc_html_e('Your email', 'asterion-bricks'); ?></label>
<input type="email" id="av-insights-newsletter" name="email" class="av-input" placeholder="<?php esc_attr_e('your@email.com', 'asterion-bricks'); ?>" required style="flex:1;min-width:200px;">
<button type="submit" class="av-btn av-btn--primary av-btn--md"><?php esc_html_e('Subscribe', 'asterion-bricks'); ?></button>
</form>
</div>
<style>@media (min-width: 768px) { .av-newsletter-row { grid-template-columns: 2fr 1.5fr !important; } }</style>
</div>
</section>
<?php get_footer(); ?>

View File

@@ -1,92 +0,0 @@
<?php
/**
* Template Name: Legal
*
* /legal/privacy/, /legal/terms/, /legal/cookies/, /legal/notice/.
* Renders the WP page content (filled in by counsel) inside the standard
* narrow container with a clean editorial hero.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
get_header();
?>
<section class="av-section">
<div class="av-container av-container--narrow">
<header class="av-section__head">
<span class="av-eyebrow"><?php esc_html_e('Legal', 'asterion-bricks'); ?></span>
<h1 class="av-section__title"><?php the_title(); ?></h1>
<p class="av-section__lead">
<?php esc_html_e('Last updated:', 'asterion-bricks'); ?>
<?php echo esc_html(get_the_modified_date()); ?>
</p>
</header>
<div class="av-prose" style="font-size:var(--text-body-md);line-height:var(--leading-body-md);color:var(--color-text-secondary);">
<?php
if (have_posts()) :
while (have_posts()) : the_post();
the_content();
endwhile;
endif;
// Default skeleton when the page content is empty — populated by counsel later.
$content = get_post_field('post_content', get_queried_object_id());
if (trim($content) === '') :
?>
<p style="padding:var(--space-6);background:var(--color-bg-subtle);border-left:3px solid var(--color-warning);border-radius:var(--radius-sm);">
<strong><?php esc_html_e('Placeholder.', 'asterion-bricks'); ?></strong>
<?php esc_html_e('This page is to be drafted with legal counsel. The structure below outlines the sections expected.', 'asterion-bricks'); ?>
</p>
<?php
// Slug-driven placeholder sections.
$slug = get_post_field('post_name', get_queried_object_id());
$skeletons = [
'privacy' => [
'Data controller (Asterion VR), contact, DPO if relevant',
'Data collected (form submissions, cookie identifiers, IP, etc.)',
'Purpose of processing (sales follow-up, newsletter, analytics)',
'Legal basis (consent, contract, legitimate interest)',
'Retention periods',
'Data sharing (subcontractors, hosts)',
'Data subject rights (access, rectification, deletion, portability, objection)',
'Complaint procedure (CNIL link)',
],
'terms' => [
'Site usage conditions',
'Intellectual property',
'Liability limitations',
'Applicable law and jurisdiction',
],
'cookies' => [
'List of cookies (functional, analytics, marketing)',
'Cookie purpose, retention, third parties',
'Consent management procedure',
],
'notice' => [
'Asterion VR — RCS 821 520 970 Rennes — NAF 6201Z — SIRET 821 520 970 00012',
'Adresse, téléphone, e-mail, directeur de la publication',
'Hébergeur',
],
];
if (isset($skeletons[$slug])) :
?>
<h2 style="font-family:var(--font-display);font-size:var(--text-h3);margin-top:var(--space-10);margin-bottom:var(--space-4);"><?php esc_html_e('Sections to include', 'asterion-bricks'); ?></h2>
<ul style="display:flex;flex-direction:column;gap:var(--space-2);padding-left:var(--space-6);">
<?php foreach ($skeletons[$slug] as $line) : ?>
<li style="list-style:disc;"><?php echo esc_html($line); ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
</section>
<?php get_footer(); ?>

View File

@@ -1,100 +0,0 @@
<?php
/**
* Template Name: Resources
*
* /resources/ — datasheets, whitepapers, videos, brochures.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
get_header();
$home_url = home_url('/');
$groups = [
[
'eyebrow' => 'Datasheets',
'title' => 'Technical specifications.',
'items' => [
['title' => 'PROSERVE — full datasheet', 'note' => 'Hardware specifications, software capabilities, scenarios. Gated, light form.'],
['title' => 'MY PROSERVE — quick datasheet', 'note' => '4-page summary.'],
['title' => 'PROSERVE FLEX — quick datasheet', 'note' => '4-page summary.'],
['title' => 'PROSERVE ACADEMY — quick datasheet', 'note' => '4-page summary.'],
['title' => 'PROSERVE+ — customization brochure', 'note' => 'Hardware add-ons, custom scenarios, process.'],
],
],
[
'eyebrow' => 'Whitepapers',
'title' => 'Long-form analyses.',
'items' => [
['title' => 'VR vs Live Fire: the 2026 cost-benefit comparison', 'note' => 'Quantified comparison across cost, fidelity, scalability, environmental.'],
['title' => "The Instructor's Guide to VR Training Programs", 'note' => "Methodology, drill design, AAR debriefing, assessment frameworks."],
['title' => 'Use of Force Training: how immersive simulation reduces tactical errors', 'note' => 'Reviews academic literature and Asterion deployment data.'],
['title' => 'Sovereignty in security simulation: why your training data must never leave your network', 'note' => 'For procurement and IT teams.'],
],
],
[
'eyebrow' => 'Videos',
'title' => 'Show, not tell.',
'items' => [
['title' => 'PROSERVE — 90-second product film', 'note' => 'Cinematic overview.'],
['title' => 'Instructor Cockpit walkthrough (3 minutes)', 'note' => 'Inside the instructor PC.'],
['title' => 'AAR replay — annotated example (4 minutes)', 'note' => 'A drill replayed with overlays.'],
['title' => 'MY PROSERVE — unboxing & first deployment (6 min)', 'note' => 'From mini-case to first scenario in real time.'],
['title' => 'Customer story — [name redacted unit] (8 min)', 'note' => 'Operational deployment, instructor interview, AAR excerpts.'],
],
],
[
'eyebrow' => 'Brochures',
'title' => 'For procurement and end-user briefs.',
'items' => [
['title' => 'PROSERVE general brochure', 'note' => '8 pages, EN/FR.'],
['title' => 'Industry-specific brochures', 'note' => 'Police, Special Forces, Military, Firefighters — one PDF each.'],
],
],
];
?>
<section class="av-hero" aria-labelledby="resources-headline">
<div class="av-hero__media" aria-hidden="true">
<div style="width:100%;height:100%;background:linear-gradient(135deg, #081427 0%, #0B1F3A 50%, #13294B 100%);"></div>
</div>
<div class="av-container">
<div class="av-hero__inner">
<p class="av-hero__eyebrow"><?php esc_html_e('Resources', 'asterion-bricks'); ?></p>
<h1 id="resources-headline" class="av-hero__headline">Everything to evaluate, justify, and procure.</h1>
<p class="av-hero__sub">
Whether you are building an internal pitch, drafting a procurement specification, or briefing your team — these resources are designed to help.
</p>
</div>
</div>
</section>
<?php foreach ($groups as $idx => $group) :
$is_dark = ($idx % 2 === 1);
$section_class = 'av-section' . ($is_dark ? ' av-section--dark' : '');
?>
<section class="<?php echo esc_attr($section_class); ?>" data-av-reveal>
<div class="av-container">
<div class="av-section__head">
<span class="av-eyebrow<?php echo $is_dark ? ' av-eyebrow--gold' : ''; ?>"><?php echo esc_html($group['eyebrow']); ?></span>
<h2 class="av-section__title"><?php echo esc_html($group['title']); ?></h2>
</div>
<div class="av-grid-3">
<?php foreach ($group['items'] as $item) : ?>
<article class="av-card<?php echo $is_dark ? ' is-on-dark' : ''; ?>">
<div class="av-card__body">
<h3 class="av-card__title" style="font-size:var(--text-h4);"><?php echo esc_html($item['title']); ?></h3>
<p class="av-card__excerpt"><?php echo esc_html($item['note']); ?></p>
<div class="av-card__cta">
<a href="<?php echo esc_url($home_url . 'contact/'); ?>" class="av-link<?php echo $is_dark ? ' av-link--on-dark' : ''; ?> av-link--with-arrow"><?php esc_html_e('Request access', 'asterion-bricks'); ?></a>
</div>
</div>
</article>
<?php endforeach; ?>
</div>
</div>
</section>
<?php endforeach; ?>
<?php get_footer(); ?>

View File

@@ -1,33 +0,0 @@
<?php
/**
* Template Name: Solution Detail
*
* Custom page template assigned to /solutions/my-proserve/, /solutions/proserve-flex/,
* /solutions/proserve-academy/ and /solutions/customization/.
*
* Looks up the page slug in inc/solutions-data.php, hands the matched array off
* to template-parts/solution-detail.php, and renders the result wrapped in the
* standard header + footer.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
$slug = get_post_field('post_name', get_queried_object_id());
$data = include av_data_file('solutions-data');
if (! isset($data[$slug])) {
// Fallback : fall back to the basic page rendering if the slug isn't mapped.
get_header();
echo '<section class="av-section"><div class="av-container">';
echo '<h1>' . esc_html(get_the_title()) . '</h1>';
echo '<p>' . esc_html__('Solution data not found for slug:', 'asterion-bricks') . ' ' . esc_html($slug) . '</p>';
echo '</div></section>';
get_footer();
return;
}
get_header();
set_query_var('av_solution', $data[$slug]);
get_template_part('template-parts/solution-detail');
get_footer();

View File

@@ -1,120 +0,0 @@
<?php
/**
* Template Name: Solutions Overview
*
* Custom page template assigned to /solutions/. Renders the 4-tier comparison
* and PROSERVE+ teaser per PDF Strategie & Contenu D.2.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
get_header();
$home_url = home_url('/');
?>
<!-- HERO -->
<section class="av-hero" aria-labelledby="solutions-headline">
<div class="av-hero__media" aria-hidden="true">
<div style="width:100%;height:100%;background:
radial-gradient(ellipse at 30% 30%, rgba(201,164,90,0.15), transparent 60%),
linear-gradient(135deg, #081427 0%, #0B1F3A 50%, #13294B 100%);"></div>
</div>
<div class="av-container">
<div class="av-hero__inner">
<p class="av-hero__eyebrow"><?php esc_html_e('Solutions', 'asterion-bricks'); ?></p>
<h1 id="solutions-headline" class="av-hero__headline" style="font-size:clamp(2.25rem,5vw,var(--text-display-lg));line-height:var(--leading-display-lg);">
One platform. Three formats. Zero rip-and-replace.
</h1>
<p class="av-hero__sub">
Whether you train one officer or sixteen, on a single mission or a full curriculum, PROSERVE&trade; delivers the same software, the same scenarios, the same instructor tools — at the right scale for your operation. Start solo. Add operators. Scale to a full academy. Your investment never resets.
</p>
<div class="av-hero__ctas">
<a href="<?php echo esc_url($home_url . 'request-demo/'); ?>" class="av-btn av-btn--primary av-btn--lg"><?php esc_html_e('Request a Demo', 'asterion-bricks'); ?></a>
</div>
</div>
</div>
</section>
<!-- COMPARISON TABLE -->
<section class="av-section" data-av-reveal>
<div class="av-container">
<div class="av-section__head">
<span class="av-eyebrow"><?php esc_html_e('Compare', 'asterion-bricks'); ?></span>
<h2 class="av-section__title">Compare the three solutions.</h2>
<p class="av-section__lead">
Same software. Same scenarios. Same instructor tools. Different format, different scale.
</p>
</div>
<div style="overflow-x:auto;border:1px solid var(--color-border-subtle);border-radius:var(--radius-md);">
<table style="width:100%;border-collapse:collapse;font-size:var(--text-body-md);min-width:720px;">
<thead>
<tr style="background:var(--color-brand-navy);color:var(--color-text-on-dark);">
<th scope="col" style="padding:var(--space-4);text-align:left;font-weight:var(--font-weight-semibold);"></th>
<th scope="col" style="padding:var(--space-4);text-align:left;font-weight:var(--font-weight-semibold);">MY PROSERVE</th>
<th scope="col" style="padding:var(--space-4);text-align:left;font-weight:var(--font-weight-semibold);">PROSERVE FLEX</th>
<th scope="col" style="padding:var(--space-4);text-align:left;font-weight:var(--font-weight-semibold);">PROSERVE ACADEMY</th>
</tr>
</thead>
<tbody>
<?php
$rows = [
['Trainees', '1 (solo)', '15 (team)', '116 (unit)'],
['Format', 'Mini-case', 'One case per operator + instructor PC', 'Dedicated room + rack + instructor station'],
['Floor space', '3 m² minimum', '9 m² to 100 m²', 'Up to 900 m²'],
['Setup time', '< 5 min', '< 5 min (solo) / 20 min (team)', 'Pre-installed (rapid recalibration)'],
['Multi-user cooperative', 'No', 'Yes (up to 5)', 'Yes (up to 16)'],
['Mobility', 'Ultra-portable', 'Highly portable', 'Fixed'],
['Best for', 'Individual continuous training (police municipale, on-call officers)', 'Tactical teams, mobile units, special operations', 'Academies, central training centres, large units'],
['Indicative price', 'From €9,000 / unit', 'On request', 'On request'],
];
foreach ($rows as $i => $row) :
$bg = ($i % 2 === 0) ? 'transparent' : 'var(--color-bg-subtle)';
?>
<tr style="background:<?php echo esc_attr($bg); ?>;">
<th scope="row" style="padding:var(--space-4);text-align:left;font-weight:var(--font-weight-semibold);color:var(--color-text-primary);border-bottom:1px solid var(--color-border-subtle);">
<?php echo esc_html($row[0]); ?>
</th>
<?php for ($c = 1; $c <= 3; $c++) : ?>
<td style="padding:var(--space-4);color:var(--color-text-secondary);border-bottom:1px solid var(--color-border-subtle);">
<?php echo esc_html($row[$c]); ?>
</td>
<?php endfor; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div style="display:flex;flex-wrap:wrap;gap:var(--space-4);margin-top:var(--space-8);">
<a href="<?php echo esc_url($home_url . 'solutions/my-proserve/'); ?>" class="av-btn av-btn--secondary av-btn--md">MY PROSERVE</a>
<a href="<?php echo esc_url($home_url . 'solutions/proserve-flex/'); ?>" class="av-btn av-btn--secondary av-btn--md">PROSERVE FLEX</a>
<a href="<?php echo esc_url($home_url . 'solutions/proserve-academy/'); ?>" class="av-btn av-btn--secondary av-btn--md">PROSERVE ACADEMY</a>
</div>
</div>
</section>
<!-- PROSERVE+ TEASER -->
<section class="av-section av-section--dark" data-av-reveal>
<div class="av-container">
<div style="display:grid;grid-template-columns:1fr;gap:var(--space-10);align-items:center;" class="av-plus-row">
<div style="aspect-ratio:4/3;background:linear-gradient(135deg,#13294B,#081427);border-radius:var(--radius-md);"></div>
<div style="display:flex;flex-direction:column;gap:var(--space-4);">
<span class="av-eyebrow av-eyebrow--gold"><?php esc_html_e('PROSERVE+', 'asterion-bricks'); ?></span>
<h2 class="av-section__title">Need something the catalog doesn&rsquo;t list?</h2>
<p class="av-section__lead">
PROSERVE+&trade; is the customization track. Wrist trackers, haptic vests, custom weapon replicas, bespoke environments, scenarios in your unit&rsquo;s exact uniforms and doctrine. Designed and produced in our French studio.
</p>
<div>
<a href="<?php echo esc_url($home_url . 'solutions/customization/'); ?>" class="av-btn av-btn--primary av-btn--md">
<?php esc_html_e('Explore PROSERVE+ customization', 'asterion-bricks'); ?>
</a>
</div>
</div>
</div>
<style>@media (min-width: 1024px) { .av-plus-row { grid-template-columns: 1fr 1.2fr !important; } }</style>
</div>
</section>
<?php get_footer(); ?>

View File

@@ -1,29 +0,0 @@
<?php
/**
* Template Name: Technology Detail
*
* Custom page template for the 6 /technology/* sub-pages.
* Same data schema as industries — reuses template-parts/industry-detail.php.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
$slug = get_post_field('post_name', get_queried_object_id());
$data = include av_data_file('technology-data');
if (! isset($data[$slug])) {
get_header();
echo '<section class="av-section"><div class="av-container">';
echo '<h1>' . esc_html(get_the_title()) . '</h1>';
echo '<p>' . esc_html__('Technology data not found for slug:', 'asterion-bricks') . ' ' . esc_html($slug) . '</p>';
echo '</div></section>';
get_footer();
return;
}
get_header();
// industry-detail partial expects the data under `av_industry`.
set_query_var('av_industry', $data[$slug]);
get_template_part('template-parts/industry-detail');
get_footer();

View File

@@ -1,101 +0,0 @@
<?php
/**
* Template Name: Technology Overview
*
* Hub /technology/ — six pillars per PDF Strategie & Contenu D.11.
*
* @package AsterionBricks
*/
defined('ABSPATH') || exit;
get_header();
$home_url = home_url('/');
$pillars = [
[
'eyebrow' => '01',
'title' => 'VR Hardware',
'body' => 'Fully wireless 5K headset. Wi-Fi 6E streaming from a dedicated rendering laptop. 90 Hz refresh, 120° field of view, sub-millimeter inside-out tracking. Designed for daily operational use.',
'cta' => 'Inside the hardware',
'href' => '/technology/vr-hardware/',
],
[
'eyebrow' => '02',
'title' => 'Software & AI',
'body' => 'Powered by Unreal Engine 5. Autonomous AI agents with vision, hearing, and personality. Real-time ballistic simulation. Locally hosted, no cloud dependency.',
'cta' => 'Inside the software',
'href' => '/technology/software-ai/',
],
[
'eyebrow' => '03',
'title' => 'Weapons & Tracking',
'body' => 'Universal Weapon Digitizer (UWeD) attaches to gas-blowback replicas, CO2 replicas, or real service weapons firing blanks. Recoil-induced tracking loss is corrected by an exclusive IMU compensation system.',
'cta' => 'Inside weapons & tracking',
'href' => '/technology/weapons-tracking/',
],
[
'eyebrow' => '04',
'title' => 'Scenarios Library',
'body' => 'Eleven environments. More than fifty validated scenarios. Updates four times per year. From fire range and IPSC to multi-room CQB, supermarket, residential apartment, and industrial sites.',
'cta' => 'Browse the scenario library',
'href' => '/technology/scenarios/',
],
[
'eyebrow' => '05',
'title' => 'Instructor Cockpit',
'body' => "Real-time supervision from any angle. Walk through walls. Switch into any trainee's view. Toggle shot trace, field of view, threat highlighting. Manipulate the scenario in real time.",
'cta' => 'Inside the instructor cockpit',
'href' => '/technology/instructor-cockpit/',
],
[
'eyebrow' => '06',
'title' => 'After-Action Review',
'body' => 'Frame-level replay from any camera angle. Per-trainee statistics. Per-unit dashboards. Comparable across sessions. Exportable for command review.',
'cta' => 'Inside after-action review',
'href' => '/technology/after-action-review/',
],
];
?>
<section class="av-hero" aria-labelledby="technology-headline">
<div class="av-hero__media" aria-hidden="true">
<div style="width:100%;height:100%;background:
radial-gradient(ellipse at 50% 30%, rgba(201,164,90,0.18), transparent 60%),
linear-gradient(135deg, #081427 0%, #0B1F3A 50%, #13294B 100%);"></div>
</div>
<div class="av-container">
<div class="av-hero__inner">
<p class="av-hero__eyebrow"><?php esc_html_e('Technology', 'asterion-bricks'); ?></p>
<h1 id="technology-headline" class="av-hero__headline">Engineered for the unforgiving.</h1>
<p class="av-hero__sub">
Six layers of technology, each engineered against a real failure mode of conventional simulators. Wireless 5K visuals. Real-recoil weapons. Autonomous AI agents. Ballistic physics. An instructor cockpit that sees through walls. And an after-action review that ends every session with data.
</p>
</div>
</div>
</section>
<section class="av-section" data-av-reveal>
<div class="av-container">
<div class="av-section__head">
<span class="av-eyebrow"><?php esc_html_e('The six pillars', 'asterion-bricks'); ?></span>
<h2 class="av-section__title">The six pillars of PROSERVE&trade;.</h2>
</div>
<div class="av-grid-3">
<?php foreach ($pillars as $p) : ?>
<article class="av-card av-card--feature">
<div class="av-card__body">
<span class="av-card__eyebrow" style="color:var(--color-brand-gold);"><?php echo esc_html($p['eyebrow']); ?></span>
<h3 class="av-card__title"><?php echo esc_html($p['title']); ?></h3>
<p class="av-card__excerpt"><?php echo esc_html($p['body']); ?></p>
<div class="av-card__cta">
<a href="<?php echo esc_url($home_url . ltrim($p['href'], '/')); ?>" class="av-link av-link--with-arrow"><?php echo esc_html($p['cta']); ?></a>
</div>
</div>
</article>
<?php endforeach; ?>
</div>
</div>
</section>
<?php get_footer(); ?>