| name | frontend-quality |
| description | Pre-ship UI quality gate for the sagas reading site -- accessibility (WCAG, keyboard, contrast), performance (fast static delivery, fonts, motion cost), and SEO (per-page metadata, canonical, OG/Twitter, sitemap, JSON-LD, RSS). Mobile-first, since the product is long-form reading on phones at night. Audits and fixes against this repo's design tokens and dash/motion house rules. |
| when_to_use | Trigger on: "is this accessible", "a11y", "check accessibility", "make it mobile friendly", "is it fast", "performance", "Lighthouse", "Core Web Vitals", "SEO", "metadata", "OG image", "sitemap", "is this ready to ship visually", or before shipping a new page/section/route. Pairs with the `design` skill for visual fixes and `writing-craft` for prose.
|
frontend-quality
A pre-ship gate for the three axes that decide whether a new page is ready: accessibility, performance, SEO -- all mobile-first. Audit, then fix against the repo's house rules. Adapted from the kalchar frontend-quality gate for this Astro static site.
Context: who visits
The product is reading long-form fiction and essays on a phone, often at night. Design and audit for phone width first, then scale up. A desktop-only check is incomplete. Dark-first is intentional (.impeccable.md).
1. Accessibility (WCAG 2.1 AA)
- Prefer native elements (
<nav>/<article>/<ol>/<li>/<a>/<button>), not role=. The pages already use semantic landmarks; keep it that way.
- Contrast >= 4.5:1 body text, 3:1 large/UI. All color flows through the OKLCH custom properties in
src/styles/global.css via light-dark(); check BOTH themes (preview_resize with colorScheme).
- Every interactive element: keyboard-reachable, visible focus ring (the global
:focus-visible flame outline), and an accessible name. Icon-only/decorative SVG (the header flame) must be aria-hidden.
- Skeleton/ghost rows are decorative: mark them
aria-hidden="true" so screen readers don't announce placeholder noise.
- Min tap target ~44px on mobile: chapter cards and pager links must have enough padding.
- Reading-specific: body copy stays within
--measure (~65ch), line-height >= 1.6; the drop cap must not break screen-reader word order (it's a ::first-letter, so it's safe).
- Quick check: keyboard-tab the page top to bottom, confirm the focus ring is visible on every link, and that motion is opacity/transform only.
2. Performance (mobile-first static)
- Astro ships no framework runtime -- client JS is limited to the small inline reader scripts (progress, preferences, nav, TTS). Any NEW
<script> must justify itself against the reading experience; this is a reading site, not an app.
- Fonts are the main weight: self-hosted Eczar + Literata via Fontsource (
@fontsource-variable/*/index.css). Only import weights actually used; never add a Google Fonts CDN link.
- LCP is the hero heading (text) on the homepage and the first paragraph on a chapter -- text LCP is fast; don't introduce a large hero image without
loading="eager" + width/height.
- CLS: reserve space. Skeleton rows have fixed heights; any future image needs explicit
width/height or an aspect-ratio box.
- Motion: the
rise and shimmer animations use transform/opacity/background-position only (compositor-friendly). Do not animate layout props (width/height/top). Every animation MUST have a prefers-reduced-motion path -- global.css freezes entrances, shimmer, and the ambient glow when the OS asks (WCAG 2.3.3).
- Tools:
pnpm build prints per-route output; watch for unexpected JS. Verify a real mobile viewport via preview_*.
3. SEO
- Per-page metadata: every page renders through
Base.astro, which requires title + description and sets canonical, OG, and Twitter tags. New routes MUST pass a real, specific description (not the site default). No literal em/en dash in titles/descriptions (repo dash rule).
- ogType: pass
ogType="book" on book pages, "article" on chapters, default "website" elsewhere.
- Canonical: derived from
Astro.url against site in Base.astro -- always the sagargupta.online/sagas/... host. Never hardcode a URL.
- Sitemap: generated by
@astrojs/sitemap (astro.config.mjs) at /sagas/sitemap-index.xml; public/robots.txt points to it. New routes appear automatically -- confirm they do in dist/sitemap-0.xml.
- JSON-LD: the
WebSite + Person block in Base.astro. Keep it truthful; add per-type structured data (e.g. Book, Article) only if it stays accurate.
- RSS:
src/pages/rss.xml.ts -- a chapter enters the feed only once it has publishedOn. Item links and the channel link both carry the /sagas base (see the withBase note in CLAUDE.md gotchas).
Workflow
- Identify scope (one component / one page / whole site).
- Audit each of the three axes above; report findings as
file:line | axis | issue | fix | severity.
- Fix the safe, clear ones against the design tokens + house rules; for visual-judgment calls, pair with the
design skill.
- Verify (never claim "accessible/fast/indexed" on static reasoning for a visible change):
pnpm check + pnpm build clean.
- Grep
dist/ for the new page's canonical, OG tags, and sitemap entry.
- Load a real mobile viewport via
preview_*; keyboard-tab it; flip light/dark.
- Hand off to the commit/PR flow when green.
House rules that double as quality rules
- Mobile-first; most readers are on phones.
- No em/en dash characters anywhere (chat, code, copy, metadata) --
-- or -.
- Motion is visible by default AND fully disabled under
prefers-reduced-motion -- both paths ship together, always.
- Internal links go through
withBase(); the site lives at a subpath (/sagas).
- Chapter prose is Sagar's voice -- never "fix" it here; this gate is about the shell around the words, not the words.