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:
@@ -672,6 +672,10 @@
|
||||
.av-hero {
|
||||
position: relative;
|
||||
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);
|
||||
overflow: hidden;
|
||||
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)
|
||||
============================================================================ */
|
||||
|
||||
@@ -12,6 +12,14 @@
|
||||
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 -->
|
||||
|
||||
|
||||
@@ -103,7 +103,9 @@ $av_includes = [
|
||||
'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 → 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.
|
||||
];
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* 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');
|
||||
|
||||
@@ -23,6 +23,16 @@ defined('ABSPATH') || exit;
|
||||
<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">
|
||||
|
||||
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
|
||||
*
|
||||
* Renders any page that has no Custom Template assigned (or whose Custom
|
||||
* Template was stripped after the post_content migration). The header and
|
||||
* footer are shared globals; the content is whatever Gutenberg / Bricks
|
||||
* stored in post_content.
|
||||
* 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
|
||||
*/
|
||||
@@ -13,9 +17,21 @@ defined('ABSPATH') || exit;
|
||||
|
||||
get_header();
|
||||
|
||||
if (have_posts()) {
|
||||
while (have_posts()) { the_post();
|
||||
the_content();
|
||||
$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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user