diff --git a/wp-content/themes/asterion-bricks/README.md b/wp-content/themes/asterion-bricks/README.md new file mode 100644 index 0000000..18808fd --- /dev/null +++ b/wp-content/themes/asterion-bricks/README.md @@ -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. diff --git a/wp-content/themes/asterion-bricks/assets/css/components.css b/wp-content/themes/asterion-bricks/assets/css/components.css new file mode 100644 index 0000000..004b994 --- /dev/null +++ b/wp-content/themes/asterion-bricks/assets/css/components.css @@ -0,0 +1,233 @@ +/** + * Asterion Bricks — Components + * + * Component-level styles, layered on top of tokens + utilities. + * Loaded last in the cascade. + * + * Naming : `.av-` base, `.av---`, + * `.av-__`, `.av-.is-`. + */ + +/* ============================================================================ + 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 + ============================================================================ */ diff --git a/wp-content/themes/asterion-bricks/assets/css/tokens.css b/wp-content/themes/asterion-bricks/assets/css/tokens.css new file mode 100644 index 0000000..6a75164 --- /dev/null +++ b/wp-content/themes/asterion-bricks/assets/css/tokens.css @@ -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; + } +} diff --git a/wp-content/themes/asterion-bricks/assets/css/utilities.css b/wp-content/themes/asterion-bricks/assets/css/utilities.css new file mode 100644 index 0000000..dfe7591 --- /dev/null +++ b/wp-content/themes/asterion-bricks/assets/css/utilities.css @@ -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; } +} diff --git a/wp-content/themes/asterion-bricks/assets/fonts/Inter-Variable-Italic.woff2 b/wp-content/themes/asterion-bricks/assets/fonts/Inter-Variable-Italic.woff2 new file mode 100644 index 0000000..2f06c65 Binary files /dev/null and b/wp-content/themes/asterion-bricks/assets/fonts/Inter-Variable-Italic.woff2 differ diff --git a/wp-content/themes/asterion-bricks/assets/fonts/Inter-Variable.woff2 b/wp-content/themes/asterion-bricks/assets/fonts/Inter-Variable.woff2 new file mode 100644 index 0000000..d15208d Binary files /dev/null and b/wp-content/themes/asterion-bricks/assets/fonts/Inter-Variable.woff2 differ diff --git a/wp-content/themes/asterion-bricks/assets/fonts/README.md b/wp-content/themes/asterion-bricks/assets/fonts/README.md new file mode 100644 index 0000000..b7324ab --- /dev/null +++ b/wp-content/themes/asterion-bricks/assets/fonts/README.md @@ -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 `` 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. diff --git a/wp-content/themes/asterion-bricks/assets/img/.gitkeep b/wp-content/themes/asterion-bricks/assets/img/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/wp-content/themes/asterion-bricks/assets/js/main.js b/wp-content/themes/asterion-bricks/assets/js/main.js new file mode 100644 index 0000000..1482fd0 --- /dev/null +++ b/wp-content/themes/asterion-bricks/assets/js/main.js @@ -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 : target + * 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 :