A free, open-source Astro theme for a single-page project-curation site — a grid of games, apps, and projects you've made, each linking out to where it actually lives.
Everything you'd normally touch — content, columns, header links, visual
style — lives in one file: kansquares.config.json. No component edits
needed for normal use.
npm create astro@latest -- --template bilarikan/astro-theme-kansquares
cd your-project-name
npm install
npm run devThen open kansquares.config.json and replace the example content with your
own. npm run build produces a static site in dist/, deployable anywhere
that serves static files (see Deploying below).
The config is validated at build time (via Zod) — a typo or missing required field fails the build with a specific, readable error instead of shipping a broken page.
- A new item: add an object to an existing section's
itemsarray. - A new section: add an object to the top-level
sectionsarray with its ownid,label, anditems. - Cover images: drop the file in
public/covers/and reference it by path (e.g./covers/your-image.png). Any static image format works; there's no build-time image processing, so pre-size/compress images yourself before adding them — a 1.9:1 aspect ratio (matching the built-in example covers) looks best in every preset except Squares, which is 1:1.
Set theme.preset to any of these. All eight ship in every install, all get
automatic light/dark variants from prefers-color-scheme (no manual toggle),
and all read the same config — switching preset is a one-line change.
| Preset | Look |
|---|---|
tray (default) |
Warm paper palette, heading + description shown under each card. |
poster |
Huge centered wordmark, image-only cards, nothing captioned underneath. |
brutalist |
High-contrast, monospace, hard edges, thick borders, inverts on hover. |
squares |
Literal 1:1 square cards in a tighter grid, instead of wide rectangles. |
zine |
Cut-and-paste scrapbook feel — tilted, tape-bordered cards on newsprint. |
editorial |
Structurally different from the rest: a magazine-style list (thumbnail, serif title, one line) instead of a tile grid. |
terminal |
Phosphor green on black, window-chrome dots, dashed borders. Deliberately has no light mode. |
blueprint |
Drafting-sheet blue, monospace labels, dashed sheet edge. |
Only poster (image-only) and editorial (list instead of grid) change the
actual markup; the rest are pure CSS swaps of the same components — see
src/styles/presets.css.
npm run build outputs a fully static site to dist/. A few notes if you're
deploying to shared/Apache-style hosting (e.g. Namecheap + cPanel), rather
than a git-integrated host like Netlify/Vercel/Cloudflare Pages:
- Upload the contents of
dist/intopublic_html/(or a subfolder, if the site isn't living at the domain root) — not thedistfolder itself.public_html/index.htmlshould exist afterward. - Astro emits
404.htmlat the build root rather than wiring up a server-level 404 route. Add this to your.htaccess:ErrorDocument 404 /404.html
For Netlify/Vercel/Cloudflare Pages: point the build command at npm run build and the output directory at dist — no extra config needed.
kansquares.config.jsonis read and validated once, insrc/lib/config.ts. Every component imports the typed, already-validated config from there — nothing else touches the raw JSON.src/lib/themes.tsis the single source of truth for which presets exist (themeNames) and their structural quirks (themeMeta— currently just "does this preset use a list layout" and "does this preset hide all text and rely on the image alone"). Adding a ninth preset means adding it here and adding a matching[data-preset="..."]block tosrc/styles/presets.css— nothing else needs to change.- Colors, fonts, radii and borders are all CSS custom properties
(
--kq-*, defined insrc/styles/tokens.css) thatpresets.cssoverrides per preset. Components never hardcode a color — they only ever read a--kq-*variable — which is what lets one set of markup produce eight very different looks. - The one genuine layout fork is Editorial's row list vs. everyone else's
tile grid, handled in
Section.astro/Card.astroviathemeMeta[preset].listLayout.
MIT — see LICENSE. Fork it, rename it, ship it.

{ "site": { "title": "Your Name", "description": "A collection of games, tools, and other fun things.", "url": "https://example.com", "ogImage": "/covers/example-three.svg", // optional "favicon": "/favicon.svg", // optional, defaults to this "footer": "Built with <a href=\"...\">Kansquares</a>." // optional, raw HTML; "" removes it }, "nav": [ // any number of these, in any order — this is your header/top links row { "label": "Blog", "url": "https://example.com/blog" } ], "theme": { "preset": "tray", // see Presets below "columns": { "mobile": 2, "tablet": 2, "desktop": 3, "wide": 4 }, "showHeadingUnderImage": true // false hides `heading` and `description` on every card }, "sections": [ { "id": "fun", // used for the section's anchor id — keep it URL-safe "label": "Fun", // the visible heading "items": [ { "title": "Eyes Front!", // required "heading": "Nine students. One whistle.", // optional short tagline "description": "One or two sentences.", // optional "image": "/covers/eyes-front.png", // required — usually something under public/covers/ "imageAlt": "Describe the image, not just the title", // required — never falls back to blank "url": "https://bilarikan.itch.io/eyes-front" // required } ] } ] }