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:
2026-05-09 11:49:10 +02:00
parent f9192631ff
commit 6f62082dc4
15 changed files with 1359 additions and 0 deletions

View 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();
});
})();