chore: session 2 snapshot — archive before Bricks-first restart
Snapshots the project at the end of session 2, just before resetting to a Bricks-first orthodox architecture. RESTART-PLAN.md at the root explains why this approach is being abandoned and what the next session will look like. Why we restart - Three architectures got stacked: Custom PHP Templates → Gutenberg blocks in post_content → Bricks data in post meta. Each layer fights the next. - Bricks Builder UI does not load because our header.php / footer.php / page.php emit chrome that conflicts with Bricks' own template-parts. - CSS overrides multiplied (Gutenberg + Bricks variants) and still don't pixel-match the original PHP-rendered visual. - 8+ one-shot mu-plugins live in LocalWP to patch over inconsistencies. What survives the restart (preserved here on main) - BRIEF, both PDFs, .txt extracts (sources of truth) - assets/css/tokens.css (full design system, untouched) - assets/fonts/Inter-Variable*.woff2 (RGPD self-hosted) - inc/<section>-data.php files (text copy from PDF strategie, will become /content-source/ on next session for reuse as translation dictionary and as input to programmatic page generation) - The Git repo, remote, branch main - LocalWP site, Bricks v2.3.4, WPML 4.9.3 with active licenses - WPML EN + FR setup with directory URL strategy What gets wiped at the start of next session - wp-content/themes/asterion-bricks/header.php / footer.php / page.php / index.php / front-page.php / template-parts/ / templates/ - inc/render-blocks.php, inc/render-blocks-native.php, inc/render-bricks.php - inc/shortcodes.php, inc/i18n.php, inc/form-handler.php - 35+ WP pages created over the session - All one-shot mu-plugins in LocalWP/wp-content/mu-plugins/ Restart approach (detailed in RESTART-PLAN.md) - Minimalist child theme: style.css, functions.php, theme.json, tokens.css, trimmed utilities.css, fonts, inc/cpt.php — that's it. - No header.php / footer.php / page.php in child theme — Bricks Templates (Header / Footer / Single — Page) take over via Bricks' Conditions. - User builds 5-7 archetype Bricks templates visually (~1-2h each) then programmatic generation fills the variants from inc/*-data.php sources. - WPML duplication after EN is validated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
250
RESTART-PLAN.md
Normal file
250
RESTART-PLAN.md
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
# Restart Plan — Asterion VR refonte
|
||||||
|
|
||||||
|
**Date** : 2026-05-09 — fin de session 2
|
||||||
|
**Décision** : repartir d'une base propre, avec une approche Bricks-first orthodoxe.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Pourquoi redémarrer
|
||||||
|
|
||||||
|
L'approche actuelle a empilé trois architectures incompatibles :
|
||||||
|
|
||||||
|
1. **Custom Templates PHP** rendant depuis des data files (livrée, fonctionnait visuellement mais non éditable)
|
||||||
|
2. **Migration Gutenberg blocks** dans `post_content` (rendu visuel cassé, blocs `core/html` non éditables)
|
||||||
|
3. **Migration Bricks data** dans `_bricks_page_content_2` (Bricks builder UI ne s'affiche pas correctement parce que mes `header.php` / `footer.php` interfèrent)
|
||||||
|
|
||||||
|
À chaque tentative de fix, je rajoute un patch (override CSS, mu-plugin, conditionnel). Le résultat :
|
||||||
|
- Code du child theme pollué (multiple renderers coexistent : `render-blocks.php`, `render-blocks-native.php`, `render-bricks.php`)
|
||||||
|
- Settings Bricks bricolés, pas alignés sur le workflow Bricks officiel
|
||||||
|
- 35 pages WP créées + 4 doublons supprimés + duplications WPML qui ont nécessité un patch slug
|
||||||
|
- mu-plugins en cascade (8+ fichiers one-shot dans LocalWP)
|
||||||
|
|
||||||
|
**Bricks attend que le thème soit _minimaliste_ et qu'il prenne lui-même la main sur le rendu.** Mon child theme actuel fait l'inverse : il fournit des templates complets qui combattent Bricks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Ce qu'on garde (à préserver dans le repo avant reset)
|
||||||
|
|
||||||
|
| Asset | Lieu actuel | Sort |
|
||||||
|
|---|---|---|
|
||||||
|
| Brief + 2 PDFs + .txt extraits | racine repo | **garder tel quel** — sources de vérité |
|
||||||
|
| `inc/solutions-data.php` + `industries-data.php` + `technology-data.php` + `editorial-data.php` + `forms-data.php` | child theme | **garder, mais déplacer dans `/content-source/`** — données pures, réutilisables comme source de génération ou comme dictionnaire de traduction WPML |
|
||||||
|
| `assets/css/tokens.css` (palette, typo, spacing, motion) | child theme | **garder dans le nouveau child theme** — design system partagé entre Bricks et notre CSS |
|
||||||
|
| `assets/fonts/Inter-Variable.woff2` + `Inter-Variable-Italic.woff2` | child theme | **garder** (RGPD, déjà self-hostés) |
|
||||||
|
| `BRIEF-Claude-Code.md`, `MISSING-ASSETS.md`, `README.md` | racine | **garder, à amender** |
|
||||||
|
| Repo Git, remote `git.polymorph.fr`, branche `main` | — | **garder**, on continue dessus |
|
||||||
|
| LocalWP `asterion-2026` site, WordPress 6.9, port 10004 | LocalWP | **garder**, juste reset le contenu pages + theme |
|
||||||
|
| Bricks parent v2.3.4 + WPML 4.9.3 + licenses | LocalWP | **garder** |
|
||||||
|
|
||||||
|
## Ce qu'on supprime / archive
|
||||||
|
|
||||||
|
- Tout `wp-content/themes/asterion-bricks/` actuel **archivé dans une branche `archive/php-templates`** puis ré-init du dossier
|
||||||
|
- Toutes les pages WP créées (35+) → wipe complet
|
||||||
|
- Tous les mu-plugins one-shot dans LocalWP → wipe (sauf `asterion-debug.php` et `asterion-status.php` que je laisse temporairement comme outils dev)
|
||||||
|
- Les overrides CSS Gutenberg / Bricks accumulés
|
||||||
|
- Les Custom Templates `templates/page-*.php`
|
||||||
|
- Les renderers PHP `render-blocks.php`, `render-blocks-native.php`, `render-bricks.php`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Architecture cible — Bricks-first orthodoxe
|
||||||
|
|
||||||
|
### 3.1. Child theme minimaliste
|
||||||
|
|
||||||
|
Le child theme `asterion-bricks` ne fait **plus** :
|
||||||
|
- ❌ Pas de `header.php` / `footer.php` custom (Bricks gère via ses **templates**)
|
||||||
|
- ❌ Pas de `front-page.php` / `page.php` / `index.php` custom (Bricks gère)
|
||||||
|
- ❌ Pas de Custom Templates `page-*.php`
|
||||||
|
|
||||||
|
Le child theme fait **uniquement** :
|
||||||
|
- ✅ `style.css` : header WP avec `Template: bricks` (obligatoire)
|
||||||
|
- ✅ `functions.php` : enqueue tokens.css + utilities.css en frontend, register CPT (case_study, scenario), self-host fonts
|
||||||
|
- ✅ `assets/css/tokens.css` : design tokens (couleurs, typo, spacing) en CSS variables
|
||||||
|
- ✅ `assets/css/utilities.css` : helpers très ciblés uniquement (`.av-skip-link`, `.av-sr-only`)
|
||||||
|
- ✅ `assets/fonts/` : Inter Variable self-hosté
|
||||||
|
- ✅ `inc/cpt.php` : Custom Post Types
|
||||||
|
- ✅ `theme.json` : optionnel, pour Gutenberg color picker en cohérence
|
||||||
|
|
||||||
|
**Total estimé : 8 fichiers, ~400 lignes.** Drastiquement plus léger.
|
||||||
|
|
||||||
|
### 3.2. Bricks Templates (= header / footer / archives / single)
|
||||||
|
|
||||||
|
Tout le shell visuel devient des **Bricks Templates** créés via le builder :
|
||||||
|
|
||||||
|
| Template Bricks | Type | Rôle |
|
||||||
|
|---|---|---|
|
||||||
|
| `Header` | header | Logo + nav (Solutions / Industries / Technology / Why Asterion / Insights) + CTA + lang switcher |
|
||||||
|
| `Footer` | footer | 5 colonnes + bottom strip |
|
||||||
|
| `Archive — Insights` | archive | Liste des posts (blog hub) |
|
||||||
|
| `Single — Insight` | single | Article de blog |
|
||||||
|
| `Archive — Customers` | archive | Liste case_study |
|
||||||
|
| `Single — Case Study` | single | Page case_study individuelle |
|
||||||
|
| (option) `Single — Page` | single | Fallback si une page n'a pas de Bricks data |
|
||||||
|
|
||||||
|
Ces templates s'appliquent automatiquement à toutes les pages/posts via les **Bricks "Conditions"** (par exemple "Apply on : All pages"). Tu (Jérôme) construis ces 5-7 templates **une seule fois** dans Bricks, à ton rythme.
|
||||||
|
|
||||||
|
### 3.3. Pages individuelles
|
||||||
|
|
||||||
|
Pour chaque page (Home, Solutions hub, MY PROSERVE, Industries hub, Police, etc.), 2 options selon l'effort qu'on veut investir :
|
||||||
|
|
||||||
|
**Option A — Build visuel dans Bricks (recommandé pour les 7 templates de page)**
|
||||||
|
|
||||||
|
1. Tu (Jérôme) construis 1 page de chaque archetype dans Bricks builder :
|
||||||
|
- Home
|
||||||
|
- Solution detail (1 fois → réutilisable pour les 4 tiers)
|
||||||
|
- Industry detail (1 fois → 4 industries)
|
||||||
|
- Technology detail (1 fois → 6 tech pages)
|
||||||
|
- Editorial (1 fois → 5 pages)
|
||||||
|
- Conversion form (1 fois → 4 forms)
|
||||||
|
- Legal (1 fois → 4 legals)
|
||||||
|
|
||||||
|
2. Pour chaque archetype, tu **sauvegardes en Template Bricks** avec un nom (ex. "Solution Detail") dans Bricks → Templates.
|
||||||
|
|
||||||
|
3. Pour les variantes (4 solutions, 4 industries, etc.) : tu importes le template puis change juste le contenu textuel (Bricks "Bulk Edit" ou copy-paste).
|
||||||
|
|
||||||
|
**Effort estimé** : 7 templates × 30-60 min de build visuel = 4-7h de ton temps.
|
||||||
|
|
||||||
|
**Option B — Génération programmatique depuis les data files**
|
||||||
|
|
||||||
|
Une fois UN template Bricks finalisé visuellement pour un archetype, je peux :
|
||||||
|
1. Récupérer son JSON via `_bricks_page_content_2`
|
||||||
|
2. Identifier les "slots" textuels (le titre h1, les paragraphes, les card items, etc.)
|
||||||
|
3. Pour chaque variante (Police, Military, etc.), prendre les data du fichier `industries-data.php`, faire un templating substitution, écrire dans la nouvelle page WP.
|
||||||
|
|
||||||
|
Je l'ai prouvé techniquement — la difficulté c'est de **partir d'un template Bricks bien construit** pour ne pas inventer le format.
|
||||||
|
|
||||||
|
→ Je propose qu'on fasse **A pour 1 industrie + 1 solution + 1 tech**, puis **B pour les 11 autres similaires** (4 industries × 4 solutions × 6 tech, soit 14, dont 3 déjà manuels = 11 à générer).
|
||||||
|
|
||||||
|
### 3.4. Traduction WPML
|
||||||
|
|
||||||
|
Méthode standard :
|
||||||
|
1. Une fois une page EN finalisée dans Bricks (template + contenu), elle a son `_bricks_page_content_2`
|
||||||
|
2. Dans WPML → Translation Dashboard → choisir la page → "Translate"
|
||||||
|
3. WPML Translation Editor scanne les valeurs textuelles dans le JSON Bricks et expose un éditeur côté-à-côté EN/FR
|
||||||
|
4. Tu / le linguiste rentre les traductions, valide, publie
|
||||||
|
|
||||||
|
Pour pré-remplir : on peut écrire un mu-plugin one-shot qui pour chaque page EN crée la page FR via WPML API + pré-remplit les traductions en utilisant les data files (déjà bilingues si je les structure ainsi). Mais à faire APRÈS qu'au moins 1 page soit validée en Bricks.
|
||||||
|
|
||||||
|
### 3.5. Tokens et style cohérence
|
||||||
|
|
||||||
|
Mes tokens CSS (couleurs, typo, spacing) doivent être accessibles dans Bricks pour qu'on puisse les sélectionner via les controls Bricks (color picker, font picker, etc.).
|
||||||
|
|
||||||
|
Bricks a une feature **Theme Styles** qui permet de définir une palette globale et des variables CSS. On va :
|
||||||
|
1. Importer `tokens.css` via `functions.php` (chargé sur frontend ET dans builder)
|
||||||
|
2. Configurer **Bricks → Theme Styles** avec la même palette (color slugs `brand-navy`, `brand-gold`, etc.) référençant nos variables
|
||||||
|
3. Quand tu construis dans Bricks builder, tu choisis "Brand Navy" dans le color picker → Bricks émet `var(--bricks-color-brand-navy)` que je mappe à `var(--color-brand-navy)` dans tokens.css
|
||||||
|
|
||||||
|
Résultat : le design system est centralisé dans `tokens.css`, mais accessible visuellement dans Bricks builder. Tu modifies un token → tout le site update.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Étapes de restart (ordre)
|
||||||
|
|
||||||
|
### Phase 0 — Backup (avant compactage)
|
||||||
|
|
||||||
|
1. Créer une branche `archive/session-2-php-templates` depuis `main`
|
||||||
|
2. Pousser sur le remote
|
||||||
|
3. La branche `main` reste là où elle est (juste un commit "archive snapshot" et on continue)
|
||||||
|
|
||||||
|
### Phase 1 — Reset du theme
|
||||||
|
|
||||||
|
1. `git rm -r wp-content/themes/asterion-bricks/`
|
||||||
|
2. Recréer le child theme **minimaliste** (8 fichiers, comme décrit en 3.1)
|
||||||
|
3. `tokens.css` repris en l'état (déjà parfait)
|
||||||
|
4. Ré-importer Inter fonts (déjà téléchargées)
|
||||||
|
5. Activer le child theme
|
||||||
|
|
||||||
|
### Phase 2 — Reset des pages WP
|
||||||
|
|
||||||
|
1. Mu-plugin one-shot qui : `wp_delete_post(force) sur tous les posts type=page`. Garde le post `Sample Page` ou wipe complet. Reset des options `_bricks_page_content_2` partout.
|
||||||
|
2. Reset Bricks settings : `delete_option('bricks_global_settings')` puis re-set juste `postTypes = ['page','post','case_study','scenario']`.
|
||||||
|
|
||||||
|
### Phase 3 — Bricks Theme Styles
|
||||||
|
|
||||||
|
1. Tu ouvres **Bricks → Theme Styles** dans WP admin
|
||||||
|
2. Tu crées une theme style "Asterion VR" appliquée à tout le site
|
||||||
|
3. Tu y configures : couleurs (utilise mes hex de tokens.css), typo (Inter), heading sizes
|
||||||
|
4. Sauvegarde
|
||||||
|
|
||||||
|
### Phase 4 — Templates Bricks
|
||||||
|
|
||||||
|
1. **Header** : Bricks → Templates → Add New → type "Header" → tu construis le menu sticky avec 5 items + CTA + lang switcher → conditions "Apply on : Entire website"
|
||||||
|
2. **Footer** : pareil, type "Footer", 5 colonnes + bottom strip
|
||||||
|
3. (option) **Single — Page fallback** pour les pages sans Bricks data
|
||||||
|
|
||||||
|
### Phase 5 — Première vraie page
|
||||||
|
|
||||||
|
1. WP admin → Pages → Add New → "Industries" (slug `industries`) → publier vide
|
||||||
|
2. Ouvrir **Edit with Bricks**
|
||||||
|
3. Construire la page hub Industries (hero + 4 cards) en Bricks éléments natifs, en utilisant le copy depuis `content-source/industries-data.php`
|
||||||
|
4. Sauver → exporter le template → versionner le JSON dans `wp-content/themes/asterion-bricks/templates/industries-overview.json`
|
||||||
|
|
||||||
|
### Phase 6 — Validation
|
||||||
|
|
||||||
|
Tu valides visuellement Industries. Si OK, on continue avec :
|
||||||
|
- 4 pages industry detail (template "Industry Detail")
|
||||||
|
- Solutions hub + 4 tiers
|
||||||
|
- Technology hub + 6 pillars
|
||||||
|
- Editorial × 5
|
||||||
|
- Conversion × 4
|
||||||
|
- Legal × 4
|
||||||
|
- Insights / Resources
|
||||||
|
- Home
|
||||||
|
|
||||||
|
Soit **34 pages** à construire au total. Avec mon aide pour la génération programmatique (option B en 3.3), on en fait probablement **7 manuellement, 27 générées** à partir des templates manuels.
|
||||||
|
|
||||||
|
### Phase 7 — WPML
|
||||||
|
|
||||||
|
Une fois EN finalisé, mu-plugin one-shot qui :
|
||||||
|
- Duplique chaque page EN en FR via WPML API (utilise les data FR des data files comme pré-remplissage)
|
||||||
|
- Tu valides, le linguiste affine
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Risques résiduels
|
||||||
|
|
||||||
|
- **Bricks license** : si elle expire (annuelle), le builder est bridé. Vérifier renouvellement.
|
||||||
|
- **Format JSON Bricks** : peut changer entre versions Bricks majeures (v3.x). On versionne nos exports template JSON pour pouvoir les re-importer.
|
||||||
|
- **Performance** : Bricks ajoute du CSS / JS supplémentaire vs HTML pur. Lighthouse cible (90+) atteignable mais demande optimisation (lazy load, caching).
|
||||||
|
- **WPML traduction** : le linguiste doit comprendre Bricks Translation Editor (formation 30 min suffisent normalement).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Compactage du contexte
|
||||||
|
|
||||||
|
Avant de redémarrer, il faut :
|
||||||
|
1. **Mettre à jour les memories** (`project_asterion_status.md`, etc.) avec :
|
||||||
|
- L'état archivé en branche
|
||||||
|
- Le plan ci-dessus
|
||||||
|
- Les data files (sources de vérité textuelle)
|
||||||
|
- Les tokens.css (à reprendre tel quel)
|
||||||
|
- Les contraintes (XAMPP intouchable, Bricks-first, etc.)
|
||||||
|
2. **Compactage du contexte** : tu commandes `/compact` ou redémarres une nouvelle session, je reprends depuis les memories.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Time-box
|
||||||
|
|
||||||
|
| Phase | Effort dev (moi) | Effort utilisateur (toi) | Calendrier |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 0 — Backup | 5 min | 0 | jour J |
|
||||||
|
| 1 — Theme reset | 30 min | valider | jour J |
|
||||||
|
| 2 — Pages reset | 15 min | 0 | jour J |
|
||||||
|
| 3 — Theme Styles | 0 | 30 min | jour J |
|
||||||
|
| 4 — Templates header/footer | 0 | 1-2h | jour J/J+1 |
|
||||||
|
| 5 — 1ère page (Industries) | 30 min guidance | 30 min | jour J+1 |
|
||||||
|
| 6 — Validation + autres pages | 2-3h génération | 4-7h build | semaine |
|
||||||
|
| 7 — WPML traductions | 1h génération | 30 min × 35 pages = ~17h | linguiste 1-2 semaines |
|
||||||
|
|
||||||
|
**On peut avoir un site EN complet en Bricks dans 1 semaine** si tu mets 4-7h de build visuel. La trad FR vient ensuite.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Ce que je veux que tu confirmes avant de démarrer
|
||||||
|
|
||||||
|
1. **OK pour archiver la session actuelle dans une branche `archive/session-2-php-templates` ?** (rien ne se perd, juste isolé)
|
||||||
|
2. **OK pour le child theme minimaliste sans header.php / footer.php** ? (Bricks gère via templates)
|
||||||
|
3. **Tu acceptes de construire 5-7 templates Bricks visuels** (1-2h chacun) ? Ou tu préfères que je tente une génération 100% programmatique (plus risqué, format JSON instable) ?
|
||||||
|
4. **OK pour les CPT case_study + scenario en plus de page/post** ? (déjà déclarés, on les garde)
|
||||||
|
|
||||||
|
Réponds aux 4. Ensuite : `/compact` et on redémarre propre.
|
||||||
@@ -672,6 +672,10 @@
|
|||||||
.av-hero {
|
.av-hero {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: var(--color-brand-navy);
|
background-color: var(--color-brand-navy);
|
||||||
|
background-image:
|
||||||
|
radial-gradient(ellipse at 70% 30%, rgba(201, 164, 90, 0.15), transparent 60%),
|
||||||
|
radial-gradient(ellipse at 20% 70%, rgba(31, 91, 168, 0.20), transparent 60%),
|
||||||
|
linear-gradient(135deg, var(--color-brand-navy-deep) 0%, var(--color-brand-navy) 50%, var(--color-bg-dark-elevated) 100%);
|
||||||
color: var(--color-text-on-dark);
|
color: var(--color-text-on-dark);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
@@ -920,6 +924,361 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
12c. Bricks Builder element overrides — when a Bricks element carries one
|
||||||
|
of our `av-*` classes, raise specificity so our design-system rules
|
||||||
|
win against Bricks default styles (which use a single `.brxe-*`
|
||||||
|
class selector).
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Hero — re-apply our layout & background on top of brxe-section. */
|
||||||
|
.brxe-section.av-hero {
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--color-brand-navy);
|
||||||
|
/* Use background (shorthand) so .bricks-lazy-hidden's
|
||||||
|
`background-image: none !important` doesn't kill the gradient outright;
|
||||||
|
the shorthand wins by being declared after the shorthand reset. */
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse at 70% 30%, rgba(201, 164, 90, 0.15), transparent 60%),
|
||||||
|
radial-gradient(ellipse at 20% 70%, rgba(31, 91, 168, 0.20), transparent 60%),
|
||||||
|
linear-gradient(135deg, var(--color-brand-navy-deep) 0%, var(--color-brand-navy) 50%, var(--color-bg-dark-elevated) 100%);
|
||||||
|
color: var(--color-text-on-dark);
|
||||||
|
min-height: 70vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-block: var(--space-20);
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.brxe-section.av-hero { min-height: 85vh; padding-block: var(--space-32); }
|
||||||
|
}
|
||||||
|
.brxe-container.av-hero__inner {
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--container-max);
|
||||||
|
margin-inline: auto;
|
||||||
|
padding-inline: var(--container-padding-mobile);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-6);
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) { .brxe-container.av-hero__inner { padding-inline: var(--container-padding-tablet); } }
|
||||||
|
@media (min-width: 1280px) { .brxe-container.av-hero__inner { padding-inline: var(--container-padding-desktop); } }
|
||||||
|
|
||||||
|
/* Hero typography on Bricks-rendered text-basic + heading. */
|
||||||
|
.brxe-text-basic.av-hero__eyebrow {
|
||||||
|
font-size: var(--text-overline);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: var(--tracking-overline);
|
||||||
|
color: var(--color-brand-gold);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.brxe-heading.av-hero__headline {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: clamp(2.5rem, 6vw, var(--text-display-xl));
|
||||||
|
line-height: var(--leading-display-xl);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
letter-spacing: var(--tracking-tight);
|
||||||
|
color: var(--color-text-on-dark);
|
||||||
|
margin: 0;
|
||||||
|
max-width: 18ch;
|
||||||
|
}
|
||||||
|
.brxe-text-basic.av-hero__sub {
|
||||||
|
font-size: var(--text-body-lg);
|
||||||
|
line-height: var(--leading-body-lg);
|
||||||
|
color: var(--color-text-on-dark-muted);
|
||||||
|
max-width: 60ch;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section spacing on Bricks-rendered sections. */
|
||||||
|
.brxe-section.av-section {
|
||||||
|
padding-block: var(--space-16);
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.brxe-section.av-section { padding-block: var(--space-20); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plain Bricks container reuses our container width / padding. */
|
||||||
|
.brxe-container.av-container {
|
||||||
|
max-width: var(--container-max);
|
||||||
|
margin-inline: auto;
|
||||||
|
padding-inline: var(--container-padding-mobile);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) { .brxe-container.av-container { padding-inline: var(--container-padding-tablet); } }
|
||||||
|
@media (min-width: 1280px) { .brxe-container.av-container { padding-inline: var(--container-padding-desktop); } }
|
||||||
|
|
||||||
|
/* Cards grid — Bricks `block` element uses display:flex column by default.
|
||||||
|
Force CSS grid back. */
|
||||||
|
.brxe-block.av-grid-3 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--space-6);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) { .brxe-block.av-grid-3 { grid-template-columns: repeat(2, 1fr); } }
|
||||||
|
@media (min-width: 1024px) { .brxe-block.av-grid-3 { grid-template-columns: repeat(3, 1fr); } }
|
||||||
|
|
||||||
|
.brxe-block.av-grid-4 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--space-6);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) { .brxe-block.av-grid-4 { grid-template-columns: repeat(2, 1fr); } }
|
||||||
|
@media (min-width: 1280px) { .brxe-block.av-grid-4 { grid-template-columns: repeat(4, 1fr); } }
|
||||||
|
|
||||||
|
/* Card design on Bricks block. */
|
||||||
|
.brxe-block.av-card {
|
||||||
|
background-color: var(--color-bg-elevated);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
overflow: hidden;
|
||||||
|
padding: var(--space-6);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-3);
|
||||||
|
transition:
|
||||||
|
box-shadow var(--duration-default) var(--ease-default),
|
||||||
|
transform var(--duration-default) var(--ease-default);
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.brxe-block.av-card { padding: var(--space-8); }
|
||||||
|
}
|
||||||
|
.brxe-block.av-card:hover {
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
.brxe-text-basic.av-card__eyebrow {
|
||||||
|
font-size: var(--text-overline);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: var(--tracking-overline);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.brxe-heading.av-card__title {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: var(--text-h3);
|
||||||
|
line-height: var(--leading-h3);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.brxe-text-basic.av-card__excerpt {
|
||||||
|
font-size: var(--text-body-md);
|
||||||
|
line-height: var(--leading-body-md);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button on Bricks button element. Bricks renders <a class="brxe-button bricks-button">. */
|
||||||
|
.brxe-button.av-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
height: 44px;
|
||||||
|
padding: 0 var(--space-5);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: var(--text-body-md);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
line-height: 1;
|
||||||
|
text-decoration: none;
|
||||||
|
border: var(--border-default) solid transparent;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
align-self: flex-start;
|
||||||
|
transition:
|
||||||
|
background-color var(--duration-fast) var(--ease-default),
|
||||||
|
color var(--duration-fast) var(--ease-default),
|
||||||
|
transform var(--duration-fast) var(--ease-default),
|
||||||
|
border-color var(--duration-fast) var(--ease-default);
|
||||||
|
}
|
||||||
|
.brxe-button.av-btn--md { height: 44px; padding: 0 var(--space-5); }
|
||||||
|
.brxe-button.av-btn--lg { height: 52px; padding: 0 1.75rem; font-size: var(--text-body-lg); }
|
||||||
|
|
||||||
|
.brxe-button.av-btn--primary {
|
||||||
|
background-color: var(--color-brand-gold);
|
||||||
|
color: var(--color-brand-navy);
|
||||||
|
border-color: var(--color-brand-gold);
|
||||||
|
}
|
||||||
|
.brxe-button.av-btn--primary:hover,
|
||||||
|
.brxe-button.av-btn--primary:focus-visible {
|
||||||
|
background-color: var(--color-brand-gold-soft);
|
||||||
|
border-color: var(--color-brand-gold-soft);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
.brxe-button.av-btn--secondary {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--color-brand-navy);
|
||||||
|
border-color: var(--color-brand-navy);
|
||||||
|
}
|
||||||
|
.brxe-button.av-btn--secondary:hover,
|
||||||
|
.brxe-button.av-btn--secondary:focus-visible {
|
||||||
|
background-color: var(--color-brand-navy);
|
||||||
|
color: var(--color-text-on-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
12b. Gutenberg block overrides — neutralize default layout styles when
|
||||||
|
a block also carries one of our `av-*` design-system classes. We
|
||||||
|
keep our grid / spacing / button rules in charge.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Group blocks: drop the WP layout flow constraints that introduce extra margins. */
|
||||||
|
.av-hero.wp-block-group,
|
||||||
|
.av-section.wp-block-group,
|
||||||
|
.av-card.wp-block-group,
|
||||||
|
.av-card__body.wp-block-group,
|
||||||
|
.av-container.wp-block-group,
|
||||||
|
.av-hero__inner.wp-block-group {
|
||||||
|
margin-block: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero overrides — keep the hero layout we already had. */
|
||||||
|
.av-hero.wp-block-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.av-hero__inner.wp-block-group {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container blocks: WP `is-layout-constrained` pads/centers via wp-container-X
|
||||||
|
helpers. We keep our own .av-container width/padding. */
|
||||||
|
.av-container.wp-block-group {
|
||||||
|
max-width: var(--container-max);
|
||||||
|
margin-inline: auto;
|
||||||
|
padding-inline: var(--container-padding-mobile);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) { .av-container.wp-block-group { padding-inline: var(--container-padding-tablet); } }
|
||||||
|
@media (min-width: 1280px) { .av-container.wp-block-group { padding-inline: var(--container-padding-desktop); } }
|
||||||
|
|
||||||
|
/* Columns: WP forces display:flex via .is-layout-flex. We force grid back so
|
||||||
|
our 1/2/3/4-col responsive rules win. */
|
||||||
|
.av-grid-3.wp-block-columns,
|
||||||
|
.av-grid-3.wp-block-columns.is-layout-flex {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--space-6);
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.av-grid-3.wp-block-columns,
|
||||||
|
.av-grid-3.wp-block-columns.is-layout-flex { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.av-grid-3.wp-block-columns,
|
||||||
|
.av-grid-3.wp-block-columns.is-layout-flex { grid-template-columns: repeat(3, 1fr); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.av-grid-4.wp-block-columns,
|
||||||
|
.av-grid-4.wp-block-columns.is-layout-flex {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--space-6);
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.av-grid-4.wp-block-columns,
|
||||||
|
.av-grid-4.wp-block-columns.is-layout-flex { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.av-grid-4.wp-block-columns,
|
||||||
|
.av-grid-4.wp-block-columns.is-layout-flex { grid-template-columns: repeat(4, 1fr); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Each column: drop the flex-basis WP applies. */
|
||||||
|
.wp-block-columns.av-grid-3 > .wp-block-column,
|
||||||
|
.wp-block-columns.av-grid-4 > .wp-block-column {
|
||||||
|
flex-basis: auto !important;
|
||||||
|
flex-grow: 0 !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section spacing — re-apply our padding-block to the migrated section blocks. */
|
||||||
|
.av-section.wp-block-group {
|
||||||
|
padding-block: var(--space-16);
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.av-section.wp-block-group { padding-block: var(--space-20); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards inside a wp-block-column: keep our card design. */
|
||||||
|
.av-card.wp-block-group {
|
||||||
|
background-color: var(--color-bg-elevated);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%; /* fill the column for visual rhythm */
|
||||||
|
}
|
||||||
|
.av-card__body.wp-block-group {
|
||||||
|
padding: var(--space-6);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-3);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.av-card__body.wp-block-group { padding: var(--space-8); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons row: WP wraps every button in `.wp-block-button` and adds
|
||||||
|
`.is-layout-flex` to the parent, which centers and adds gap.
|
||||||
|
Strip the WP padding/margin so our `.av-btn` sizing wins. */
|
||||||
|
.wp-block-buttons.av-card__cta,
|
||||||
|
.wp-block-buttons.av-hero__ctas {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.wp-block-button.av-btn {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.wp-block-button.av-btn .wp-block-button__link {
|
||||||
|
/* Reset Gutenberg's built-in button skin — let .av-btn rules win. */
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
line-height: 1;
|
||||||
|
transition: none;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
/* Inherit our .av-btn padding via the containing flex layout. */
|
||||||
|
}
|
||||||
|
/* Auto-margin-top to push CTAs to the bottom of cards. */
|
||||||
|
.av-card__cta {
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Eyebrow / overline reset — Gutenberg paragraph defaults could drift. */
|
||||||
|
p.av-card__eyebrow,
|
||||||
|
p.av-hero__eyebrow {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WordPress core "global styles" reset noise — collapse the auto block-margin
|
||||||
|
that pushes our text away from the hero edges. */
|
||||||
|
.wp-block-group.av-hero > .wp-block-group,
|
||||||
|
.wp-block-group.av-section > .wp-block-group {
|
||||||
|
margin-block: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
13. Reveal animation (scroll-triggered, JS-driven via main.js)
|
13. Reveal animation (scroll-triggered, JS-driven via main.js)
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
|
|||||||
@@ -12,6 +12,14 @@
|
|||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
$av_year = date_i18n('Y');
|
$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 -->
|
</main><!-- /#main -->
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,9 @@ $av_includes = [
|
|||||||
'inc/cpt.php', // Custom post types: case_study, scenario.
|
'inc/cpt.php', // Custom post types: case_study, scenario.
|
||||||
'inc/seo.php', // Schema.org JSON-LD overrides.
|
'inc/seo.php', // Schema.org JSON-LD overrides.
|
||||||
'inc/form-handler.php', // Conversion form POST handler.
|
'inc/form-handler.php', // Conversion form POST handler.
|
||||||
'inc/render-blocks.php', // Data array → Gutenberg HTML for the migrator.
|
'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.
|
'inc/shortcodes.php', // [av_form slug="..."] etc.
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -150,10 +152,20 @@ add_action('after_setup_theme', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable Gutenberg block library default styles when Bricks is active —
|
* Disable Gutenberg block library default styles on the public frontend only —
|
||||||
* we own typography end-to-end through tokens.css.
|
* 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 () {
|
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');
|
||||||
wp_dequeue_style('wp-block-library-theme');
|
wp_dequeue_style('wp-block-library-theme');
|
||||||
wp_dequeue_style('global-styles');
|
wp_dequeue_style('global-styles');
|
||||||
|
|||||||
@@ -23,6 +23,16 @@ defined('ABSPATH') || exit;
|
|||||||
<body <?php body_class(); ?>>
|
<body <?php body_class(); ?>>
|
||||||
<?php wp_body_open(); ?>
|
<?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>
|
<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">
|
<header class="av-header" role="banner">
|
||||||
|
|||||||
228
wp-content/themes/asterion-bricks/inc/render-blocks-native.php
Normal file
228
wp-content/themes/asterion-bricks/inc/render-blocks-native.php
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
<?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]);
|
||||||
|
}
|
||||||
258
wp-content/themes/asterion-bricks/inc/render-bricks.php
Normal file
258
wp-content/themes/asterion-bricks/inc/render-bricks.php
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
@@ -2,10 +2,14 @@
|
|||||||
/**
|
/**
|
||||||
* Asterion Bricks — Default page template
|
* Asterion Bricks — Default page template
|
||||||
*
|
*
|
||||||
* Renders any page that has no Custom Template assigned (or whose Custom
|
* If the current page has Bricks data in its `_bricks_page_content_2` post
|
||||||
* Template was stripped after the post_content migration). The header and
|
* meta, defer to Bricks' own renderer (Bricks\Frontend::render_content). This
|
||||||
* footer are shared globals; the content is whatever Gutenberg / Bricks
|
* is the same pattern Bricks parent's page.php uses; we keep it here so the
|
||||||
* stored in post_content.
|
* 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
|
* @package AsterionBricks
|
||||||
*/
|
*/
|
||||||
@@ -13,9 +17,21 @@ defined('ABSPATH') || exit;
|
|||||||
|
|
||||||
get_header();
|
get_header();
|
||||||
|
|
||||||
if (have_posts()) {
|
$av_bricks_data = class_exists('\\Bricks\\Helpers')
|
||||||
while (have_posts()) { the_post();
|
? \Bricks\Helpers::get_bricks_data(get_the_ID(), 'content')
|
||||||
the_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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user