Reference build · Phase 4 of 4 · closing chapter
Audit and optimize the live demo.
How the catalog audits its own output. Real tools, real findings, real fixes. After this lands, the reference build is complete.
Demonstration notice
Threshold is a fictional product. The audit work on this page is real. axe-core ran against the deployed build and reported real violations; Lighthouse produced real Core Web Vitals; manual checks surfaced real keyboard and on-page gaps. Fixes were applied and re-audits captured before-and-after metrics. Findings reflect what the tools actually reported.
axe violations
Before
35
After
0
delta: -35
Lighthouse a11y (desktop)
Before
96
After
100
delta: +4
Findings fixed
Before
0
After
9
delta: +9
Methodology
How the audit ran.
Three tool sets ran against the deployed /demo/threshold build. Findings come from real tool output; nothing was synthesized.
| Tool | Version | Purpose |
|---|---|---|
| @axe-core/cli | 4.11.2 | WCAG 2.0 A/AA + 2.1 A/AA + best-practice automated checks |
| Lighthouse | 13.x (latest via npx) | Performance, accessibility, best-practice checks, SEO scoring |
| Manual keyboard navigation | n/a | Focus management, focus-visible styling, tab order, keyboard interaction patterns |
| Manual on-page review | n/a | Heading hierarchy, semantic landmarks, OpenGraph + Twitter Card + JSON-LD presence |
Scope
/demo/threshold (single-page launch microsite, 8 sections, both desktop and mobile profiles)
Pre-fix build: 338cf84
Excluded from scope
- Indexing-related findings (the demo is intentionally noindex/nofollow)
- Real-domain findings (demo lives at a path on rampstack.co)
- Analytics findings (no analytics on the demo by demonstration discipline)
- Backend / API findings (the waitlist form is local-only)
Mini-deck 1
Accessibility.
7 findings across axe-core and manual checks. The largest gain came from the focus-visible pattern: Phase 3 used opacity for hover/focus, which fails WCAG 2.4.7 because focus needs to be visually distinct. Adding focus-visible:ring styles to every interactive element resolved the keyboard-navigation gap in one pattern change.
axe color-contrast
Before
34
After
0
delta: -34
axe region landmark
Before
1
After
0
delta: -1
Lighthouse a11y mobile
Before
96
After
100
delta: +4
Color contrast fails AA on small uppercase text
axe-core · WCAG 1.4.3 Contrast (Minimum) (AA)
axe-core reported 34 nodes failing the color-contrast rule. Most are 12px uppercase eyebrow labels in the muted-teal accent (#5B8B85, 4.1:1 against white), which clears AA at 18px+ but fails at 12px where AA requires 4.5:1. Additional violations: white/45 placeholder spans in the footer, neutral-500 (#7B8392) helper text below KPIs, and the Step counter on the form progress indicator.
- Before
- 34 color-contrast violations reported by axe-core 4.11 against WCAG AA
- Fix
- Swapped #5B8B85 (accent) for #3F6661 (accent-deep, 6.5:1) on every small-text use: eyebrows, mono numerals on capability cards, the form Step counter. Strengthened white/45 placeholder spans to white/70. Deepened neutral helper text from #7B8392 to #4A5568. Brand-system tokens unchanged; only contextual usage rules adjusted.
- After
- 0 color-contrast violations after re-run
Demo banner content not contained by a landmark
axe-core · WCAG 1.3.1 Info and Relationships (A)
The persistent demonstration banner sits above <main> and renders as a plain <div>. axe-core flagged the content as outside any landmark, which means screen readers cannot navigate to it via landmark shortcuts.
- Before
- 1 region violation reported by axe-core
- Fix
- Wrapped the banner in <aside aria-label="Demonstration notice"> so the content has a labelled complementary landmark.
- After
- 0 region violations after re-run
Focus-visible state missing on interactive elements
manual-keyboard · WCAG 2.4.7 Focus Visible (AA)
All buttons, anchor links, and waitlist form options used hover:opacity-90 with no :focus-visible variant. Keyboard users could not reliably see which element held focus, which fails WCAG 2.4.7. The waitlist form was the most affected surface because every option button shared the same focus story.
- Before
- grep -r 'focus-visible' app/demo/threshold returned 0 occurrences
- Fix
- Added focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#3F6661] focus-visible:ring-offset-2 to every interactive element across the 8 sections plus the layout's skip link. Ring color uses accent-deep so the focus state stays brand-coherent.
- After
- All interactive elements show a 2px accent-deep ring with 2px offset on focus-visible. Manual keyboard sweep across all 8 sections shows clear focus states throughout.
Required form fields not announced as required
manual-keyboard · WCAG 4.1.2 Name, Role, Value (A)
The waitlist form's name and email fields are required (the Confirm button is disabled until both validate) but lack aria-required. Screen reader users hear a generic text input rather than a required input.
- Before
- aria-required not set on name or email inputs
- Fix
- Added aria-required="true" to both inputs.
- After
- Both fields announce as required
Form progress changes not announced to screen readers
manual-keyboard · WCAG 4.1.3 Status Messages (AA)
The Step X of 4 indicator changes when the user advances stages, but the change is not announced to assistive tech. A screen reader user advancing through the form gets no audible confirmation that progress moved.
- Before
- Progress indicator container was a plain <div> with no aria-live attribute
- Fix
- Added role="status" and aria-live="polite" to the progress indicator wrapper so stage changes are announced politely without interrupting current narration.
- After
- Stage changes announce as 'Step 2 of 4, Team' (or equivalent for current stage)
Skip-to-main-content link missing
manual-keyboard · WCAG 2.4.1 Bypass Blocks (A)
Keyboard users have to tab through the persistent banner and primary navigation before reaching main content on every page visit. A skip link is the standard remediation.
- Before
- No skip link present anywhere in the demo route
- Fix
- Added a sr-only-by-default skip link in the layout that becomes focus-visible on Tab. Targets #main-content (the existing main element id from page.tsx).
- After
- First Tab on the page focuses the skip link; pressing Enter scrolls past the banner and nav to main content
Form helper text not programmatically associated with inputs
manual-keyboard · WCAG 1.3.1 Info and Relationships (A)
The waitlist confirmation message references the email and name values, and the confirmation panel includes a 'Demonstration only' helper note. The helper note is not associated with the inputs via aria-describedby. Edge-case finding because the helper appears post-submission rather than during input.
- Before
- aria-describedby not used in form
Skills used
Mini-deck 2
Performance.
Phase 3 shipped clean: desktop Performance 100/100, LCP under a second, zero CLS. The audit caught no critical perf issues, only documented opportunities. Mobile LCP at 3.4 seconds is the most actionable item; it's deferred to Phase 5 candidacy because a focused fix (font preload, JetBrains Mono subsetting, lazy-loaded below-fold sections) belongs in dedicated optimization work, not the closing audit.
Desktop
Score
100/100
LCP
0.8s
TBT
0 ms
CLS
0
Mobile
Score
89/100
LCP
3.7s
TBT
30 ms
CLS
0
Note on mobile variance: Mobile Performance score dropped 3 points (92 -> 89) on the post-fix run. The change in shipped JS is +1 KiB (the JSON-LD script + aria attributes + focus-visible CSS classes). Lighthouse run-to-run variance on the same build is commonly 5-10 points, so the 3-point delta is within noise rather than a real regression. Re-running the audit twice more on the post-fix build produces scores in the 88-94 band with no consistent direction.
Mobile LCP at 3.4 seconds
lighthouse · Core Web Vitals: LCP target <= 2.5s
Lighthouse mobile LCP measured 3.4s on the simulated mobile profile, above the 2.5s 'good' threshold. Desktop LCP is 0.7s, so the mobile gap is the simulated network throttling rather than a fundamental render issue.
- Before
- Mobile LCP 3.4s (Lighthouse mobile, default Slow 4G simulation)
- Fix
- Documented as a Phase 5 candidate. Likely wins: preload IBM Plex Serif 600 weight used in the hero h1, subset JetBrains Mono to digits + a few letters, lazy-load below-fold sections via dynamic imports. Not applied this dispatch because the desktop performance is 100/100 and mobile is 92/100; the demo is functional.
27 KiB of unused JavaScript in initial bundle
lighthouse · Lighthouse: Reduce unused JavaScript
Lighthouse identified 27 KiB of estimated savings from unused JS in the demo route's initial bundle. Most likely Next.js framework code that the demo route does not exercise. Common at this scale; not a blocking issue.
- Before
- 27 KiB unused JS reported by Lighthouse
Skills used
Mini-deck 3
On-page.
Heading hierarchy, semantic landmarks, and meta tags were the on-page surface. Phase 3 already shipped the hierarchy and landmarks correctly; the audit added the link-preview tags (OpenGraph, Twitter Card) and an Organization JSON-LD entry that explicitly names the demonstration framing in machine-readable form.
Note on Lighthouse SEO score: 69/100 unchanged. Lighthouse's SEO category does not reward Open Graph, Twitter Card, or Organization JSON-LD; the score is dominated by the intentional noindex penalty. The OG/Twitter/JSON-LD additions improve link-preview rendering and machine-readable demonstration framing without lifting the Lighthouse score.
Open Graph meta tags missing
manual-onpage · OpenGraph protocol; SERP card / social share rendering
The demo route is intentionally noindexed, but the OG tags still inform how the URL renders when pasted into chat tools, email clients, and dev-team Slack. Phase 3 set canonical and robots metadata but not OG.
- Before
- No og:* tags in rendered HTML
- Fix
- Added openGraph metadata to the demo route page.tsx with title, description, type=website, url. Title carries the demonstration framing.
- After
- <meta property="og:title">, <meta property="og:description">, <meta property="og:type">, <meta property="og:url"> all render correctly
Twitter Card meta tags missing
manual-onpage · Twitter Card spec
Same rationale as OG tags: noindex does not affect link-preview rendering on X/Twitter, Bluesky, or other tools that read Twitter Card metadata. Phase 3 omitted these.
- Before
- No twitter:* tags in rendered HTML
- Fix
- Added twitter metadata block to demo route with card=summary, title, description.
- After
- <meta name="twitter:card">, <meta name="twitter:title">, <meta name="twitter:description"> all render correctly
Organization structured data missing with explicit fictional disclaimer
manual-onpage · Schema.org Organization; demonstration framing discipline
The demo represents a fictional organization. Adding Organization JSON-LD with disambiguatingDescription naming the fictional status is both a demonstration-discipline win (machine-readable that the entity is fictional) and an SEO skill demonstration. Lighthouse SEO category does not require structured data, but the discipline is worth showing.
- Before
- No <script type="application/ld+json"> in rendered HTML
- Fix
- Added inline JSON-LD script in layout.tsx declaring the Organization with name 'Threshold (fictional)', a disambiguatingDescription noting the demonstration framing, and a sameAs link back to the Phase 3 walkthrough.
- After
- <script type="application/ld+json"> renders with valid Organization schema and explicit fictional disclaimer in the description fields
Skills used
The reference build
Four phases. One reference build.
The catalog composing from blank brief through deployed launch microsite. Real research, real brand foundations, real working code, real audit findings, real fixes. Threshold is fictional. The methodology is not.
Phase 1
Strategy and research
Real Ahrefs research on a fictional brief. 20 keywords analyzed, 5 competitor profiles, content gaps and backlink opportunities curated.
Read →
Phase 2
Brand and design
Working brand system: 14 color tokens, 9 type tokens, 5 logo variants, 3 component primitives, 4 compositional mockups, all rendered live.
Read →
Phase 3
Build and ship
The deployed launch microsite at /demo/threshold. Real Next.js, 8 sections, live multi-step waitlist form. Persistent demonstration framing throughout.
Read →
Phase 4
Audit and optimize
This walkthrough. 12 findings; 9 fixed, 1 documented, 2 deferred. axe 35 → 0, Lighthouse a11y 96 → 100.
You are here · complete
Demonstration framing
Threshold is a fictional product built to illustrate how the catalog composes from blank brief through research, brand, build, and audit. The audit on this page is real (real axe-core, real Lighthouse, real manual checks); the product itself is not. The fictional product's tagline, "Know how new users actually get to value.", drove every audit decision, including which fixes to prioritise (anything that obstructed comprehension of the tagline within 5 seconds was a critical fix).
Skills used (Phase 4)
Tooling used (Phase 4)
- @axe-core/cli 4.11 for WCAG 2.0/2.1 A and AA automated checks
- Lighthouse for performance, accessibility, best-practice checks, and SEO scoring (desktop + mobile presets)
- Manual keyboard sweeps for focus-visible, tab order, and form interaction patterns
- Manual on-page review for heading hierarchy, semantic landmarks, OG / Twitter / JSON-LD presence
Raw audit reports preserved at audit-artifacts/threshold-phase-4/ for reproducibility.