Guillaume Lebedel · · 15 min We Rebuilt Our Marketing Site with Astro, Claude Code, and Cloudflare. In a couple of evenings.
Table of Contents
We’d been on Webflow since the company started. It worked until it didn’t: harder to get AI to work with than plain code, no version control, no way to generate pages from data or ship interactive content in blog posts. So we moved to code and used Claude Code as the primary builder (and sometimes Cursor for the in-Slack experience): an AI-first website where anyone on the team makes changes through natural language. Both founders and a product marketing lead, two weeks, no agency.
The result: ~200 commits in two weeks, 86,224 lines of code, 38 blog posts, 914 auto-generated connector pages, and 42 page templates. Five GitHub Actions workflows. Fully shifted to Cloudflare Pages.
Why Webflow stopped working for us
Everything was slow and nobody could self-serve. Updating a blog post meant waiting for a designer. Fixing a hero typo was a CMS update someone had to approve and publish. Most of the team already uses Claude Code daily. Webflow was the odd one out. AI can build a React component in seconds but can’t produce anything Webflow Designer can import, and even a well-built Webflow MCP runs into hard limits from how Webflow executes code internally.
No version control. Webflow has staging, but it’s shared across the team — no branching, no pull requests, no diffs. If something breaks, you’re debugging through a shared environment with no clean rollback.
SEO was a black box. We had duplicate pages we couldn’t programmatically clean up, no control over canonical URLs without workarounds, and no server-side rendering. StackOne’s connectors page needed to show 200+ integrations pulled from the StackOne API. In Webflow, your options are manual CMS entries, expensive third-party tools, or client-side JS fetching data at runtime.
No way to vibe-code rich content. Adding a floating TOC with share buttons to the blog layout would have been days of work in Webflow. With Claude Code it was under an hour. Any custom interface (a filterable grid, an animated explainer, a live chart) means fighting Webflow’s abstractions rather than just building the thing.
Why we chose Astro and Cloudflare Pages
We picked Astro 5 with Cloudflare Pages. A code-first stack was the point: it works with AI, supports version control, and makes richer content possible: charts, interactive diagrams, data-driven pages. Things a visual builder can’t touch.
Astro’s content collections let us write blog posts, case studies, and changelog entries in MDX (Markdown with JSX components). Each content type has a typed schema. Adding a blog post means creating a .mdx file and pushing to main. The build system handles everything else.
Cloudflare Pages gives us automatic deploys from GitHub, preview URLs for every PR, and a global CDN. The deploy pipeline is: push to GitHub, Cloudflare builds the Astro site, serves it from the edge. No infrastructure to manage.
Tailwind v4 with CSS-based configuration keeps the design system in a single global.css file. Colors, typography, spacing are all defined as CSS variables. Claude Code reads this file and applies the design system correctly across every component it creates.
React islands (client:visible) for the few interactive pieces: connector search/filter, ECharts visualizations, mobile navigation. Everything else renders as static HTML at build time.
The architecture looks like this:
content/blog/*.mdx ─┐
content/legal/*.mdx ─┤
content/connectors/ ─┼──→ Astro 5 Build ──→ Cloudflare Pages ──→ CDN
src/pages/*.astro ─┤ ↑
src/components/ ─┘ GitHub Push
(auto-deploy)
Claude Code as the builder
Some real examples from the sessions that built this project:
React filter, category tabs, search, and a responsive connector card grid, generated from JSON in the content collection, interactive filter wired with client:visible. One prompt, one session.
Screenshots of both sites, layout diffs identified, spacing and font fixes applied automatically. The visual QA loop: describe what’s off, Claude Code fixes it, check again.
Workflow file, per-reviewer checklist files, and claude-code-action wired up for all four jobs. Non-engineers get specific PR feedback without waiting for a human. (We also used Claude Code to build our semantic search for the connector pages.)
Picked build-time Shiki, built a shared highlightCode() utility, re-rendered every code block on the site. Removed a ~2–3MB client-side JS bundle.
LCP hero not preloaded, font-display missing, connector filter on the critical path. Plan written, all three fixed in the same session.
What we’d change: four tool options made Claude pause to pick one. To be fair, the options exist because Chrome DevTools MCP drops randomly; agent-browser was always the fallback. One tool with a stated fallback would have been faster. “Exhaustive” also triggered a long report when targeted Core Web Vitals questions were all we needed.
Ran Lighthouse mobile audits across all pages, flagged breakpoint issues, fixed stacking and overflow on every layout. Then generated a /responsive-design skill with the exact patterns used (mobile-first grid rules, image handling, touch target sizes) and a CLAUDE.md section listing what to check before committing any UI change.
Setting up for AI-first web development
Before writing a single component, we set up the project so Claude Code could work well in it.
CLAUDE.md. The useful part isn’t tech-stack version specifics — it’s the standards the model won’t infer from reading code alone: where files go, which libraries are already in the project and which to never add, preferred patterns (client:visible over client:load, Astro over React for static content, build-time rendering wherever possible). Without it, Claude Code falls back to safe generic defaults — technically correct, but wrong for this stack.
## 🚨 Critical: Architecture Decisions ← what the model must not change
## 📁 Project Structure ← where files live, naming conventions
## 🎨 Design System ← colors, fonts, spacing, component patterns
## Marketplace Plugins ← skills table with slash commands and purpose
## ✍️ Creating Content ← blog, case studies, changelog, legal schemas
## 🆕 Creating New Pages ← static vs dynamic routes, SSR rules
## 🔍 SEO & Performance ← mandatory for a marketing site
## 🔒 GitHub Actions ← SHA pinning, what not to touch
## 🔧 Component Patterns ← React islands, Astro scripts, JSX gotchas
## 📚 Skills Reference
Content: /website-blog-creator, /website-page-creator, /technical-blog, /natural-writing
Design: /stackone-web-design-guidelines, /animate
SEO: /seo-audit, /review-seo, /web-perf
PR gates: /review-practices, /review-accessibility, /review-security
## 🚨 Committing from Claude Code ← run checks manually, then --no-verify
## ✅ Pre-Commit Checklist
Skills and plugins as quality gates. Claude Code supports “skills”: reusable instruction sets invoked with slash commands. We built and installed several from our StackOne Claude Marketplace: /stackone-web-design-guidelines (brand tokens, component patterns, responsive rules), /technical-blog (blog creation workflow with critique checklist), /natural-writing (strips AI-sounding language; yes, we wrote an anti-LLM skill for our LLM), /seo-audit (meta tags, canonical URLs, Core Web Vitals). PR gates run separately: /review-practices, /review-accessibility, and /review-security on every push.
Four AI reviewers in CI, running in parallel. Every PR triggers four Claude jobs: practices, SEO, security, and accessibility. Each posts inline GitHub suggestions on the exact diff lines flagged; one click to apply.
@review Best Practices sonnet · reads review-practices.md ▸
Checks Astro 5 patterns, Tailwind v4 usage, Cloudflare Pages compatibility, component structure, and design system adherence. Posts inline suggestion blocks on the exact diff lines so authors can apply fixes in one click.
SEO haiku · reads review-seo.md ▸
Audits meta tags, canonical URLs, heading hierarchy, structured data, and Open Graph. Flags missing or duplicate titles, broken canonical declarations, and heading skips (h1 → h3).
- Title tag present and under 60 chars
- Meta description present and under 160 chars
- Canonical URL declared and matching slug
- OG image and title for social sharing
- Heading hierarchy (no skipped levels)
- Structured data (JSON-LD) for articles and listings
Security sonnet · reads review-security.md + security-instructions.md · blocking gate ▸
Reviews for secrets, unsafe patterns, SHA pinning in Actions, XSS vectors, and MDX injection. The only blocking reviewer: its summary comment must end with VERDICT=PASS or the PR can't merge. Uses claude-code-action with a dedicated security-gate step that reads the verdict from the posted comment.
- No API keys or tokens in React island props
- MDX content must not render raw HTML from untrusted sources
- GitHub Actions must be pinned to full commit SHAs, not version tags
- Dynamic route params must be sanitized before use in file paths
- No raw HTML injection in client-side components
Accessibility haiku · reads review-accessibility.md ▸
Checks WCAG 2.1 AA compliance: alt text, ARIA roles, keyboard navigation, colour contrast, and focus management. Skips files with no a11y relevance (JSON, config, CSS-only changes). Only flags real violations, not nitpicks.
- Images have descriptive alt text (not empty or "image of")
- Interactive elements reachable by keyboard
- ARIA labels present on icon-only buttons
- Colour contrast meets 4.5:1 for normal text
- Focus indicators visible and not suppressed
- Form inputs have associated labels
pr-review.yml (simplified) ▸
on: pull_request # also: issue_comment with "@review"
jobs:
should-run: # checks @review trigger, outputs pr_number
outputs: run, pr_number
practices:
needs: should-run
uses: anthropics/claude-code-action
prompt: read review-practices.md, review diff, post inline suggestions
seo: # runs in parallel
uses: anthropics/claude-code-action
prompt: read review-seo.md, review diff, post inline suggestions
security: # runs in parallel
uses: anthropics/claude-code-action
prompt: read review-security.md, end summary with VERDICT=PASS or VERDICT=FAIL
- name: security-gate # reads VERDICT from posted comment, exits 1 on FAIL
accessibility: # runs in parallel
uses: anthropics/claude-code-action
prompt: read review-accessibility.md, WCAG 2.1 AA only Plans for complex work. For multi-step tasks, we wrote plans and executed in batches with checkpoints. The plan lists every file to create, every component to build, and what to verify after each step. Claude Code reports progress and pauses for review between batches. For larger changes we used claude --worktree to spin up isolated git worktrees, letting multiple sessions run in parallel on different parts of the migration without stepping on each other.
Browser automation: Chrome DevTools MCP and agent-browser. For anything visual — layout comparisons, Lighthouse audits, screenshot QA — we used Chrome DevTools MCP, which connects to a running Chrome instance and lets Claude Code inspect, screenshot, and run audits without leaving the session. When the Claude Code Chrome extension connection dropped (which it does), or when Chrome DevTools MCP struggled with parallelized sessions, agent-browser from Vercel was consistently more reliable: a headless browser CLI Claude Code drives from the command line.
The investment was maybe two hours.
A typical session
A typical session was 2–3 hours. Describe a page, Claude Code builds it, and the fix cycle is usually one more message. The loop was fast because Claude Code runs pnpm dev, checks build errors, and often fixes them before we noticed. Screenshots helped too: point Claude Code at the old Webflow URL and the new localhost, and it takes its own screenshots via Chrome DevTools MCP or agent-browser, compares the two, spots the diffs, and applies fixes. No manual screenshotting.
When something broke, Claude Code read the error, traced it to source, and fixed it. Most MDX and compilation issues resolved without anyone opening the code.
Faster shipping, better perf, and more content than we started with
- Write a brief
- Wait for designer
- Design in Webflow — no version control, no preview URL
- Wait for a dev to be free
- Review cycle
- Manual CMS publish
- Something looks wrong? Start over from step 3
- Describe what you want
- Claude Code builds the component, pushes a branch
- Cloudflare generates a preview URL
- Review on a real preview URL, get Claude Code to iterate — or catch anything the build exposed that local dev didn't
- Merge — live in 90 seconds
No designer handoff. No publish queue. Anyone on the team can do this.
- Spot a wrong date or typo
- File an issue or Slack someone
- Wait for a dev to context-switch
- They fix it, you review, it merges
A one-word change cost an hour of someone else's time.
- Tag
@Cursoror@claudein Slack with the repo and what to change - It opens a branch, makes the fix, posts back a preview URL
- Merge
No terminal. No editor. No interrupting a dev.
The site has kept growing since: 558 commits total as of today, with new product pages and blog posts shipped weekly using the same workflow.
Performance improved too. Mobile Lighthouse went from 42 to 92 through SVG compression, Core Web Vitals fixes (LCP, CLS, INP), JS bundle cleanup, and moving static assets to an immutable CDN caching tier.
Previous Webflow site
Mobile
New site
Mobile
Desktop
Disclaimer: these numbers were taken before adding our cookie banner back. Numbers are slightly lower now. 🙈
Everyone can ship good, fast, rich website pages
Our PMM and PM ship product pages, blog posts, and copy updates directly. Sales ships announcement posts. Everyone can ship rich, interactive blog content. No designer handoff, no waiting. Describe what you want to Claude Code, review the preview URL, merge.
For smaller fixes (a wrong date, a missing event, a typo), there’s an even shorter path. Tag @Cursor in Slack with the repo name and describe what you want. It picks up the request, opens a branch, makes the change, and reports back. No terminal, no editor.
MDX with embeddable Astro and React components is what makes content richer than any visual builder. In Webflow or Framer, you’re limited to what the builder supports. Here, describe what you want (a live chart, a terminal UI, a comparison table) and Claude Code writes the component inline, drops it into the MDX file, and it just works. This post’s Lighthouse gauges, bar charts, and reviewer accordion were all built during this session, not pulled from a library.
The PR workflow means every change is tracked. Roll back a factual error in one click, and see who changed what.
What GitHub + Claude Code + Astro + Cloudflare gave us
914 connector pages from a nightly sync. A GitHub Action runs daily, pulls connector data from the StackOne API, and creates a JSON file per connector. Astro generates a page for each one at build time. Adding a new connector to the API means a new page appears on the site the next morning. In Webflow, each connector was a manual CMS entry.
Sitemap with real lastmod dates. Astro’s sitemap plugin generates the XML, but we extended it. A build-time function reads frontmatter dates from every blog post, changelog entry, press release, and legal page, then injects correct lastmod timestamps into the sitemap. Redirects, API routes, and internal-only pages are excluded automatically. In Webflow, the sitemap is a black box: you get whatever it generates and can’t customize it.
llms.txt and llms-full.txt for AI discoverability. Following the llms.txt standard, we auto-refresh two files on every push to main so LLMs can accurately reference StackOne. llms.txt is a concise structured overview: main pages, all 200+ connectors grouped by category, recent blog posts, case studies, changelog, and press. llms-full.txt includes the full body content of every blog post, case study, and changelog entry, so an LLM can answer detailed questions without crawling the site.
A GitHub Action watches for content changes on main and regenerates both files automatically. The script reads MDX frontmatter and JSON connector data directly from the content/ directory, so adding a blog post or connector updates both files on the next push.
Rich interactive content, everywhere. In Webflow, rich content means iframes or plugins. Here, anything you can describe, Claude Code builds natively: ECharts, animated SVG diagrams, terminal UIs, comparison tables, filterable grids. The animated homepage section explaining what StackOne does was built by our CEO Romain in a single Claude Code session. Not polished yet, a designer will refine it, but it shipped as a working v1 in hours rather than weeks.
Preview environments per PR. Every pull request gets a unique Cloudflare Pages preview URL. Marketing can review a blog post draft in the real site, on a real URL, before it merges. Webflow had staging, but it was the whole site, not per-change.
SEO on Astro + Cloudflare Pages
A few things we had to fix or wire up that Webflow would have handled (or hidden) for us.
Google Search Console flagged 404s immediately after launch. The migration left old Webflow slugs without redirects. GSC’s coverage drilldown report lists every affected URL. We connected our own Google Search Console MCP connector to Claude Code, which let it query crawl errors and coverage data directly without manually exporting CSVs. It then generated all the 301 redirects in astro.config.mjs in one session. Worth doing the day you launch, not weeks later when GSC has already penalized the pages.
Astro’s redirect config doesn’t support wildcards. For patterns like /integrations/* → /connectors, use Cloudflare’s _redirects file in public/ instead. The Astro config handles specific path mappings; the _redirects file handles everything else.
301 vs 302 isn’t pedantic. Internal page reorganization gets a 301: the old URL is gone. External service endpoints (/app, /docs, /login) get a 302. If you use a 301 for something pointing offsite and that URL ever changes, the redirect is cached in browsers indefinitely with no way to clear it. We use 302 for anything pointing outside the domain.
Client-side islands hide content from crawlers. BlogFilter.tsx and ConnectorFilter.tsx are fully client-side React. Blog cards, connector cards, the page H1: anything rendered inside them is invisible to search engines. Keep indexable content in the Astro wrapper and use the island only for interactive filter state.
Sitemap lastmod is wrong by default. Astro’s sitemap plugin uses the build date for every page. We extended it to read date from MDX frontmatter and inject the real lastmod per post. Wrong lastmod dates confuse crawl scheduling; Google deprioritizes re-crawling pages that claim to never change.
noindex non-canonical pages. Platform pages not in the main navigation, internal tooling routes, tag archives: anything you don’t want indexed needs <meta name="robots" content="noindex, nofollow">. Without it, crawl budget goes to pages you don’t want ranked and GSC fills with noise.
Web Performance on Astro + Cloudflare Pages
Honest admission: performance took longer than the entire migration. A lot of trial and error, learning Astro’s rendering pipeline, and toggling every setting in the Cloudflare dashboard to see what moved the needle. But the score went from 42 to 92, so it was worth it.
We didn’t profile performance until after launch.
Core Web Vitals: what was actually broken
LCP was 13.1s on mobile. Three separate causes, all fixable:
Three background SVGs were 200–400KB each — they’re repeating dot/box grids. Converting to SVG <pattern> (one tile, repeated) brought the combined size from ~600KB to ~1.2KB.
Blog featured images had no fetchpriority="high". The browser treated them as regular resources, discovered them late in the waterfall, and they became the LCP element.
Animation CSS was loading synchronously, blocking render. Deferring it until after JS was ready removed the render-blocking dependency.
After all three: LCP 139ms on blog posts, ~5s on the heavier homepage.
CLS was 0.334 (failing threshold). Images without explicit width and height attributes cause layout shift when they load. Adding dimensions to every image: 0.0003.
INP on the connectors page was ~848ms. 900+ connector cards filtered synchronously on every keystroke. Wrapping the state update in React’s useDeferredValue defers the expensive re-render until the browser is idle:
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
// input renders with `query` (immediate)
// list re-renders with `deferredQuery` (deferred, when idle)
Input stays responsive; the list catches up on the next idle frame.
Eliminating JavaScript that didn’t need to exist
React islands where Astro components work fine. BlogTOC, ShareButtons, and ReadingProgress were React islands. None of them need React. A table of contents is static markup, share buttons open a URL, and a reading progress bar is one IntersectionObserver. Converting them to Astro components removed React and jsx-runtime from every blog page: ~45KB per pageview gone.
Client-side syntax highlighting. Shiki bundles ~2–3MB of JS and language grammars. Moving to build-time rendering via src/lib/highlight.ts ships static HTML instead. No flash of unstyled code, no large bundle.
Fonts: self-hosted, subsetted, preloaded
All fonts live in src/fonts/. Vite content-hashes them at build time, so the output path is /_astro/inter.A1B2C3D4.woff2. That means the browser can cache them indefinitely.
Two changes made a measurable difference:
Subsetted Inter. Full Inter WOFF2 files are ~110KB each. Running pyftsubset to keep only Latin and extended Latin glyphs brings them to ~19KB. For a B2B SaaS marketing site that doesn’t display CJK or Arabic text, the other 80KB is just unused data sent to every visitor.
Removed TTF fallbacks. Every @font-face block had format("woff2"), format("truetype"). TTF support only matters for IE11 and early Edge. WOFF2 has been universally supported since 2016. Removing the TTF entries doesn’t affect rendering, but it cleans up font format resolution and reduces the CSS footprint.
Font preload links start font requests at HTML parse time, parallel to CSS instead of waiting for the stylesheet to load. We preload the above-the-fold Inter weights in BaseLayout.astro and SFMono-Medium.woff2 in BlogLayout.astro.
Caching tiers
Three different policies, each deliberate:
| Path | Browser cache | Cloudflare CDN |
|---|---|---|
/_astro/* (JS, CSS, fonts, hashed images) | immutable, 30 days | 1 year |
/images/*, /logos/* | 2 hours | 30 days |
| HTML pages | no cache | 30 days |
/_astro/* files are content-hashed by Vite — if they change, the URL changes, so they’re safe to cache indefinitely. Browser holds them 30 days; Cloudflare CDN for a year.
HTML gets zero browser cache (users always see current content) but is cached at Cloudflare’s edge, cutting time-to-first-byte from ~600ms to ~20–50ms on warm requests. Cloudflare Pages purges the edge on every deploy.
About 55 images were in public/images/, getting a 2h browser cache. Moving them to src/assets/images/ and importing via ES modules routes them through Vite — content-hashed, immutable tier.
Speculation Rules API for instant navigation
Added to every page:
{
"prerender": [{
"where": { "href_matches": "/*" },
"eagerness": "moderate"
}]
}When Chrome thinks a user is about to navigate (hover, mouse proximity, or visibility), it prerenders the target page in the background. Navigation feels instant. Browsers without Speculation Rules support ignore the <script type="speculationrules"> block entirely, with no downside for non-supporting browsers.
Quick wins
A few one-liner changes that apply to any Astro + Cloudflare site:
Connector logos: PNG → SVG. The connector grid loaded ~57 PNGs averaging ~8KB each. SVGs are ~2KB, scale without loss, and are sharp at retina. Homepage image payload dropped ~340KB.
<link rel="preconnect"> for CDN domains. Saves the TCP+TLS handshake on first request. Only add it for domains that load on that specific page — a preconnect on every page for a CDN you only hit on /connectors/ wastes it on all the others.
fetchpriority="high" on the LCP element. One attribute. The browser stops guessing and starts fetching the right image immediately.
font-display: swap on @font-face. Without it, text is invisible until the custom font loads. Swap shows fallback text immediately and swaps in the custom font when ready.
Third-party scripts. We ended up back where we started: standard async GTM. Total third-party main thread cost is 27ms, all of it after the page has painted — zero impact on Core Web Vitals. The 42 score was never really about third-party scripts.
The Partytown and Zaraz detour
We tried two alternatives before landing back on async GTM.
Partytown moves scripts into a web worker. CookieYes (our consent banner) and Partytown both proxy the same DOM setters to intercept consent state — with both active, they called each other until the call stack overflowed. Consent banner never rendered, and CookieYes still wrote a partial cookie, so the banner was skipped on the next visit entirely. We also saw GA4 dropping sessions from Partytown’s sendBeacon proxying. Patched both issues, but it stayed fragile and network requests inside the worker don’t appear in DevTools — debugging was guesswork.
Zaraz was next. Didn’t fit: it changes how consent gating works and not all our tags were compatible.
Back to async GTM. The third-party main thread cost is 27ms, all of it after the page has already painted. The 42 score was never about third-party scripts.
Most of this Claude handled end-to-end: diagnose in the local dev environment or a Cloudflare preview branch, implement the fix, verify. The only times I had to step in manually were when its own suggestions caused new problems. Partytown was Claude’s idea. So were some caching headers that were too aggressive on non-hashed assets — had to walk those back. Good instincts, wrong in practice. For everything else, the loop ran without me.
These fixes came in sequence. The biggest single improvement was the caching work: moving assets to Vite’s content-hashed pipeline (/_astro/*) with a one-year CDN TTL meant all fonts, JS, and CSS load once and get served from the edge on every repeat visit. SVG compression, fetchpriority on hero images, and deferred animation CSS had the most visible immediate impact on LCP. Bundle cleanup came after. The mobile Lighthouse score is 92 today (TBT: 0ms, FCP: 1.7s, CLS: 0.001). Lab scores vary between runs, but the trend holds. The remaining opportunity is LCP at 3.2s in lab / 2.8s in field data, just above the 2.5s Core Web Vitals threshold.
Disclaimer: we’re not pretending this flow will work for everyone
Code + AI is more approachable than a visual builder. Visual builders require learning their interface and abstractions. Non-engineers here don’t touch Astro or Tailwind. They describe what they want, Claude Code writes it, the PR workflow handles the rest.
Is this right for everyone? Probably not. If you have a large marketing org with specialized design, copy, and web teams, the tooling already works for you. But we’d argue this setup is what actually helps you scale before that org exists, rather than the over-designed, over-processed, over-gatekept workflow that slows down early-stage teams. Ship it, iterate with AI, fix it in the next PR.
DNS and the apex domain (Route 53 + Cloudflare)
If you want to keep DNS on Route 53 (or anywhere other than Cloudflare), Cloudflare Pages won’t let you add a custom domain without transferring the zone. The workaround: convert your Cloudflare zone to CNAME setup (Business plan) so Cloudflare keeps the zone but Route 53 stays authoritative. Then add a CNAME for www in Route 53 pointing to your *.pages.dev URL.
The root domain (stackone.com) is trickier. You can’t CNAME at the apex in standard DNS, and Route 53 ALIAS records only support AWS resources or other records in the same hosted zone (not arbitrary external hostnames). The fix: create a CloudFront distribution with a CloudFront Function that 301-redirects everything to https://www.stackone.com, get an ACM cert for the apex, and point Route 53 with an ALIAS to the CloudFront distribution. That gives you HTTPS on the apex with no S3 bucket needed. Adds 30 minutes to setup, but it’s solid.
~558 commits in. Most described in natural language, reviewed by automated reviewers, and shipped by whoever made the change.
The
greenvariant hardcodes#00AF66directly instead of using a CSS variable. This bypasses the design system's@themepattern and means future theme updates won't propagate here.