
Technical Analysis
Headless Approach with Astro
Just like kahoot360.com, this site uses Astro as a static site generator with WordPress as a headless CMS. At build time, Astro fetches content from the WordPress REST API and renders it to static HTML. A single data-access layer in src/lib/wordpress.ts handles all fetching, so components never know or care where the data comes from.
To structure the content, I registered three custom post types: card for the homepage cards, section for the sections on this page, and list_item for the bullet points within each section. The list_item type is hierarchical — each item’s post_parent links it to its section, so the template knows which bullets belong where. All three support menu_order, so editors can drag-and-drop to reorder content without touching code.
For structured fields beyond title and content — like a card’s pill label, image URL, and alt text — I used Advanced Custom Fields (ACF). Each content type has its own field group, and every field has show_in_rest enabled so it appears in the REST API response under the acf key.
// src/lib/wordpress.ts
const USE_MOCK = false;
const WP_BASE_URL = "https://your-wp-site.com/wp-json/wp/v2";
export async function getCards(locale: string) {
if (USE_MOCK) {
return getMockData("cards", locale);
}
// Custom post type endpoint — orderby=menu_order
// lets editors drag-and-drop to reorder.
const res = await fetch(
`/card?orderby=menu_order&order=asc&lang=&_embed`
);
return (await res.json()) ?? [];
}Build-time fetching: Astro calls the WordPress REST API during the build process
Mock data layer: local JSON files shaped exactly like real API responses
Single swap point: one constant in
wordpress.tsswitches from mock to live
BEM Conventions
kahoot360.com uses structured BEM class names like page-header and page-header--microsite. I followed the same approach throughout this site — every component uses Block Element Modifier naming. The navbar is navbar with navbar__logo, navbar__links, and navbar__link. The card is card with card__content, card__title, card__pill, and card__illustration. No utility classes, no framework — just semantic, component-oriented CSS that describes what something is, not how it looks.
<!-- Card component -->
<div class="card">
<div class="card__content">
<div class="card__illustration">...</div>
<div class="card__pill">...</div>
<h3 class="card__title">...</h3>
<p class="card__text">...</p>
</div>
</div>navbar/navbar__logo/navbar__links/navbar__linkcard/card__content/card__title/card__pill/card__illustrationhero/hero__title/hero__subtitle/hero__content/hero__ctafooter/footer__text
Component Reusability
On kahoot360.com, the --microsite modifier shows how one base component adapts to different contexts. I took the same approach with Card.astro — it accepts props for title, text, pill, image, and variant, so the same component renders differently depending on what you pass in. The Hero component works the same way: it optionally shows an illustration, a pill, or content depending on the props. One component, many contexts, less duplicated code — exactly what the job description asks for.
<!-- Same Card component, different content --> <Card pill="CSS" title="BEM Conventions" text="Structured BEM class names..." image="/illustrations/Controller.png" imageAlt="Controller" /> <Card pill="Architecture" title="Headless Approach" text="Astro fetches from WordPress..." image="/illustrations/Classroom.png" imageAlt="Classroom" />
Card.astro— accepts title, text, pill, image, imageAlt, and variant propsHero.astro— optionally renders illustration, pill, and contentButton.astro— variant prop switches between primary and secondary styles