20 January 2025
Overview
Features
- Live catalogue: products and categories load from Dark's dashboard API, so stock and prices can be changed from a browser without touching the site's code.
- Category tabs with icons and descriptions, each fading in its own grid of product cards.
- Stock badges (in stock, on hold, coming soon) and colour-coded tags.
- Three ways to buy: a secure embedded checkout, an external purchase link, or opening a Discord ticket for custom orders.
- Shop status banner (open, delayed, holiday, restocking, paused), set from the dashboard with a note and a "back on" date.
- Live reviews pulled straight from the community's Discord reviews channel, with an average rating and a scrolling carousel.
- FAQ, scroll-spy navigation, animated stats and reveal-on-scroll effects.



How it's built
- Front end: hand-written HTML, CSS and JavaScript with a little jQuery. No framework and no build step.
- Catalogue and reviews: fetched at runtime from Dark's dashboard API, which only accepts requests from the shop's own address.
- Checkout: a payment provider's embedded checkout, so card details never touch the site.
- Product art: generated from an HTML template, screenshotted with a headless browser and converted to small WebP files, so every product image shares one consistent style.
Engineering highlights
Treating the catalogue as untrusted
textContent rather than injecting HTML, and each field is checked against a strict pattern before it's used:
JavaScript
function button(item) {
if (item.button === "discord") return ticketLink();
// only a numeric product ID goes to the embedded checkout
if (item.button === "checkout" && /^\d+$/.test(item.product)) {
return checkoutButton(item.product);
}
// external links must be https
if (item.button === "checkout" && /^https:\/\//.test(item.product)) {
return externalLink(item.product);
}
return disabled("Soon"); // anything unexpected is shown as unavailable
}Failing gracefully
JavaScript
fetch(CATALOGUE_URL)
.then((res) => (res.ok ? res.json() : Promise.reject(res.status)))
.then(renderShop)
.catch(() => showMessage("Shop's restocking, check the Discord."));Performance, SEO and accessibility
- Fast loading: responsive WebP images with explicit sizes (no layout shift), lazy-loaded product and review images, product art around 15 KB each, and long-lived caching for fonts and assets.
- Security headers: framing blocked, content sniffing disabled, a strict referrer policy, and integrity checks on third-party scripts.
- SEO and sharing: canonical URLs, Open Graph and Twitter cards with a custom preview image for every page.
- Accessibility: keyboard-operable tabs and FAQ, proper ARIA states and labels, and reduced-motion support for animations.

