@sneat/astro
v0.3.0
Published
Shared Astro building blocks for Sneat marketing landings — i18n toolkit, build/deploy scripts, and themeable chrome (BaseLayout, Header, Footer, LangSwitcher …) driven by the fleet --color-* token contract.
Readme
@sneat/astro
Shared Astro building blocks for the Sneat marketing landings — the i18n
toolkit, build/deploy scripts, and themeable chrome that ~40 landing sites were
copy-forking from sneat-ext-template.
Everything visual is styled through one token contract (the fleet's existing
--color-* semantic layer), so a site themes the whole package by defining its
own tokens. Everything textual is injected — the package ships structure and
behaviour, the site supplies its words and identity.
Install
pnpm add @sneat/astroPeer dependency: astro >= 7. It ships components and scripts as source
(Astro compiles them in your build), so there is no build step to run here.
Quick start
1. Declare your locales (src/i18n/config.ts):
import { createI18n } from '@sneat/astro/i18n';
export const i18n = createI18n({
defaultLocale: 'en',
langs: [
{ code: 'en', label: 'English', short: 'EN', tag: 'en', ogLocale: 'en_GB' },
{ code: 'ru', label: 'Русский', short: 'RU', tag: 'ru', ogLocale: 'ru_RU' },
],
});
// Re-export the bound helpers so your pages read exactly as before:
export const { langs, localeHref, routeFromPath, localeParams, ogImageFor } = i18n;English-only site? Pass a single-locale langs array; the header switcher then
hides itself automatically.
2. Map your brand tokens onto the contract (src/styles/global.css), the
same shape the other landings already use:
:root {
--color-bg: var(--brand-bg);
--color-surface: var(--brand-surface);
--color-border: var(--brand-border);
--color-text: var(--brand-ink);
--color-text-muted: var(--brand-muted);
--color-accent: var(--brand-primary);
--color-accent-text: #fff;
/* … see src/styles/contract.css for the full vocabulary … */
}3. Use the layout — pass your i18n, a locale-independent site
(SiteMeta) and a per-locale chrome (SiteChrome):
---
import BaseLayout from '@sneat/astro/layouts/BaseLayout.astro';
import { i18n } from '../i18n/config';
import { getSite, getChrome, getCopy } from '../i18n/data'; // your copy layer
const locale = 'en';
---
<BaseLayout
i18n={i18n}
site={getSite()}
chrome={getChrome(locale)}
locale={locale}
title="ToGethered — the people already near you"
description="…"
siteDescription={getCopy(locale).site.seoDescription}
transparentNav
>
<slot name="head">
<!-- your own fonts (self-hosted Fontsource imports, or a <link>) -->
</slot>
<!-- page content -->
</BaseLayout>SiteMeta is identity, so its description can only be one language.
Multilingual sites therefore pass siteDescription — the site's own one-liner
in this page's language — and SeoHead puts that in the JSON-LD
Organization/WebSite nodes. Skip it and every locale's structured data
describes the site in whichever language SiteMeta.description happens to be,
under a <html lang> that says otherwise. It's the site's line, not the page's:
both nodes are site-scope (#org, #site), so keep it stable across the pages
of a locale rather than passing each page's description.
What's in the box
Components — @sneat/astro/components/<Name>.astro
| | |
|---|---|
| SeoHead | canonical, full hreflang cluster + x-default, OG/Twitter, Organization+WebSite JSON-LD, theme-color, favicon, geo-gated GA4 |
| Header | brand · nav · language switcher · CTA. Solid sticky bar, or transparent over a hero (themed via the --nav-* contract) |
| Footer | brand + tagline, link columns, legal bottom bar |
| LangSwitcher | native-language dropdown; every alternate a real <a hreflang>; opens in pure CSS; themed via --lang-* |
| GoogleAnalytics | GA4 with Consent Mode v2, EEA/UK geo-gating, [data-track] delegation. Renders nothing without an id |
| ServiceCard, Legal | product card with status pill; privacy/terms body |
| PricingTiers | responsive grid of subscription-pricing cards + a quiet contact-line footnote; pure data in, no product copy baked in |
Pricing
PricingTiers renders a grid of pricing cards (name, price, priceNote,
features, an optional launch badge, a CTA) plus an optional "too big for
these tiers?" contact line. It's structure only — the section wrapper
(eyebrow, <h2>, lede, the page's own id="pricing" anchor) stays your
page's job, same as ServiceCard.
---
import PricingTiers from '@sneat/astro/components/PricingTiers.astro';
import { sneatWorkPricing, sneatWorkPricingContactLine } from '@sneat/astro/data/pricing';
---
<section id="pricing" class="section">
<div class="container">
<div class="text-center section-head">
<p class="eyebrow">Launch pricing</p>
<h2>One subscription, the whole suite</h2>
</div>
<PricingTiers tiers={sneatWorkPricing} contactLine={sneatWorkPricingContactLine} />
</div>
</section>sneatWorkPricing is the canonical, settled Sneat.work-family ladder
(Free / Pro / Team / Team Lifetime / Company — see
src/data/pricing.ts for the full provenance note). Pass your own
PricingTier[] instead when a site needs a different ladder — data and
layout are separable by design. Each card and its CTA render
data-tier={tier.id}, so a site's own script can hook a click (e.g. to
prefill a waitlist form, or intercept a lifetime-plan checkout) without the
component knowing about waitlists or billing. Requires the site's own
.btn / .btn-primary / .btn-secondary utility classes, same as
ServiceCard's CTA.
Layout — @sneat/astro/layouts/BaseLayout.astro (composes SeoHead +
Header + your page + Footer).
i18n — @sneat/astro/i18n — createI18n(config) → localeHref,
routeFromPath, localeFromPath, ogImageFor, localeParams, langByCode.
Styles — @sneat/astro/styles/contract.css — the documented token contract
with neutral zero-specificity defaults (imported for you by BaseLayout).
Scripts — @sneat/astro/scripts/*.mjs, run from your site root:
| | |
|---|---|
| make-og.mjs / check-og.mjs | render OG cards from /og-card/* routes (Playwright + sharp); guard that every page's card exists |
| sitemap.mjs | createSitemapConfig({ site, locales, defaultLocale }) → hreflang-aware @astrojs/sitemap config |
| worker.mjs | Cloudflare edge helpers — legacy redirects, localised 404, root-mounted app shell |
| smoke.mjs | createSmoke(url) post-deploy assertions |
| assemble-app.mjs | merge an Angular app build into the landing dist/ |
Theming contract
Components reference only the tokens in
src/styles/contract.css — never a raw brand colour.
The trigger over a photographic hero and the switcher menu are themeable per nav
context via the --nav-* and --lang-* sub-contracts. See each component's
header comment.
Adding a locale
Add it to your createI18n langs, add its data overlay + copy, and the
[locale]/* pages, hreflang alternates, switcher and sitemap all follow. The
type-checker lists any copy record you haven't completed.
Part of the Sneat ecosystem.
