Project Scaffold — Astro 6, Svelte 5, Tailwind 4, Sveltia CMS
Initial project scaffold for arthouse-site — an image-heavy portfolio site for boutique e-commerce. Astro 6 + Svelte 5 + Tailwind 4 stack, three-mode theme system with client-managed colors, Sveltia CMS at /admin, WhatsApp CTA via server-side redirect, content collections for galleries/pricing/pages/SEO, and a horizontal swipe carousel component.
Changelog - 2026-04-21 (01)
Project Scaffold — Astro 6, Svelte 5, Tailwind 4, Sveltia CMS
Overview
Full project scaffold for arthouse-site, a mobile-first image-heavy portfolio site for a boutique e-commerce client based in Paris. The site is designed to be self-manageable by the client through Sveltia CMS and deployable independently from the Astro-Knots pseudomonorepo.
Specification lives at context-v/specs/Maintain-an-Image-Heavy-Portfolio-Site.md (v0.0.0.3).
Stack
| Dependency | Version | Role |
| Astro | 6.1.8 | Static site generation with hybrid server routes |
| Svelte | 5.x | Interactive components (carousels, mode toggle) |
| Tailwind CSS | 4.x | Utility-first styling via @tailwindcss/vite |
| @astrojs/vercel | 10.x | Deployment adapter |
| @astrojs/sitemap | 3.7.x | Sitemap generation |
| Sveltia CMS | CDN | Git-backed content management at /admin |
Tailwind 4 Integration Notes
Tailwind 4 auto-content-detection failed in the monorepo context — .astro and .svelte files weren't being scanned for utility classes. Fixed with explicit @source directives in global.css:
@import "tailwindcss";
@source "../../src/**/*.astro";
@source "../../src/**/*.svelte";
@source "../../src/**/*.ts";Also removed duplicate base reset styles (*, html, body, a, img) that were conflicting with Tailwind's preflight layer and preventing responsive md: variants from applying.
Theme & Mode System
Three display modes — dark (default), vibrant, and light — using CSS custom properties on data-theme="arthouse" and data-mode attributes.
Color Architecture
:root Named colors (hex values ONLY here)
│
▼
[data-theme="arthouse"]
[data-mode="dark"] Semantic tokens → var(--color-{name})
[data-mode="vibrant"]
[data-mode="light"]
│
▼
Components Use ONLY semantic tokensClient-Managed Colors
Named colors and mode assignments are defined in markdown data files (src/content/theme/colors.md and src/content/theme/modes.md), editable through the CMS. Currently rendered as CSS fallbacks in global.css — build-time generation from the data files is a future step.
Mode Toggle
localStorage-based persistence. Cycles dark → vibrant → light via a button in the nav bar. Initial mode from SITE_MODE env var, overridden by stored preference.
Content Model
Five content collections, all using loose Zod schemas (.passthrough(), no strict validation):
Galleries (src/content/galleries/)
Organized by layout orientation, not content taxonomy:
portrait/— tall/vertical imageslandscape/— wide/horizontal imagesgrid/— mixed aspect ratios
Images listed as a YAML array in frontmatter:
images:
- /images/portfolio/photo-one.jpeg
- /images/portfolio/photo-two.jpegPages (src/content/pages/)
Marketing pages with hardcoded layouts but client-editable content. Sections use the marketing block content model:
sections:
- headingLevelInt: 1
contextSetterTxt: "Small text above heading"
headingTxt: "The Main Heading"
subHeadingTxt: "Supporting copy"
ctaTxt: "Call to Action"
ctaComponentName: "link"
ctaHref: "/portfolio/"Pricing (src/content/pricing/)
Each tier is a markdown file. Client can add/remove/reorder tiers by creating/deleting files.
Theme (src/content/theme/)
Single-file collections for the color palette and mode assignments.
SEO Pages (src/content/seo-pages/)
Placeholder — no content yet.
Images (src/content/images/)
Placeholder — individual image metadata files. Currently galleries reference image paths directly rather than image slugs.
Components
MarketingBlock.astro
Reusable component for marketing sections. Accepts the standard content model props (headingLevelInt, contextSetterTxt, headingTxt, subHeadingTxt, ctaTxt, ctaComponentName, ctaHref). Renders dynamic heading levels, WhatsApp CTA variant, and styled copy.
HorizontalSwipe.svelte
Touch-swipe carousel for gallery images:
Svelte 5 with
$props()and$state()CSS scroll-snap with momentum-based touch scrolling
Responsive slide widths: 85% mobile, 45% tablet, 30% desktop
Dot indicators with active state animation
Arrow buttons on desktop (hidden on mobile)
Lazy loading for off-screen images
3:4 aspect ratio crop on slides
BaseLayout.astro
Page wrapper with sticky nav, mode toggle, footer. Pure Tailwind utilities, px-6 md:px-12 horizontal padding aligned across nav/content/footer.
Sveltia CMS
Setup
Single Astro page at src/pages/admin.astro loads the CMS from CDN. Config at public/admin/config.yml maps all content collections.
Local Mode
"Work with Local Repository" mode reads/writes markdown files on disk. Does not handle image uploads — binary file writes from the browser are blocked by browser security. Image files must be placed manually during local development.
Production Mode
GitHub OAuth backend commits both text and binary (uploaded images) directly to the repo via GitHub API. Auto-deploys via Vercel on push.
Config Fixes
Single-file collections (
theme-colors,theme-modes) must be nested under a parent collection with afiles:array — cannot sit at the top level ofcollections:Nested
fields:inside YAML flow mappings ({ }) is invalid — expanded to block syntaxGallery image lists use
widget: listwithfield: { widget: image }instead of markdown body
WhatsApp CTA
Server-side redirect at /api/contact.ts:
Phone number from
WHATSAPP_NUMBERenv var — never in client-side HTMLPre-filled message from
WHATSAPP_DEFAULT_MESSAGEenv varQuery params for tier and ref tracking
302 redirect to
wa.meURL
Route Map
| Route | Status | Description |
/ | Working | Split layout: marketing copy left, hero image right |
/portfolio/ | Working | Gallery carousel from content collection |
/services/ | Stub | Pricing tiers placeholder |
/contact/ | Working | WhatsApp CTA with server redirect |
/admin | Working | Sveltia CMS |
/design-system/ | Stub | Component browser placeholder |
Pseudomonorepo Integration
Added as git submodule at
sites/arthouse-siteAdded to
pnpm-workspace.yamlPrivate repo at
lossless-group/arthouse-site.npmrcconfigured withlink-workspace-packages=falseBuilds independently:
pnpm install && pnpm buildfrom site directory
Known Issues / Next Steps
Theme CSS generator — colors.md and modes.md data files exist but CSS is still hardcoded as fallbacks. Need build-time generation.
Gallery detail pages —
/portfolio/[slug]routes not yet implementedLightbox — click-to-enlarge on gallery images
MasonryGrid and VerticalSwipe — only HorizontalSwipe implemented
Mobile nav — hamburger menu not yet implemented (nav links hidden on mobile)
Vercel deployment — not yet connected
GitHub OAuth — not yet configured for CMS production use
OG images — no Open Graph image generation yet
SEO landing pages — content type exists but no pages or routes
Image optimization — not using Astro's
<Image>component yet
