Configuration
8-BitQuest has no CMS and no integration options object. Its configuration is five TypeScript modules in src/config/, imported directly by the pages and sections that need them. They are typed by one shared file, src/config/types/configDataTypes.ts, so a missing field is a build error rather than an empty string in production.
They are named *.json.ts but imported without the extension:
import siteData from "@config/siteData.json";
The @config/* alias resolves to ./src/config/*, and TypeScript appends .ts. They are modules, not JSON — which is exactly why they can carry comments, derive one field from another, and be checked against an interface.
siteData.json.ts — who the site is
The identity layer: brand name, page title, description, the author block, the default social image, and the sameAs list. BaseHead, the footer and the JSON-LD builders all read from it.
const siteData = {
name: "8-BitQuest",
title: "8-BitQuest — retro pixel-art dev portfolio",
description: "A retro 8-bit, pixel-art developer portfolio built on Astro 7 …",
author: {
name: "Your Name",
email: "[email protected]",
twitter: "yourhandle",
},
defaultImage: { src: "/og.jpg", alt: "8-BitQuest" },
sameAs: [],
} satisfies SiteDataProps;
Four things about this file will save you time later.
author.twitteris the handle without the@.BaseHeadrenders it into the Twitter card attribution, so drop the leading@.defaultImage.srcis apublic/path, not a bundled import. It is the fallback OG image for any page that does not set its own. Replacepublic/og.jpgwith a real 1200×630 image before launch.sameAsfeeds the Organization JSON-LD. It is the list of social/profile URLs that disambiguate your brand for search engines —["https://x.com/yourhandle", "https://github.com/yourorg"]. Empty is fine; the array simply drops out of the structured data.- The file uses
satisfies SiteDataProps, which checks the shape while keeping the literal types, so autocomplete onsiteData.author.namestays precise.
portfolioData.json.ts — the copy a buyer makes their own
This is the file that turns the sample “Full-Stack Dev” persona into you. It holds only the facts a buyer is expected to customise — identity, biography, the experience and scoreboard numbers, the home intro, and the contact prompt — in one typed place. Presentational labels (the SYS_SPECS captions, “Role:” and “Yrs:”, the scoreboard colours) stay in their components, because they are design, not content.
const portfolioData = {
profile: {
tagline: "Dev 01", // About-hero badge
heading: "The Full-Stack Dev", // About-hero H1
role: "Full-Stack", // DevProfile ROLE value
years: "8+", // DevProfile YRS value
bio: ["Welcome to the mainframe. …", "…"], // one <p> per entry
shortBio: "…", // home About-section bio
meta: { location: "…", role: "…", favorite: "…" },
skills: [ { label: "Frontend", pct: 95 }, { label: "Backend", pct: 90 } ],
},
stats: {
home: ["Posts: 42", "Years: 03", "Coffee: 9000+"],
profile: ["Class: Full-Stack Dev", "Lvl: 8+", "XP: 8.5K", "Stars: 2.1K"],
},
home: { tagline: "Player 1", heading: "Welcome, Player One", intro: "…" },
contact: { prompt: "Want to chat about a project …" },
} satisfies PortfolioDataProps;
Two conventions to keep in mind. The skills bars render as HP-style meters, so pct is a 0–100 number that becomes the fill width. And the two stats arrays are display text only — each strip’s per-stat colour is defined in its component and pairs by array order, so keep the count and order in step with what the component expects. The voice is first-person singular throughout; keep it consistent if you rewrite.
siteSettings.json.ts — how the site behaves
Three named exports, no default export:
export const siteLang = "en" as const;
export const siteLocale = "en-US" as const;
export const siteSettings = {
useViewTransitions: true,
useAnimations: true,
} satisfies SiteSettingsProps;
siteLang becomes <html lang>. siteLocale is the BCP-47 tag used for Intl date formatting, og:locale and the JSON-LD inLanguage.
The two switches:
useViewTransitionsgates<ClientRouter />. Off means no client-side navigation and no page-transition curtain. It pairs withvite.build.assetsInlineLimit: 0inastro.config.mjs, which stops short scripts being inlined so they don’t break under the router.useAnimationsis the master switch for the decorative motion layer — the scroll reveals via<Reveal>, ambient loops. It does not touch intentional micro-interactions like a rotating accordion chevron, and it has nothing to do with accessibility:prefers-reduced-motionis honoured by a global CSS guard whatever this flag says. See Motion.
This is a single-language theme. The siteLang/siteLocale constants are the only locale facts in the project — the former i18n helper layer was removed, and its shape is recorded in the theme’s wiki/ if a project ever needs it back.
navData.json.ts — the header links
export const navItems = [
{ label: "About", href: "/about/" },
{ label: "Projects", href: "/projects/" },
{ label: "Blog", href: "/blog/" },
{ label: "Contact", href: "/contact/" },
] as const satisfies readonly NavItemProps[];
Four links, Title-case in the data and uppercased in the UI by the Press Start 2P face. A nav item is routing and label only. The trailing slashes are load-bearing: the site is configured trailingSlash: "always", and the header decides which link is current by comparing this href against the pathname (@js/nav’s isActive). Home is intentionally not a nav item — the brand wordmark in the header is the home link, the common logo-as-home pattern.
legalData.json.ts — terms and privacy
One record per document, both required by the type, keyed terms and privacy:
{
title: string;
description: string; // the page's meta description
lastUpdated: string; // ISO date, YYYY-MM-DD
intro: string;
sections: { heading: string; body: string[] }[];
}
Each body entry becomes one <p>. Both documents ship with placeholder sections and an explicit warning at the top of the file: this is template text, replace it with your own, reviewed by a qualified legal professional. The renderer (Sections/Legal/LegalArticle.astro) is deliberately simple — heading plus paragraphs, no nested lists or tables. If your legal documents need richer structure, promote legalData to a markdown content collection and render <Content /> in the section.
What is not in the config layer
Colours, type and spacing are not here. They are CSS custom properties in src/styles/tailwind-theme.css and src/styles/global.css — see Colors and Typography. Putting design tokens behind a JavaScript config would mean a build step between you and a colour change, for no gain.
Long-form content is not here either. Blog posts, projects and the author profile are content-collection entries under src/data/ — see Content Collections.
Verifying
Type errors in this layer surface with pnpm check, and everything is caught by the time you reach pnpm build. Config is not a trust boundary here — it is code you wrote — so Zod validation is reserved for data arriving from outside the type system: MDX frontmatter and contact-form input.