sitecrate-presence (Tier 1 template)
sitecrate-presence is the starting point for every SiteCrate Tier 1 (Presence)
client build. It’s a minimal forkable scaffold — no pre-built sections.
Ahmed builds sections per client with Claude Code, driven by the Build Brief
from the admin.
The stack mirrors sitecrate.ca: Vite + React 18 SPA, hash-based routing, no
React Router, no component library, no Tailwind.
The golden rule: everything client-specific lives in client.config.js
Section titled “The golden rule: everything client-specific lives in client.config.js”src/client.config.js exports a single CLIENT object. Every component imports
from it. No other file should contain client strings.
export const CLIENT = { // Identity name: 'Business Name', tagline: 'Your tagline goes here', phone: '', // must contain digits — 'N/A' renders a broken tel: link email: '', address: '', domain: '', // bare domain only — no https:// prefix
// Branding — injected as CSS vars at runtime by App.jsx accentHue: '250', // oklch hue: 250=blue, 145=green, 25=red, 30=orange, 210=teal accentChroma: '0.14', // 0.14 vibrant, 0.08 muted, 0.20 vivid fontHead: 'Geist', // 'Geist' | 'Instrument Serif' | 'Space Grotesk'
// SEO — gaId must exactly match GA_MEASUREMENT_ID in index.html (both instances) metaTitle: 'Business Name | City', metaDesc: '140–160 char description for Google.', gaId: 'G-XXXXXXXXXX',
// Google Business gmbUrl: '', // Google Maps short URL mapsEmbed: '', // Full Maps embed src
// Content — always an array, even when empty (null causes TypeError) services: [ // { title: 'Service Name', icon: '🔧', desc: 'One-line description' } ],}Known pitfalls
Section titled “Known pitfalls”phonemust contain digits after stripping non-numeric chars.'N/A'/'TBD'are truthy but producehref="tel:"(a broken link). Nav and Footer both guard against this.domainmust be bare (e.g.hamiltonplumbing.ca) — nohttps://. The Footer strips the protocol if present.gaIdmust exactly match both instances ofGA_MEASUREMENT_IDinindex.html. If they differ, every analytics event is silently lost.servicesmust always be an array —null/undefinedcauses aTypeErroron the?.lengthcheck inHomePage.jsx.
CSS-var theming
Section titled “CSS-var theming”App.jsx runs once on mount and injects the brand colours as CSS variables:
root.style.setProperty('--accent', `oklch(0.62 ${c} ${h})`)root.style.setProperty('--accent-2', `oklch(0.72 ${c} ${h})`)root.style.setProperty('--accent-soft', `oklch(0.62 ${c} ${h} / 0.12)`)// Also overrides --font-head if CLIENT.fontHead !== 'Geist'All CSS uses var(--accent) — changing two numbers in client.config.js
re-themes the entire site.
Routing
Section titled “Routing”Hash-based. Add routes as you build pages:
const ROUTES = ['home', 'services', 'about', 'contact']const pages = { home: <HomePage go={go} />, services: <ServicesPage go={go} />, about: <AboutPage go={go} />, contact: <ContactPage go={go} />,}Add nav links in src/components/Nav.jsx:
const NAV_LINKS = [ { label: 'Services', route: 'services' }, { label: 'Contact', route: 'contact' },]Analytics & forms
Section titled “Analytics & forms”- Analytics —
src/analytics.jsreadsCLIENT.gaId. Page views auto-fire on route change;window.gtagexistence is checked before every call, so it’s safe if the GA script hasn’t loaded yet. - Netlify Forms — a hidden
<form name="contact" netlify>inindex.htmlregisters the form at build time. Any React form withname="contact"and thenetlifyattribute submits to Netlify Forms. Always includenetlify-honeypot="bot-field".
Styling token system
Section titled “Styling token system”All base styles live in src/styles/global.css:
| Group | Tokens |
|---|---|
| Light surfaces | --bg, --bg-elev, --bg-muted |
| Text | --ink, --ink-2, --ink-3, --ink-4 |
| Borders | --line, --line-2 |
| Dark sections | --dark, --dark-2, --dark-3, --dark-ink, --dark-ink-2, --dark-ink-3 |
| Brand (runtime) | --accent, --accent-2, --accent-soft |
| Typography | --font-head, --font-body, --font-mono |
| Radii | --r-sm, --r-md, --r-lg, --r-xl |
Base classes: .container, .btn, .btn-primary, .btn-ghost, .btn-on-dark,
.eyebrow, .section-head, .cta-band. Add component styles directly in
global.css — no CSS modules.
Pre-launch checklist
Section titled “Pre-launch checklist”-
client.config.js— all fields populated (name, phone, email, address, domain, services, GA ID, accent colours) -
index.html—<title>,<meta name="description">, all OG/Twitter tags; bothGA_MEASUREMENT_IDinstances replaced with the real GA4 ID matchingCLIENT.gaId; canonical URL updated -
public/sitemap.xml— replaceDOMAIN.cawith the client’s real domain -
public/robots.txt— replaceDOMAIN.cawith the client’s real domain -
public/og-image.png— 1200×630 for the client brand -
public/favicon.svg— replaced with the client favicon -
npm run buildpasses zero errors - All pages score 90+ Lighthouse mobile
- Contact form appears in the Netlify Forms dashboard
- GA4 Realtime shows events firing
See Client delivery: intake → launch for the full per-client workflow.