feat(child-theme): scaffold asterion-bricks v0.1.0
Initial child theme structure inheriting from Bricks parent. Token-driven, RGPD-native, mobile-first foundation ready for component and template work. What's in this commit - style.css: WP theme metadata (Template: bricks, v0.1.0) - functions.php: enqueue cascade tokens -> utilities -> components, defer-loaded main.js, font preload, theme support, image sizes (av-hero 2400x1350, av-card 800x600, av-thumb 400x300), Gutenberg default styles dequeued - theme.json: Gutenberg color/typo palette mirroring tokens - assets/css/tokens.css: full design system per Design Handoff section 2 (brand colors, neutrals, semantics, type scale + line-heights, 8pt spacing, radii, shadows, z-index, motion, prefers-reduced-motion) - assets/css/utilities.css: modern reset + .av-container, .av-skip-link, .av-sr-only, .av-stack, .av-section helpers, opinionated heading scale - assets/css/components.css: index of planned components + buttons (5 variants x 4 sizes per Design Handoff section 3.1) and links - assets/js/main.js: vanilla skeleton with header scroll, IntersectionObserver reveal, count-up, mobile drawer; respects prefers-reduced-motion - inc/cpt.php: register_post_type case_study (slug /customers/) + scenario (slug /scenarios/) + shared 'industry' taxonomy - inc/seo.php: Organization JSON-LD on home; placeholders for Product, Article, FAQPage, BreadcrumbList - assets/fonts/: Inter Variable + Italic WOFF2 self-hosted (RGPD; no Google Fonts CDN). Inter Display @font-face commented — Inter 4+ uses opsz axis from the same file. Out of scope (intentionally deferred) - Component fills: cards, forms, navigation, alerts, modals, tabs (placeholders) - Page templates: home and beyond - Bricks JSON template exports - Schema.org Product / Article / FAQPage / BreadcrumbList implementations Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
53
wp-content/themes/asterion-bricks/README.md
Normal file
53
wp-content/themes/asterion-bricks/README.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Asterion Bricks — Child theme
|
||||||
|
|
||||||
|
Custom WordPress child theme for **asterionvr.com**, built on top of [Bricks Builder](https://bricksbuilder.io). Tactical-cinematic design system for the PROSERVE XR training platform.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
asterion-bricks/
|
||||||
|
├── style.css # WP theme metadata only (CSS lives in assets/css/)
|
||||||
|
├── functions.php # enqueues, theme support, includes
|
||||||
|
├── theme.json # Gutenberg color/typo palette
|
||||||
|
├── assets/
|
||||||
|
│ ├── css/
|
||||||
|
│ │ ├── tokens.css # design tokens — source of truth
|
||||||
|
│ │ ├── utilities.css # CSS reset + utility classes (.av-container, etc.)
|
||||||
|
│ │ └── components.css # component styles (buttons, cards, header, footer…)
|
||||||
|
│ ├── js/
|
||||||
|
│ │ └── main.js # vanilla JS, defer-loaded
|
||||||
|
│ ├── fonts/ # self-hosted Inter (RGPD)
|
||||||
|
│ └── img/ # static visuals
|
||||||
|
├── templates/ # Bricks template JSON exports (versioned)
|
||||||
|
└── inc/
|
||||||
|
├── cpt.php # custom post types: case_study, scenario
|
||||||
|
└── seo.php # Schema.org JSON-LD overrides
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- **CSS prefix** : `av-` (asterion-vr) on every custom selector to avoid collision with Bricks classes.
|
||||||
|
- **BEM-light** naming : `.av-card`, `.av-card--feature`, `.av-card__icon`, `.av-card.is-loading`.
|
||||||
|
- **Zero hardcoded values** — every color, spacing, radius and duration goes through `tokens.css`.
|
||||||
|
- **JS** : vanilla, defer-loaded, prefer `IntersectionObserver` over scroll listeners.
|
||||||
|
- **A11y** : keyboard reachable, `:focus-visible` outline, ARIA where needed, contrast ≥ 4.5:1, `prefers-reduced-motion` respected.
|
||||||
|
|
||||||
|
## Activation
|
||||||
|
|
||||||
|
1. Ensure Bricks parent is installed at `wp-content/themes/bricks/`.
|
||||||
|
2. WP Admin → Appearance → Themes → activate **Asterion Bricks**.
|
||||||
|
|
||||||
|
## Performance budget (per BRIEF section 9)
|
||||||
|
|
||||||
|
| Asset | Budget |
|
||||||
|
|-----------------|-------------------------------|
|
||||||
|
| HTML initial | < 100 KB gzipped |
|
||||||
|
| CSS total | < 50 KB gzipped |
|
||||||
|
| JS initial | < 200 KB gzipped |
|
||||||
|
| Hero video | < 2 MB |
|
||||||
|
| Hero poster | < 150 KB |
|
||||||
|
| LCP / CLS / TBT | < 2.5s / < 0.1 / < 200ms |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
GPL v2 or later. Same as WordPress core.
|
||||||
233
wp-content/themes/asterion-bricks/assets/css/components.css
Normal file
233
wp-content/themes/asterion-bricks/assets/css/components.css
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
/**
|
||||||
|
* Asterion Bricks — Components
|
||||||
|
*
|
||||||
|
* Component-level styles, layered on top of tokens + utilities.
|
||||||
|
* Loaded last in the cascade.
|
||||||
|
*
|
||||||
|
* Naming : `.av-<component>` base, `.av-<component>--<modifier>`,
|
||||||
|
* `.av-<component>__<element>`, `.av-<component>.is-<state>`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Section index (filled progressively as components ship)
|
||||||
|
|
||||||
|
1. Buttons .av-btn
|
||||||
|
2. Links .av-link
|
||||||
|
3. Forms .av-input, .av-label, .av-select, .av-textarea
|
||||||
|
4. Cards .av-card and variants
|
||||||
|
5. Stat callout .av-stat
|
||||||
|
6. Navigation header .av-header, .av-nav, .av-megamenu, .av-drawer
|
||||||
|
7. Footer .av-footer
|
||||||
|
8. Alerts & toasts .av-alert, .av-toast
|
||||||
|
9. Modals .av-modal
|
||||||
|
10. Tooltips & popovers .av-tooltip
|
||||||
|
11. Tabs & accordions .av-tabs, .av-accordion
|
||||||
|
12. Tables .av-table
|
||||||
|
13. Badges & tags .av-badge
|
||||||
|
|
||||||
|
Reference : Design Handoff Specification, section 3.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
1. Buttons (Design Handoff section 3.1)
|
||||||
|
|
||||||
|
Variants : primary, secondary, ghost, on-dark, destructive
|
||||||
|
Sizes : sm (32px), md (44px default), lg (52px), xl (60px hero only)
|
||||||
|
States : default, hover, focus, active, disabled, loading
|
||||||
|
Border radius : radius-sm by default (tactical aesthetic — crisp corners).
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.av-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
padding: 0 var(--space-5);
|
||||||
|
height: 44px;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: var(--text-body-md);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
border: var(--border-default) solid transparent;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition:
|
||||||
|
background-color var(--duration-fast) var(--ease-default),
|
||||||
|
color var(--duration-fast) var(--ease-default),
|
||||||
|
transform var(--duration-fast) var(--ease-default),
|
||||||
|
box-shadow var(--duration-default) var(--ease-default),
|
||||||
|
border-color var(--duration-fast) var(--ease-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.av-btn:disabled,
|
||||||
|
.av-btn.is-disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.av-btn.is-loading {
|
||||||
|
cursor: wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Sizes --- */
|
||||||
|
|
||||||
|
.av-btn--sm {
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 var(--space-3);
|
||||||
|
font-size: var(--text-body-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.av-btn--md {
|
||||||
|
height: 44px;
|
||||||
|
padding: 0 var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.av-btn--lg {
|
||||||
|
height: 52px;
|
||||||
|
padding: 0 var(--space-7, 1.75rem);
|
||||||
|
font-size: var(--text-body-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.av-btn--xl {
|
||||||
|
height: 60px;
|
||||||
|
padding: 0 var(--space-9, 2.25rem);
|
||||||
|
font-size: var(--text-h4);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Variant : primary (gold fill, navy text) --- */
|
||||||
|
|
||||||
|
.av-btn--primary {
|
||||||
|
background-color: var(--color-brand-gold);
|
||||||
|
color: var(--color-brand-navy);
|
||||||
|
border-color: var(--color-brand-gold);
|
||||||
|
}
|
||||||
|
.av-btn--primary:hover,
|
||||||
|
.av-btn--primary:focus-visible {
|
||||||
|
background-color: var(--color-brand-gold-soft);
|
||||||
|
border-color: var(--color-brand-gold-soft);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-glow-gold);
|
||||||
|
}
|
||||||
|
.av-btn--primary:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
filter: brightness(0.92);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Variant : secondary (outlined, navy text) --- */
|
||||||
|
|
||||||
|
.av-btn--secondary {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--color-brand-navy);
|
||||||
|
border-color: var(--color-brand-navy);
|
||||||
|
}
|
||||||
|
.av-btn--secondary:hover,
|
||||||
|
.av-btn--secondary:focus-visible {
|
||||||
|
background-color: var(--color-brand-navy);
|
||||||
|
color: var(--color-text-on-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Variant : ghost (navy text, no border) --- */
|
||||||
|
|
||||||
|
.av-btn--ghost {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--color-brand-navy);
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
.av-btn--ghost:hover,
|
||||||
|
.av-btn--ghost:focus-visible {
|
||||||
|
background-color: var(--color-bg-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Variant : on-dark (for dark backgrounds — text gold or white) --- */
|
||||||
|
|
||||||
|
.av-btn--on-dark.av-btn--primary {
|
||||||
|
background-color: var(--color-brand-gold);
|
||||||
|
color: var(--color-brand-navy);
|
||||||
|
}
|
||||||
|
.av-btn--on-dark.av-btn--secondary {
|
||||||
|
color: var(--color-text-on-dark);
|
||||||
|
border-color: var(--color-text-on-dark);
|
||||||
|
}
|
||||||
|
.av-btn--on-dark.av-btn--secondary:hover,
|
||||||
|
.av-btn--on-dark.av-btn--secondary:focus-visible {
|
||||||
|
background-color: var(--color-text-on-dark);
|
||||||
|
color: var(--color-brand-navy);
|
||||||
|
}
|
||||||
|
.av-btn--on-dark.av-btn--ghost {
|
||||||
|
color: var(--color-text-on-dark);
|
||||||
|
}
|
||||||
|
.av-btn--on-dark.av-btn--ghost:hover,
|
||||||
|
.av-btn--on-dark.av-btn--ghost:focus-visible {
|
||||||
|
background-color: var(--color-bg-dark-elevated);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Variant : destructive --- */
|
||||||
|
|
||||||
|
.av-btn--destructive {
|
||||||
|
background-color: var(--color-alert-red);
|
||||||
|
color: var(--color-text-on-dark);
|
||||||
|
border-color: var(--color-alert-red);
|
||||||
|
}
|
||||||
|
.av-btn--destructive:hover,
|
||||||
|
.av-btn--destructive:focus-visible {
|
||||||
|
filter: brightness(1.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
2. Links (Design Handoff section 3.2)
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.av-link {
|
||||||
|
color: var(--color-brand-navy);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--duration-fast) var(--ease-default);
|
||||||
|
}
|
||||||
|
.av-link:hover {
|
||||||
|
color: var(--color-brand-gold);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
text-underline-offset: 4px;
|
||||||
|
}
|
||||||
|
.av-link--on-dark {
|
||||||
|
color: var(--color-text-on-dark);
|
||||||
|
}
|
||||||
|
.av-link--on-dark:hover {
|
||||||
|
color: var(--color-brand-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
3. Forms — placeholder
|
||||||
|
To be filled from Design Handoff section 3.3 when conversion templates are built.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
4. Cards — placeholder
|
||||||
|
To be filled from Design Handoff section 3.4 when home solutions/industries
|
||||||
|
sections are built.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
5. Stat callout — placeholder
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
6. Navigation (header, mega menu, drawer) — placeholder
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
7. Footer — placeholder
|
||||||
|
============================================================================ */
|
||||||
234
wp-content/themes/asterion-bricks/assets/css/tokens.css
Normal file
234
wp-content/themes/asterion-bricks/assets/css/tokens.css
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
/**
|
||||||
|
* Asterion Bricks — Design tokens
|
||||||
|
*
|
||||||
|
* Source of truth for the design system. Every component, every utility,
|
||||||
|
* every Bricks override references variables defined here.
|
||||||
|
*
|
||||||
|
* Reference : Design Handoff Specification (livrable 2/2), section 2.
|
||||||
|
*
|
||||||
|
* Hard rule : NEVER hardcode a color, spacing, radius, or duration in any
|
||||||
|
* other file. If it's not yet a token, add it here first, then use it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Self-hosted fonts (RGPD — no Google Fonts CDN)
|
||||||
|
Drop the .woff2 files into ../fonts/ and these declarations pick them up.
|
||||||
|
See ../fonts/README.md for download instructions.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Inter";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("../fonts/Inter-Variable.woff2") format("woff2-variations"),
|
||||||
|
url("../fonts/Inter-Variable.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Inter";
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("../fonts/Inter-Variable-Italic.woff2") format("woff2-variations"),
|
||||||
|
url("../fonts/Inter-Variable-Italic.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Inter Display is intentionally NOT registered as its own @font-face here.
|
||||||
|
Since Inter 4.x, "Display" optical sizing is baked into the main variable
|
||||||
|
font via the `opsz` axis. The CSS stack `"Inter Display", "Inter", ...`
|
||||||
|
in --font-display falls through to "Inter" (registered above), so headlines
|
||||||
|
pick up the correct file with no 404 noise.
|
||||||
|
|
||||||
|
If a separate InterDisplay-Variable.woff2 is dropped into ../fonts/ later
|
||||||
|
(e.g. from the rsms.me extras package), uncomment the block below.
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Inter Display";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("../fonts/InterDisplay-Variable.woff2") format("woff2-variations"),
|
||||||
|
url("../fonts/InterDisplay-Variable.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Tokens
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
|
||||||
|
/* ---- BRAND COLORS ---- */
|
||||||
|
--color-brand-navy: #0B1F3A;
|
||||||
|
--color-brand-navy-deep: #081427;
|
||||||
|
--color-brand-gold: #C9A45A;
|
||||||
|
--color-brand-gold-soft: #E0C892;
|
||||||
|
--color-alert-red: #C8102E;
|
||||||
|
|
||||||
|
/* ---- NEUTRALS ---- */
|
||||||
|
--color-text-primary: #0B1F3A;
|
||||||
|
--color-text-secondary: #3E4C5E;
|
||||||
|
--color-text-muted: #6B7785;
|
||||||
|
--color-text-on-dark: #FFFFFF;
|
||||||
|
--color-text-on-dark-muted: #B6BFCC;
|
||||||
|
|
||||||
|
--color-bg-default: #FFFFFF;
|
||||||
|
--color-bg-subtle: #F5F7FA;
|
||||||
|
--color-bg-elevated: #FFFFFF;
|
||||||
|
--color-bg-dark: #0B1F3A;
|
||||||
|
--color-bg-dark-elevated: #13294B;
|
||||||
|
|
||||||
|
--color-border-default: #CCD3DC;
|
||||||
|
--color-border-subtle: #E5E9EE;
|
||||||
|
--color-border-on-dark: #1F3252;
|
||||||
|
|
||||||
|
/* ---- SEMANTIC COLORS ---- */
|
||||||
|
--color-success: #1F8B4C;
|
||||||
|
--color-warning: #C29327;
|
||||||
|
--color-error: #C8102E;
|
||||||
|
--color-info: #1E5BA8;
|
||||||
|
|
||||||
|
/* ---- TYPOGRAPHY: families ---- */
|
||||||
|
--font-display: "Inter Display", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
--font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
|
||||||
|
/* ---- TYPOGRAPHY: sizes (rem-based, root = 16px) ---- */
|
||||||
|
--text-overline: 0.75rem; /* 12px */
|
||||||
|
--text-body-sm: 0.875rem; /* 14px */
|
||||||
|
--text-body-md: 1rem; /* 16px */
|
||||||
|
--text-body-lg: 1.25rem; /* 20px */
|
||||||
|
--text-h4: 1.125rem; /* 18px */
|
||||||
|
--text-h3: 1.375rem; /* 22px */
|
||||||
|
--text-h2: 1.75rem; /* 28px */
|
||||||
|
--text-h1: 2.25rem; /* 36px */
|
||||||
|
--text-display-md: 2.75rem; /* 44px */
|
||||||
|
--text-display-lg: 3.5rem; /* 56px */
|
||||||
|
--text-display-xl: 4.5rem; /* 72px */
|
||||||
|
|
||||||
|
/* ---- TYPOGRAPHY: line heights (per Design Handoff section 2.2) ---- */
|
||||||
|
--leading-display-xl: 1.05;
|
||||||
|
--leading-display-lg: 1.10;
|
||||||
|
--leading-display-md: 1.15;
|
||||||
|
--leading-h1: 1.20;
|
||||||
|
--leading-h2: 1.25;
|
||||||
|
--leading-h3: 1.30;
|
||||||
|
--leading-h4: 1.35;
|
||||||
|
--leading-body-lg: 1.55;
|
||||||
|
--leading-body-md: 1.60;
|
||||||
|
--leading-body-sm: 1.50;
|
||||||
|
--leading-overline: 1.40;
|
||||||
|
|
||||||
|
/* ---- TYPOGRAPHY: weights ---- */
|
||||||
|
--font-weight-regular: 400;
|
||||||
|
--font-weight-medium: 500;
|
||||||
|
--font-weight-semibold: 600;
|
||||||
|
--font-weight-bold: 700;
|
||||||
|
|
||||||
|
/* ---- TYPOGRAPHY: tracking ---- */
|
||||||
|
--tracking-tight: -0.02em; /* display */
|
||||||
|
--tracking-snug: -0.01em; /* headings */
|
||||||
|
--tracking-normal: 0; /* body */
|
||||||
|
--tracking-overline: 0.08em; /* eyebrows, all-caps */
|
||||||
|
|
||||||
|
/* ---- SPACING (8-pt grid) ---- */
|
||||||
|
--space-0: 0;
|
||||||
|
--space-1: 0.25rem; /* 4px */
|
||||||
|
--space-2: 0.5rem; /* 8px */
|
||||||
|
--space-3: 0.75rem; /* 12px */
|
||||||
|
--space-4: 1rem; /* 16px */
|
||||||
|
--space-5: 1.25rem; /* 20px */
|
||||||
|
--space-6: 1.5rem; /* 24px */
|
||||||
|
--space-8: 2rem; /* 32px */
|
||||||
|
--space-10: 2.5rem; /* 40px */
|
||||||
|
--space-12: 3rem; /* 48px */
|
||||||
|
--space-16: 4rem; /* 64px */
|
||||||
|
--space-20: 5rem; /* 80px */
|
||||||
|
--space-24: 6rem; /* 96px */
|
||||||
|
--space-32: 8rem; /* 128px */
|
||||||
|
|
||||||
|
/* ---- LAYOUT: container & grid ---- */
|
||||||
|
--container-max: 1280px;
|
||||||
|
--container-padding-mobile: 1.5rem; /* 24px */
|
||||||
|
--container-padding-tablet: 2rem; /* 32px */
|
||||||
|
--container-padding-desktop: 4rem; /* 64px */
|
||||||
|
|
||||||
|
--gutter-mobile: 1rem; /* 16px */
|
||||||
|
--gutter-tablet: 1rem; /* 16px */
|
||||||
|
--gutter-desktop: 1.5rem; /* 24px */
|
||||||
|
|
||||||
|
/* ---- BORDER RADIUS ---- */
|
||||||
|
--radius-none: 0;
|
||||||
|
--radius-sm: 2px;
|
||||||
|
--radius-md: 4px;
|
||||||
|
--radius-lg: 8px;
|
||||||
|
--radius-pill: 9999px;
|
||||||
|
--radius-full: 50%;
|
||||||
|
|
||||||
|
/* ---- BORDER WIDTHS ---- */
|
||||||
|
--border-thin: 1px;
|
||||||
|
--border-default: 2px;
|
||||||
|
--border-thick: 3px;
|
||||||
|
|
||||||
|
/* ---- SHADOWS ---- */
|
||||||
|
--shadow-none: none;
|
||||||
|
--shadow-xs: 0 1px 2px rgba(11, 31, 58, 0.05);
|
||||||
|
--shadow-sm: 0 2px 4px rgba(11, 31, 58, 0.08), 0 1px 2px rgba(11, 31, 58, 0.06);
|
||||||
|
--shadow-md: 0 4px 12px rgba(11, 31, 58, 0.10), 0 2px 4px rgba(11, 31, 58, 0.06);
|
||||||
|
--shadow-lg: 0 12px 24px rgba(11, 31, 58, 0.14), 0 4px 8px rgba(11, 31, 58, 0.08);
|
||||||
|
--shadow-glow-gold: 0 0 24px rgba(201, 164, 90, 0.35);
|
||||||
|
|
||||||
|
/* ---- Z-INDEX SCALE ---- */
|
||||||
|
--z-base: 1;
|
||||||
|
--z-dropdown: 1000;
|
||||||
|
--z-sticky: 1100;
|
||||||
|
--z-fixed: 1200;
|
||||||
|
--z-modal-backdrop: 1300;
|
||||||
|
--z-modal: 1310;
|
||||||
|
--z-popover: 1400;
|
||||||
|
--z-toast: 1500;
|
||||||
|
|
||||||
|
/* ---- MOTION: easing ---- */
|
||||||
|
--ease-default: cubic-bezier(0.16, 1, 0.3, 1); /* premium snap */
|
||||||
|
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||||
|
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
--ease-linear: linear;
|
||||||
|
|
||||||
|
/* ---- MOTION: durations ---- */
|
||||||
|
--duration-instant: 100ms; /* micro-interactions */
|
||||||
|
--duration-fast: 150ms; /* button hover, focus */
|
||||||
|
--duration-default: 250ms; /* card hover, accordion */
|
||||||
|
--duration-slow: 400ms; /* modal, drawer */
|
||||||
|
--duration-deliberate: 600ms; /* hero animations */
|
||||||
|
|
||||||
|
/* ---- A11Y / TOUCH ---- */
|
||||||
|
--tap-target-min: 44px; /* WCAG AA + Apple HIG */
|
||||||
|
--focus-ring-width: 2px;
|
||||||
|
--focus-ring-offset: 2px;
|
||||||
|
--focus-ring-color: var(--color-info);
|
||||||
|
|
||||||
|
/* ---- BREAKPOINTS (used in @media; here for reference) ----
|
||||||
|
xs: 0 — mobile portrait (375px reference)
|
||||||
|
sm: 640px — mobile landscape, small tablets
|
||||||
|
md: 768px — tablet portrait
|
||||||
|
lg: 1024px — tablet landscape, small laptop
|
||||||
|
xl: 1280px — desktop default
|
||||||
|
2xl: 1536px — large desktop
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Reduced motion — respect user preference (Design Handoff section 5.5)
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
214
wp-content/themes/asterion-bricks/assets/css/utilities.css
Normal file
214
wp-content/themes/asterion-bricks/assets/css/utilities.css
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
/**
|
||||||
|
* Asterion Bricks — Utilities
|
||||||
|
*
|
||||||
|
* Modern CSS reset + reusable utility classes.
|
||||||
|
* Loaded after tokens.css, before components.css.
|
||||||
|
*
|
||||||
|
* Naming : prefix `av-` on every custom class.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Modern CSS reset (inspired by Andy Bell's modern-css-reset, Josh Comeau)
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
*, *::before, *::after { box-sizing: border-box; }
|
||||||
|
|
||||||
|
* { margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 100%;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: var(--text-body-md);
|
||||||
|
line-height: var(--leading-body-md);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
background-color: var(--color-bg-default);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
img, picture, video, canvas, svg {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, button, textarea, select { font: inherit; color: inherit; }
|
||||||
|
|
||||||
|
p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, ol { list-style: none; }
|
||||||
|
|
||||||
|
table { border-collapse: collapse; border-spacing: 0; }
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Heading hierarchy — opinionated defaults consumed when an element
|
||||||
|
carries no Bricks-specific class. Bricks may override, that's expected.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: var(--text-h1);
|
||||||
|
line-height: var(--leading-h1);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
letter-spacing: var(--tracking-snug);
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: var(--text-h2);
|
||||||
|
line-height: var(--leading-h2);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
letter-spacing: var(--tracking-snug);
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: var(--text-h3);
|
||||||
|
line-height: var(--leading-h3);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
letter-spacing: var(--tracking-snug);
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: var(--text-h4);
|
||||||
|
line-height: var(--leading-h4);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
max-width: 72ch; /* prose readability per Design Handoff section 2.2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Focus visible — keyboard accessibility
|
||||||
|
Removes outline only when focus came from the mouse; preserves it for keyboard.
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
:focus { outline: none; }
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
||||||
|
outline-offset: var(--focus-ring-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Skip link — first focusable element on every page (a11y)
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.av-skip-link {
|
||||||
|
position: absolute;
|
||||||
|
top: -40px;
|
||||||
|
left: 0;
|
||||||
|
padding: var(--space-3) var(--space-4);
|
||||||
|
background: var(--color-brand-navy);
|
||||||
|
color: var(--color-text-on-dark);
|
||||||
|
font-size: var(--text-body-sm);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
z-index: var(--z-toast);
|
||||||
|
border-bottom-right-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
.av-skip-link:focus,
|
||||||
|
.av-skip-link:focus-visible {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Visually hidden — content readable by screen readers, hidden visually
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.av-sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Container — centred max-width with responsive padding
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.av-container {
|
||||||
|
max-width: var(--container-max);
|
||||||
|
margin-inline: auto;
|
||||||
|
padding-inline: var(--container-padding-mobile);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.av-container { padding-inline: var(--container-padding-tablet); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.av-container { padding-inline: var(--container-padding-desktop); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.av-container--narrow {
|
||||||
|
max-width: 800px; /* article body */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Tap target minimum — WCAG AA / Apple HIG (44×44px)
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.av-tap-target {
|
||||||
|
min-width: var(--tap-target-min);
|
||||||
|
min-height: var(--tap-target-min);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Stack helpers — vertical rhythm via owl-selector
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.av-stack > * + * { margin-top: var(--av-stack-gap, var(--space-4)); }
|
||||||
|
.av-stack--sm > * + * { margin-top: var(--space-2); }
|
||||||
|
.av-stack--md > * + * { margin-top: var(--space-4); }
|
||||||
|
.av-stack--lg > * + * { margin-top: var(--space-8); }
|
||||||
|
.av-stack--xl > * + * { margin-top: var(--space-12); }
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Section padding helpers (vertical rhythm between page sections)
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
.av-section {
|
||||||
|
padding-block: var(--space-16);
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.av-section { padding-block: var(--space-20); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.av-section--hero {
|
||||||
|
padding-block: var(--space-24);
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.av-section--hero { padding-block: var(--space-32); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.av-section--dark {
|
||||||
|
background-color: var(--color-bg-dark);
|
||||||
|
color: var(--color-text-on-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
Print
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
.av-skip-link, .av-no-print { display: none !important; }
|
||||||
|
body { color: #000; background: #fff; }
|
||||||
|
a::after { content: " (" attr(href) ")"; font-size: 0.8em; }
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
39
wp-content/themes/asterion-bricks/assets/fonts/README.md
Normal file
39
wp-content/themes/asterion-bricks/assets/fonts/README.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Self-hosted Inter fonts
|
||||||
|
|
||||||
|
Self-hosted to comply with **GDPR**. Loading fonts from Google Fonts CDN sends each visitor's IP address to Google — explicitly ruled illegal by a German court in 2022 (LG München I, 20 January 2022, file no. 3 O 17493/20). For an institutional B2G site addressing French ministries and EU security forces, Google Fonts is a non-starter.
|
||||||
|
|
||||||
|
## Files expected here
|
||||||
|
|
||||||
|
| File | Purpose | Required |
|
||||||
|
|-----------------------------------|--------------------------------------------------|----------|
|
||||||
|
| `Inter-Variable.woff2` | Body + UI font (variable axis: weight 100-900) | ✅ Yes |
|
||||||
|
| `Inter-Variable-Italic.woff2` | Italic variant | ✅ Yes |
|
||||||
|
| `InterDisplay-Variable.woff2` | Optimized for display sizes (titles, hero) | 🟡 Recommended |
|
||||||
|
|
||||||
|
## Where to download
|
||||||
|
|
||||||
|
**Official source** : https://rsms.me/inter
|
||||||
|
|
||||||
|
1. Visit https://github.com/rsms/inter/releases/latest
|
||||||
|
2. Download the latest `Inter-X.Y.zip`.
|
||||||
|
3. Inside the ZIP, locate the `web/` folder.
|
||||||
|
4. Copy `Inter-Variable.woff2` and `Inter-Variable-Italic.woff2` into this directory.
|
||||||
|
5. For `InterDisplay-Variable.woff2`, look in `extras/web/` if present in the release, or use the same Inter Variable file (Inter 4+ merged Display into the optical-size axis).
|
||||||
|
|
||||||
|
**Alternative via npm** (if you already have a node toolchain) :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @fontsource-variable/inter
|
||||||
|
cp node_modules/@fontsource-variable/inter/files/inter-latin-wght-normal.woff2 ./Inter-Variable.woff2
|
||||||
|
cp node_modules/@fontsource-variable/inter/files/inter-latin-wght-italic.woff2 ./Inter-Variable-Italic.woff2
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[SIL Open Font License 1.1](https://github.com/rsms/inter/blob/master/LICENSE.txt). Free to use, modify, and redistribute, including in commercial websites. Attribution not required but appreciated.
|
||||||
|
|
||||||
|
## After dropping the WOFF2 here
|
||||||
|
|
||||||
|
Nothing else to do. The `@font-face` declarations in `../css/tokens.css` reference these filenames directly. The `<link rel="preload">` in `functions.php` (`wp_head` action) starts fetching them on page load.
|
||||||
|
|
||||||
|
If a file is missing, the page silently falls back to the system stack (`-apple-system`, `Segoe UI`, `Roboto`, etc.) without any user-visible error.
|
||||||
149
wp-content/themes/asterion-bricks/assets/js/main.js
Normal file
149
wp-content/themes/asterion-bricks/assets/js/main.js
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
/**
|
||||||
|
* Asterion Bricks — main JS
|
||||||
|
*
|
||||||
|
* Vanilla JS, defer-loaded. Wraps everything in DOMContentLoaded.
|
||||||
|
* Performance budget : initial bundle < 200 KB gzipped.
|
||||||
|
*
|
||||||
|
* Modules planned :
|
||||||
|
* - Header scroll behavior (background swap after 80px)
|
||||||
|
* - Mega menu trigger (hover desktop, click touch, ESC closes)
|
||||||
|
* - Mobile drawer (full-screen overlay, accordion sections)
|
||||||
|
* - Scroll-triggered fade-in (IntersectionObserver, threshold 0.15)
|
||||||
|
* - Stat callouts count-up (1000ms ease-out)
|
||||||
|
* - Hero video lazy-load + reduced-motion fallback to poster
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect reduced motion once and expose a flag for downstream modules.
|
||||||
|
* Updated reactively if the user toggles the setting mid-session.
|
||||||
|
*/
|
||||||
|
const reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||||
|
let prefersReducedMotion = reducedMotionQuery.matches;
|
||||||
|
reducedMotionQuery.addEventListener('change', (e) => {
|
||||||
|
prefersReducedMotion = e.matches;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header scroll behavior.
|
||||||
|
* After 80px scroll, header gets a `is-scrolled` class
|
||||||
|
* (consumed by .av-header.is-scrolled rule in components.css).
|
||||||
|
*/
|
||||||
|
function initHeaderScroll() {
|
||||||
|
const header = document.querySelector('.av-header');
|
||||||
|
if (!header) return;
|
||||||
|
|
||||||
|
let ticking = false;
|
||||||
|
const onScroll = () => {
|
||||||
|
if (!ticking) {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
header.classList.toggle('is-scrolled', window.scrollY > 80);
|
||||||
|
ticking = false;
|
||||||
|
});
|
||||||
|
ticking = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onScroll();
|
||||||
|
window.addEventListener('scroll', onScroll, { passive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll-triggered fade-in (Design Handoff section 5.3).
|
||||||
|
* Sections fade-in + slide-up 16px on entry, threshold 0.15, trigger once.
|
||||||
|
* Skipped entirely if user prefers reduced motion.
|
||||||
|
*/
|
||||||
|
function initScrollReveal() {
|
||||||
|
if (prefersReducedMotion) return;
|
||||||
|
|
||||||
|
const targets = document.querySelectorAll('[data-av-reveal]');
|
||||||
|
if (!targets.length) return;
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
entry.target.classList.add('is-revealed');
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, { threshold: 0.15, rootMargin: '0px 0px -10% 0px' });
|
||||||
|
|
||||||
|
targets.forEach((target) => observer.observe(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stat count-up (Design Handoff section 5.3).
|
||||||
|
* Element : <span data-av-countup="0|target|duration">target</span>
|
||||||
|
* Animates from 0 to target value over duration ms, ease-out.
|
||||||
|
*/
|
||||||
|
function initStatCountUp() {
|
||||||
|
if (prefersReducedMotion) return;
|
||||||
|
|
||||||
|
const stats = document.querySelectorAll('[data-av-countup]');
|
||||||
|
if (!stats.length) return;
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (!entry.isIntersecting) return;
|
||||||
|
const el = entry.target;
|
||||||
|
const target = parseFloat(el.dataset.avCountup) || 0;
|
||||||
|
const duration = parseInt(el.dataset.avCountupDuration || '1000', 10);
|
||||||
|
const start = performance.now();
|
||||||
|
|
||||||
|
const tick = (now) => {
|
||||||
|
const elapsed = Math.min((now - start) / duration, 1);
|
||||||
|
// ease-out cubic
|
||||||
|
const eased = 1 - Math.pow(1 - elapsed, 3);
|
||||||
|
el.textContent = Math.round(target * eased).toLocaleString();
|
||||||
|
if (elapsed < 1) requestAnimationFrame(tick);
|
||||||
|
};
|
||||||
|
requestAnimationFrame(tick);
|
||||||
|
observer.unobserve(el);
|
||||||
|
});
|
||||||
|
}, { threshold: 0.5 });
|
||||||
|
|
||||||
|
stats.forEach((s) => observer.observe(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mobile drawer toggle.
|
||||||
|
* Trigger : <button class="av-drawer-toggle" aria-controls="av-drawer">
|
||||||
|
* Drawer : <div id="av-drawer" class="av-drawer">
|
||||||
|
*/
|
||||||
|
function initMobileDrawer() {
|
||||||
|
const toggle = document.querySelector('.av-drawer-toggle');
|
||||||
|
const drawer = document.getElementById('av-drawer');
|
||||||
|
if (!toggle || !drawer) return;
|
||||||
|
|
||||||
|
const open = () => {
|
||||||
|
drawer.classList.add('is-open');
|
||||||
|
toggle.setAttribute('aria-expanded', 'true');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
};
|
||||||
|
const close = () => {
|
||||||
|
drawer.classList.remove('is-open');
|
||||||
|
toggle.setAttribute('aria-expanded', 'false');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
toggle.addEventListener('click', () => {
|
||||||
|
drawer.classList.contains('is-open') ? close() : open();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape' && drawer.classList.contains('is-open')) close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boot.
|
||||||
|
*/
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initHeaderScroll();
|
||||||
|
initScrollReveal();
|
||||||
|
initStatCountUp();
|
||||||
|
initMobileDrawer();
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
157
wp-content/themes/asterion-bricks/functions.php
Normal file
157
wp-content/themes/asterion-bricks/functions.php
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Asterion Bricks — child theme bootstrap
|
||||||
|
*
|
||||||
|
* Loads design tokens, utilities, components, and main JS in a deterministic
|
||||||
|
* order. Registers theme support, image sizes, and includes modular pieces
|
||||||
|
* (custom post types, SEO schemas).
|
||||||
|
*
|
||||||
|
* @package AsterionBricks
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
define('AV_THEME_VERSION', '0.1.0');
|
||||||
|
define('AV_THEME_DIR', get_stylesheet_directory());
|
||||||
|
define('AV_THEME_URI', get_stylesheet_directory_uri());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue child theme styles and scripts.
|
||||||
|
*
|
||||||
|
* Bricks parent stylesheet is loaded by the parent theme itself — no need to
|
||||||
|
* re-enqueue. We layer our tokens → utilities → components on top.
|
||||||
|
*
|
||||||
|
* Cache busting via `filemtime()` so dev changes are picked up without manual
|
||||||
|
* version bumps. In production, swap to AV_THEME_VERSION + a build step.
|
||||||
|
*/
|
||||||
|
add_action('wp_enqueue_scripts', function () {
|
||||||
|
$css_dir = AV_THEME_DIR . '/assets/css';
|
||||||
|
$css_uri = AV_THEME_URI . '/assets/css';
|
||||||
|
$js_dir = AV_THEME_DIR . '/assets/js';
|
||||||
|
$js_uri = AV_THEME_URI . '/assets/js';
|
||||||
|
|
||||||
|
if (file_exists($css_dir . '/tokens.css')) {
|
||||||
|
wp_enqueue_style(
|
||||||
|
'av-tokens',
|
||||||
|
$css_uri . '/tokens.css',
|
||||||
|
[],
|
||||||
|
filemtime($css_dir . '/tokens.css')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists($css_dir . '/utilities.css')) {
|
||||||
|
wp_enqueue_style(
|
||||||
|
'av-utilities',
|
||||||
|
$css_uri . '/utilities.css',
|
||||||
|
['av-tokens'],
|
||||||
|
filemtime($css_dir . '/utilities.css')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists($css_dir . '/components.css')) {
|
||||||
|
wp_enqueue_style(
|
||||||
|
'av-components',
|
||||||
|
$css_uri . '/components.css',
|
||||||
|
['av-tokens', 'av-utilities'],
|
||||||
|
filemtime($css_dir . '/components.css')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists($js_dir . '/main.js')) {
|
||||||
|
wp_enqueue_script(
|
||||||
|
'av-main',
|
||||||
|
$js_uri . '/main.js',
|
||||||
|
[],
|
||||||
|
filemtime($js_dir . '/main.js'),
|
||||||
|
['strategy' => 'defer', 'in_footer' => true]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, 20);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preload self-hosted Inter fonts to reduce LCP.
|
||||||
|
*
|
||||||
|
* Only emits <link rel="preload"> for fonts that actually exist on disk —
|
||||||
|
* silent during early dev when the WOFF2 files have not been dropped yet.
|
||||||
|
*/
|
||||||
|
add_action('wp_head', function () {
|
||||||
|
$fonts = [
|
||||||
|
'Inter-Variable.woff2',
|
||||||
|
// 'InterDisplay-Variable.woff2',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($fonts as $font) {
|
||||||
|
$path = AV_THEME_DIR . '/assets/fonts/' . $font;
|
||||||
|
if (file_exists($path)) {
|
||||||
|
printf(
|
||||||
|
'<link rel="preload" href="%s/assets/fonts/%s" as="font" type="font/woff2" crossorigin>' . "\n",
|
||||||
|
esc_url(AV_THEME_URI),
|
||||||
|
esc_attr($font)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modular includes.
|
||||||
|
*
|
||||||
|
* Each file is opt-in via file_exists() so partial scaffolds don't fatal.
|
||||||
|
*/
|
||||||
|
$av_includes = [
|
||||||
|
'inc/cpt.php', // Custom post types: case_study, scenario.
|
||||||
|
'inc/seo.php', // Schema.org JSON-LD overrides.
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($av_includes as $av_include) {
|
||||||
|
$path = AV_THEME_DIR . '/' . $av_include;
|
||||||
|
if (file_exists($path)) {
|
||||||
|
require_once $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($av_include, $path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme support and custom image sizes.
|
||||||
|
*
|
||||||
|
* Image sizes per Design Handoff section 8.3:
|
||||||
|
* - hero 2400×1350 (16:9)
|
||||||
|
* - card 800×600 (4:3)
|
||||||
|
* - thumb 400×300 (4:3)
|
||||||
|
*/
|
||||||
|
add_action('after_setup_theme', function () {
|
||||||
|
add_theme_support('post-thumbnails');
|
||||||
|
add_theme_support('html5', [
|
||||||
|
'search-form', 'comment-form', 'comment-list',
|
||||||
|
'gallery', 'caption', 'style', 'script',
|
||||||
|
]);
|
||||||
|
add_theme_support('responsive-embeds');
|
||||||
|
add_theme_support('align-wide');
|
||||||
|
add_theme_support('editor-styles');
|
||||||
|
|
||||||
|
add_image_size('av-hero', 2400, 1350, true);
|
||||||
|
add_image_size('av-card', 800, 600, true);
|
||||||
|
add_image_size('av-thumb', 400, 300, true);
|
||||||
|
|
||||||
|
load_child_theme_textdomain('asterion-bricks', AV_THEME_DIR . '/languages');
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the WP admin bar on frontend for non-admins (cleaner preview screenshots).
|
||||||
|
*/
|
||||||
|
add_action('after_setup_theme', function () {
|
||||||
|
if (! current_user_can('administrator')) {
|
||||||
|
show_admin_bar(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable Gutenberg block library default styles when Bricks is active —
|
||||||
|
* we own typography end-to-end through tokens.css.
|
||||||
|
*/
|
||||||
|
add_action('wp_enqueue_scripts', function () {
|
||||||
|
wp_dequeue_style('wp-block-library');
|
||||||
|
wp_dequeue_style('wp-block-library-theme');
|
||||||
|
wp_dequeue_style('global-styles');
|
||||||
|
wp_dequeue_style('classic-theme-styles');
|
||||||
|
}, 100);
|
||||||
123
wp-content/themes/asterion-bricks/inc/cpt.php
Normal file
123
wp-content/themes/asterion-bricks/inc/cpt.php
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Asterion Bricks — Custom Post Types
|
||||||
|
*
|
||||||
|
* Registers the post types unique to asterionvr.com :
|
||||||
|
*
|
||||||
|
* - case_study : long-form deployment write-ups, archived under /customers/
|
||||||
|
* used in /customers/ hub and /industries/* « relevant case »
|
||||||
|
* sections.
|
||||||
|
* - scenario : individual training scenarios (vehicle stop, urban CQB,
|
||||||
|
* riot containment...) referenced from /technology/scenarios/
|
||||||
|
* and from each /industries/* page.
|
||||||
|
*
|
||||||
|
* Blog posts stay on the native `post` type (per BRIEF section 6).
|
||||||
|
*
|
||||||
|
* @package AsterionBricks
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
add_action('init', function () {
|
||||||
|
|
||||||
|
/* -- case_study --------------------------------------------------- */
|
||||||
|
|
||||||
|
register_post_type('case_study', [
|
||||||
|
'label' => __('Case Studies', 'asterion-bricks'),
|
||||||
|
'labels' => [
|
||||||
|
'name' => __('Case Studies', 'asterion-bricks'),
|
||||||
|
'singular_name' => __('Case Study', 'asterion-bricks'),
|
||||||
|
'menu_name' => __('Case Studies', 'asterion-bricks'),
|
||||||
|
'add_new' => __('Add New', 'asterion-bricks'),
|
||||||
|
'add_new_item' => __('Add New Case Study', 'asterion-bricks'),
|
||||||
|
'edit_item' => __('Edit Case Study', 'asterion-bricks'),
|
||||||
|
'new_item' => __('New Case Study', 'asterion-bricks'),
|
||||||
|
'view_item' => __('View Case Study', 'asterion-bricks'),
|
||||||
|
'search_items' => __('Search Case Studies', 'asterion-bricks'),
|
||||||
|
'not_found' => __('No case studies found', 'asterion-bricks'),
|
||||||
|
'not_found_in_trash' => __('No case studies in trash', 'asterion-bricks'),
|
||||||
|
'featured_image' => __('Hero image', 'asterion-bricks'),
|
||||||
|
'archives' => __('Customers', 'asterion-bricks'),
|
||||||
|
],
|
||||||
|
'public' => true,
|
||||||
|
'publicly_queryable' => true,
|
||||||
|
'show_ui' => true,
|
||||||
|
'show_in_menu' => true,
|
||||||
|
'show_in_rest' => true, // Bricks + Gutenberg
|
||||||
|
'show_in_nav_menus' => true,
|
||||||
|
'menu_icon' => 'dashicons-portfolio',
|
||||||
|
'menu_position' => 22,
|
||||||
|
'capability_type' => 'post',
|
||||||
|
'has_archive' => 'customers',
|
||||||
|
'hierarchical' => false,
|
||||||
|
'rewrite' => [
|
||||||
|
'slug' => 'customers',
|
||||||
|
'with_front' => false,
|
||||||
|
'feeds' => true,
|
||||||
|
'pages' => true,
|
||||||
|
],
|
||||||
|
'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields', 'revisions'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/* -- scenario ----------------------------------------------------- */
|
||||||
|
|
||||||
|
register_post_type('scenario', [
|
||||||
|
'label' => __('Scenarios', 'asterion-bricks'),
|
||||||
|
'labels' => [
|
||||||
|
'name' => __('Scenarios', 'asterion-bricks'),
|
||||||
|
'singular_name' => __('Scenario', 'asterion-bricks'),
|
||||||
|
'menu_name' => __('Scenarios', 'asterion-bricks'),
|
||||||
|
'add_new_item' => __('Add New Scenario', 'asterion-bricks'),
|
||||||
|
'edit_item' => __('Edit Scenario', 'asterion-bricks'),
|
||||||
|
'new_item' => __('New Scenario', 'asterion-bricks'),
|
||||||
|
'view_item' => __('View Scenario', 'asterion-bricks'),
|
||||||
|
'search_items' => __('Search Scenarios', 'asterion-bricks'),
|
||||||
|
'featured_image' => __('Scenario thumbnail', 'asterion-bricks'),
|
||||||
|
],
|
||||||
|
'public' => true,
|
||||||
|
'publicly_queryable' => true,
|
||||||
|
'show_ui' => true,
|
||||||
|
'show_in_menu' => true,
|
||||||
|
'show_in_rest' => true,
|
||||||
|
'show_in_nav_menus' => true,
|
||||||
|
'menu_icon' => 'dashicons-shield',
|
||||||
|
'menu_position' => 23,
|
||||||
|
'capability_type' => 'post',
|
||||||
|
'has_archive' => 'scenarios',
|
||||||
|
'hierarchical' => false,
|
||||||
|
'rewrite' => [
|
||||||
|
'slug' => 'scenarios',
|
||||||
|
'with_front' => false,
|
||||||
|
],
|
||||||
|
'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields', 'revisions'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/* -- Taxonomy : industry (shared between case_study and scenario)
|
||||||
|
-- Lets us tag content by Police / Special Forces / Military / Firefighters
|
||||||
|
-- so the /industries/* pages can pull related items dynamically.
|
||||||
|
--------------------------------------------------------------- */
|
||||||
|
|
||||||
|
register_taxonomy('industry', ['case_study', 'scenario'], [
|
||||||
|
'label' => __('Industries', 'asterion-bricks'),
|
||||||
|
'labels' => [
|
||||||
|
'name' => __('Industries', 'asterion-bricks'),
|
||||||
|
'singular_name' => __('Industry', 'asterion-bricks'),
|
||||||
|
'add_new_item' => __('Add Industry', 'asterion-bricks'),
|
||||||
|
'all_items' => __('All Industries', 'asterion-bricks'),
|
||||||
|
],
|
||||||
|
'public' => true,
|
||||||
|
'show_in_rest' => true,
|
||||||
|
'show_admin_column' => true,
|
||||||
|
'hierarchical' => true,
|
||||||
|
'rewrite' => ['slug' => 'industry', 'with_front' => false],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush rewrite rules on theme activation so new slugs (/customers/, /scenarios/)
|
||||||
|
* resolve immediately without manual « Save permalinks » in WP admin.
|
||||||
|
*/
|
||||||
|
add_action('after_switch_theme', function () {
|
||||||
|
flush_rewrite_rules();
|
||||||
|
});
|
||||||
60
wp-content/themes/asterion-bricks/inc/seo.php
Normal file
60
wp-content/themes/asterion-bricks/inc/seo.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Asterion Bricks — SEO module
|
||||||
|
*
|
||||||
|
* Hosts custom Schema.org JSON-LD and any meta-tag overrides specific to
|
||||||
|
* Asterion VR. Most generic SEO (title tags, OG, sitemap) is delegated to
|
||||||
|
* Rank Math or Yoast (per BRIEF section 4).
|
||||||
|
*
|
||||||
|
* Planned :
|
||||||
|
* - Organization schema on home + footer-rendered pages
|
||||||
|
* - Product schema on /solutions/* pages (each tier = a Product)
|
||||||
|
* - Article schema on /insights/<slug>/
|
||||||
|
* - FAQPage schema on /trust/ and conversion templates
|
||||||
|
* - BreadcrumbList schema site-wide
|
||||||
|
*
|
||||||
|
* @package AsterionBricks
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject Organization JSON-LD on the home page.
|
||||||
|
* Safe minimum — extended once logos/SameAs URLs are confirmed.
|
||||||
|
*/
|
||||||
|
add_action('wp_head', function () {
|
||||||
|
if (! is_front_page()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$org = [
|
||||||
|
'@context' => 'https://schema.org',
|
||||||
|
'@type' => 'Organization',
|
||||||
|
'name' => 'Asterion VR',
|
||||||
|
'url' => home_url('/'),
|
||||||
|
'logo' => get_stylesheet_directory_uri() . '/assets/img/logo-asterion-vr.svg',
|
||||||
|
'foundingDate' => '2016',
|
||||||
|
'address' => [
|
||||||
|
'@type' => 'PostalAddress',
|
||||||
|
'addressLocality' => 'Montgermont',
|
||||||
|
'addressRegion' => 'Bretagne',
|
||||||
|
'addressCountry' => 'FR',
|
||||||
|
],
|
||||||
|
'sameAs' => [
|
||||||
|
// TODO : confirm public profiles with client.
|
||||||
|
// 'https://www.linkedin.com/company/asterion-vr/',
|
||||||
|
// 'https://www.youtube.com/@asterionvr',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
printf(
|
||||||
|
"<script type=\"application/ld+json\">%s</script>\n",
|
||||||
|
wp_json_encode($org, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
||||||
|
);
|
||||||
|
}, 5);
|
||||||
|
|
||||||
|
// TODO : Product schema for /solutions/<tier>/ pages.
|
||||||
|
// TODO : Article schema for /insights/<slug>/ pages.
|
||||||
|
// TODO : FAQPage schema for /trust/ and conversion pages.
|
||||||
|
// TODO : BreadcrumbList schema site-wide.
|
||||||
18
wp-content/themes/asterion-bricks/style.css
Normal file
18
wp-content/themes/asterion-bricks/style.css
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
Theme Name: Asterion Bricks
|
||||||
|
Theme URI: https://asterionvr.com
|
||||||
|
Template: bricks
|
||||||
|
Author: Asterion VR
|
||||||
|
Author URI: https://asterionvr.com
|
||||||
|
Description: Custom child theme for asterionvr.com — built on Bricks Builder. Tactical-cinematic design system for the PROSERVE XR training platform marketed to European security forces (police, special forces, military, firefighters). Token-driven, RGPD-native, mobile-first, WCAG 2.1 AA.
|
||||||
|
Version: 0.1.0
|
||||||
|
Requires at least: 6.0
|
||||||
|
Tested up to: 6.5
|
||||||
|
Requires PHP: 8.0
|
||||||
|
License: GPL v2 or later
|
||||||
|
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||||
|
Text Domain: asterion-bricks
|
||||||
|
Tags: bricks, child-theme, custom
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* CSS lives in /assets/css/. This file exists only for the WordPress theme metadata above. */
|
||||||
79
wp-content/themes/asterion-bricks/theme.json
Normal file
79
wp-content/themes/asterion-bricks/theme.json
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schemas.wp.org/trunk/theme.json",
|
||||||
|
"version": 2,
|
||||||
|
"settings": {
|
||||||
|
"appearanceTools": true,
|
||||||
|
"color": {
|
||||||
|
"palette": [
|
||||||
|
{ "slug": "brand-navy", "color": "#0B1F3A", "name": "Brand Navy" },
|
||||||
|
{ "slug": "brand-navy-deep", "color": "#081427", "name": "Brand Navy Deep" },
|
||||||
|
{ "slug": "brand-gold", "color": "#C9A45A", "name": "Brand Gold" },
|
||||||
|
{ "slug": "brand-gold-soft", "color": "#E0C892", "name": "Brand Gold Soft" },
|
||||||
|
{ "slug": "alert-red", "color": "#C8102E", "name": "Alert Red" },
|
||||||
|
{ "slug": "text-primary", "color": "#0B1F3A", "name": "Text Primary" },
|
||||||
|
{ "slug": "text-secondary", "color": "#3E4C5E", "name": "Text Secondary" },
|
||||||
|
{ "slug": "text-muted", "color": "#6B7785", "name": "Text Muted" },
|
||||||
|
{ "slug": "text-on-dark", "color": "#FFFFFF", "name": "Text on Dark" },
|
||||||
|
{ "slug": "bg-default", "color": "#FFFFFF", "name": "Background Default" },
|
||||||
|
{ "slug": "bg-subtle", "color": "#F5F7FA", "name": "Background Subtle" },
|
||||||
|
{ "slug": "bg-dark", "color": "#0B1F3A", "name": "Background Dark" },
|
||||||
|
{ "slug": "bg-dark-elevated", "color": "#13294B", "name": "Background Dark Elevated" }
|
||||||
|
],
|
||||||
|
"custom": false,
|
||||||
|
"customGradient": false,
|
||||||
|
"defaultGradients": false,
|
||||||
|
"defaultPalette": false
|
||||||
|
},
|
||||||
|
"typography": {
|
||||||
|
"fontFamilies": [
|
||||||
|
{
|
||||||
|
"slug": "display",
|
||||||
|
"name": "Inter Display",
|
||||||
|
"fontFamily": "\"Inter Display\", \"Inter\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "body",
|
||||||
|
"name": "Inter",
|
||||||
|
"fontFamily": "\"Inter\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fontSizes": [
|
||||||
|
{ "slug": "overline", "size": "0.75rem", "name": "Overline" },
|
||||||
|
{ "slug": "body-sm", "size": "0.875rem", "name": "Body small" },
|
||||||
|
{ "slug": "body-md", "size": "1rem", "name": "Body medium" },
|
||||||
|
{ "slug": "body-lg", "size": "1.25rem", "name": "Body large" },
|
||||||
|
{ "slug": "h4", "size": "1.125rem", "name": "Heading 4" },
|
||||||
|
{ "slug": "h3", "size": "1.375rem", "name": "Heading 3" },
|
||||||
|
{ "slug": "h2", "size": "1.75rem", "name": "Heading 2" },
|
||||||
|
{ "slug": "h1", "size": "2.25rem", "name": "Heading 1" },
|
||||||
|
{ "slug": "display-md", "size": "2.75rem", "name": "Display medium" },
|
||||||
|
{ "slug": "display-lg", "size": "3.5rem", "name": "Display large" },
|
||||||
|
{ "slug": "display-xl", "size": "4.5rem", "name": "Display XL" }
|
||||||
|
],
|
||||||
|
"customFontSize": false,
|
||||||
|
"fontStyle": true,
|
||||||
|
"fontWeight": true,
|
||||||
|
"lineHeight": true,
|
||||||
|
"letterSpacing": true,
|
||||||
|
"textTransform": true
|
||||||
|
},
|
||||||
|
"spacing": {
|
||||||
|
"units": ["px", "rem", "%", "vh", "vw"]
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"contentSize": "1280px",
|
||||||
|
"wideSize": "1280px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styles": {
|
||||||
|
"color": {
|
||||||
|
"background": "var:preset|color|bg-default",
|
||||||
|
"text": "var:preset|color|text-primary"
|
||||||
|
},
|
||||||
|
"typography": {
|
||||||
|
"fontFamily": "var:preset|font-family|body",
|
||||||
|
"fontSize": "var:preset|font-size|body-md",
|
||||||
|
"lineHeight": "1.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user