Changelog
foulfoxhacks.dev: the first release
3 min read
Sammy's developer site launches as a bento-grid portfolio built with Astro, Svelte and Tailwind CSS on a Cloudflare Worker, with an About page, projects, blog, changelog, a recently played ticker from Spotify, and the creator site's themes and display settings.
Sammy asked for a developer site on a new domain that blends into akasammythepuppy.me, built on a bento layout with Svelte, Tailwind CSS and Astro and the same themes and information as the creator site.
What it does
- Home is a bento grid: introduction, live Discord presence, the visitor’s time and Sammy’s, the five reviewed projects, core skills with the evidence for each, How I build, the Keep Android Open countdown, the latest changelog entries, credentials, work history and contact.
- About gathers the developer side of Sammy’s story in Sammy’s own words, every public repository including the ones the creator site does not list yet, the web platforms behind Sammy’s work, the languages in the public code, the full work history, credentials and education.
- Projects lists every reviewed project with its features, known limits, source, release and the full write-up on the creator site.
- Blog and Changelog are new sections with their own feed at
/rss.xml. - Recently played runs along the top of every page: the tracks Sammy last played on Spotify, and what is playing now. It pauses, closes for 30 days, and holds still for reduced motion.
- The same three themes and Display settings as the creator site: dark, light, high contrast, text size, spacing, a readable font, underlined links and reduced motion.
Under the hood
- Nothing the creator site owns is copied. Project records, profile links, the Discord presence
logic and the shared images are read from
astro/at build time; the public repository list comes fromsrc/data/github-repos.json, refreshed bynpm run sync:githubwith apolledAtdate. - Every page has one H1, a self-canonical, complete robots, Open Graph and Twitter metadata, and
a JSON-LD graph with breadcrumbs that names the same Person as the creator site, so search
engines read both sites as one person. The sitemap carries a last-modified date for each route,
and
llms.txtandrss.xmlare published beside it. - Project screenshots are served as AVIF and WebP at the width each layout needs. Hashed assets are cached for a year; prefetching starts when a link is hovered.
- The Worker answers
dev.akasammythepuppy.meandwww.foulfoxhacks.devwith a permanent redirect, adds HSTS and the other security headers, and serves recently played tracks from Spotify at/api/spotify/recent, cached for two minutes. Its Spotify credentials are Worker secrets and never reach the browser. - Astro emits a hashed Content Security Policy on every page. The only outside origins are Lanyard, Discord’s avatar CDN and Spotify’s image CDN.
robots.txtis the creator site’s crawler policy, read fromastro/public/robots.txtat build time with only the host and sitemap changed, so both sites always publish the same Content-Signal and the same rules for search, preview and training crawlers.- Two workflows:
Validate foulfoxhacks.devtests and builds the site on every change to it or to the shared inputs inastro/;Update foulfoxhacks.dev repository listrefreshes the public repositories weekly and fails when the list is more than ten days old.
Verification
npm run test:worker(11 checks) covers the redirects, headers, cache rules and the Spotify endpoint against mocked responses;npm run test:built(39 checks) covers every built page for one H1, its canonical, robots, Open Graph and JSON-LD, working internal links and anchors, sitemap coverage, and the crawler policy.- Every page was checked in Chrome at 1280 and 375 pixels wide in all three themes, with no console or CSP errors and no horizontal scrolling, and the ticker with sample tracks.
Found and fixed along the way:
wrangler devrefused to start the first Worker, which exported its helpers by name; the runtime reads every named export as an entrypoint. The helpers moved to their own module, and a test keeps the entry to its one default export.test:builtcaught three search descriptions outside 50–160 characters. Entries gained an optionaldescription, which the schema requires when the summary is too long for a result.- The ticker answered “not connected” with a 503, which every visitor’s console would have shown as an error. Not being connected is a setting, so it is now a 200.
- With real tracks the ticker never started scrolling: it measured half the track while the second, looping copy was hidden, and so always undercounted. It now measures one copy.