Project Structure
8-BitQuest has one organising idea, and once you see it the rest of the tree explains itself: a page is a thin shell that owns its layout and its SEO, and composes sections; a section owns markup and layout; a card knows about a data shape; a primitive knows nothing at all. Every file in src/components/ sits at exactly one of those levels, and the folder it lives in tells you which.
The second idea is that content and copy live outside components. Editorial text is in src/config/, long-form content is in src/data/, and design values are CSS custom properties in src/styles/. A component that hard-codes a headline is a component you have to open again later.
The tree
src/
├── actions/index.ts # the contact action — the only server code path
├── assets/images/ # optimizable media: avatars, and demo/ sample content images
├── components/
│ ├── Sections/<Page>/<Name>.astro # layout-free page sections; Global/ for shared ones
│ ├── Cards/<Name>.astro # content-aware compositions (ContentCard, PixelCardLink)
│ ├── ui/<name>/<Name>.astro # 39 UI primitives + 7 shared internal modules
│ └── svg/ # the <Icon> registry and the <PixelIcon> sibling set
├── config/ # typed site config — the copy layer
├── content.config.ts # Zod schemas for the three collections
├── data/<collection>/<slug>/ # content entries and their own images
├── js/ # blog/project data + card mapping, schema, rss, contact, small helpers
├── layouts/ # BaseHead (the <head>), BaseLayout (the shell)
├── pages/ # routes
└── styles/ # global.css (entry), tailwind-theme.css, fonts.css, motion/
The component tiers
This is the part worth internalising, because it answers “where do I put this?” for almost everything you will build.
A route in src/pages/ owns route concerns and nothing else: it imports BaseLayout, computes a unique title and description, optionally sets noindex, schema or article, and composes sections. It holds no markup of its own. src/pages/index.astro, for example, imports siteData and portfolioData for its meta tags and then renders seven Sections/Home/* blocks in order.
A section in src/components/Sections/ is a layout-free block of page content — a hero, a stats strip, a legal article. It owns its own rhythm, its own responsive behaviour and its own motion timing. It never imports BaseLayout. Sections live in a folder named after the page that renders them (Home/, About/, Contact/, Blog/, Project/, Legal/, NotFound/, UiCatalog/), or in Global/ once a second page uses them. The rule for promotion is literal: a section used by two or more pages moves to Global/, and not before. That is how SectionHeading, CardGrid and Scoreboard ended up in Global/ alongside Header and Footer.
A card in src/components/Cards/ is a content-aware composition that knows a data shape. There are two: ContentCard draws the shared post/project card (badge, pixel-face title, excerpt, tag pills, a “READ →” or “VIEW →” CTA), and PixelCardLink is the hover-lift <a> shell it sits inside. Both are built on the ui/pixel-panel primitive rather than ui/card, because they need <a>/<article> semantics. Sections map over cards; cards own their insides only.
A primitive in src/components/ui/ knows nothing about your data. It is a Button, a Dialog, a PixelPanel. There are 39 of them and they follow a strict five-rule contract, covered on the UI Components page.
There is a fifth, informal tier: a sub-part, a sibling file inside a section’s folder for something independently swappable. NotFound/NotFoundIllustration.astro is one — replace the single file to change the 404 artwork. Pure logic gets the same treatment: Home/PixelChip.astro sits beside the home sections that use it, and the small tested helpers live in src/js/ with their .test.ts next to them.
How data reaches a section
A section gets its content one of two ways, and never both for the same value. Either the route passes typed props down, or the section imports from src/config/ itself.
The props-in style is for anything derived from content or split by the route. src/pages/privacy.astro and terms.astro each pick their legalData record, use it for the SEO tags, and pass it down as a typed prop to the one shared Sections/Legal/LegalArticle.astro — so one section serves both documents and nothing is read twice.
The config-reading style is for a section’s own framing copy. Sections/Home/Hero.astro imports portfolioData.home directly, because nothing else on the page needs those strings and threading them through the route would only add a hop.
The rule against doing both matters more than it looks. If a route reads a copy record for its meta description and the section reads it again for its heading, you have two readers of one fact and no compiler to keep them honest. Pick one per datum.
Path aliases
Deep relative imports are the thing this tree makes easy to get wrong, so tsconfig.json declares seven aliases. Prefer them everywhere:
{
"@config/*": ["./src/config/*"],
"@js/*": ["./src/js/*"],
"@layouts/*": ["./src/layouts/*"],
"@components/*": ["./src/components/*"],
"@assets/*": ["./src/assets/*"],
"@images/*": ["./src/assets/images/*"],
"@/*": ["./src/*"]
}
There is no baseUrl, deliberately. Since TypeScript 4.1 paths resolve relative to the config file’s own directory, and baseUrl is deprecated — removed in TypeScript 7. The values are prefixed ./ to stay explicitly relative.
ESLint’s simple-import-sort orders imports for you, side-effect imports first and then alphabetised, so run pnpm format rather than hand-sorting.
One naming quirk worth knowing
The Sections/ subfolder is named after the page a route renders, not the URL segment. That is why you will find both Blog/ and Project/ — Blog/ holds the sections of the blog index and article (BlogHero, BlogArticle, RelatedPosts), Project/ holds the sections of the project index and detail (ProjectsHero, ProjectArticle). Two designs, two folders.
Files you will edit, and files you probably won’t
On a normal project you will spend nearly all your time in four places: src/config/ for copy and settings, src/data/ for content, src/styles/tailwind-theme.css and global.css for the palette and type, and src/components/Sections/ for layout changes.
You will occasionally touch src/pages/ — adding a route, changing a page title — and src/components/Cards/ if a card needs a new field.
You will rarely need to open src/components/ui/ (the primitives are done), src/js/ (small, tested helpers), src/layouts/ (the head and the shell), or src/styles/motion/ (an owned animation catalog). If you find yourself editing a primitive to change how it looks on one page, the primitive contract has an answer that does not involve editing it — pass a class, and tailwind-merge resolves the conflict in your favour.
What to delete before launch
Two things in the tree are for building the theme, not for shipping your site:
src/components/Sections/UiCatalog/andsrc/pages/examples/are the development-only primitive showroom. The route emits no pages in a production build, but Tailwind still scans the catalog’s markup, so its demo classes sit in the stylesheet every real page loads. Deleting both directories takes the shared CSS from roughly 76 KB to 57 KB and drops around 70 unused@keyframes. Keep them while you are still choosing components; the cost is CSS, not JavaScript, and it is by far the fastest way to see all 39 primitives at once.tasks/holds the theme’s own build and handoff prompts, not your project’s work. Delete the folder. Keepwiki/and.claude/if you want the documented, Claude-navigable workflow they describe.