# QA, ACCESSIBILITY & PERFORMANCE CHECKLIST

## 1. WCAG 2.1 AA - implemented in this build

- [x] **1.1.1** All informative images have descriptive `alt`; decorative logo in
      the footer's right column uses `alt="" role="presentation"`; all inline
      SVG icons are `aria-hidden="true"` with a text label alongside.
- [x] **1.3.1** Semantic structure: one `<h1>`, ordered `h2` → `h3`, `<main>`,
      `<nav aria-label>`, `<section aria-labelledby>`, `<figure>/<figcaption>`
      for testimonials, real `<ol>` breadcrumb.
- [x] **1.4.3** Contrast: body `#000` on `#fff` (21:1); `--cwc-green-ink #0b6b41`
      used for green text on white (≥ 5.4:1). Brand `#12bf6b` is used **only** as
      a background behind white bold text ≥ 18.66 px (3.4:1 - passes as large
      text and for UI components under 1.4.11) or as a decorative fill.
- [x] **1.4.4 / 1.4.10** Layout is fluid to 320 px; no horizontal scroll at
      400 % zoom; all font sizes in `rem`/`clamp`.
- [x] **1.4.11** Non-text contrast: input borders `#c9d2ce` on white, focus ring
      3 px `#0b6b41`.
- [x] **2.1.1 / 2.1.2** Everything operable by keyboard; menu and FAQ use real
      `<button>`s; no traps.
- [x] **2.4.1** Skip link to `#content`.
- [x] **2.4.3** DOM order matches visual order.
- [x] **2.4.7** Visible `:focus-visible` outline everywhere (never `outline:none`).
- [x] **2.5.5 / 2.5.8** Tap targets ≥ 44×44 px (buttons `min-height:48px`,
      mobile sticky bar 52 px, nav links 44 px on mobile).
- [x] **3.3.1 / 3.3.2 / 3.3.3** Visible labels on every field, helper text,
      inline error messages tied to the field, `aria-invalid`, error alert with
      `role="alert"`, success with `role="status"`, focus moves to the first
      invalid field.
- [x] **4.1.2** `aria-expanded` on menu, submenu, FAQ and local-team toggles;
      `aria-controls` on all of them; `aria-current="page"` on the active nav item.
- [x] **2.3.3** `prefers-reduced-motion` disables transitions and smooth scroll.

### To verify after CMS integration

- [ ] Screen reader pass: NVDA + Firefox, VoiceOver + Safari (iOS) - menu,
      FAQ, form errors, sticky bar.
- [ ] Automated scan: axe DevTools + Lighthouse a11y ≥ 95, zero criticals.
- [ ] Keyboard-only run-through of the full conversion path.
- [ ] Confirm CMS-authored copy hasn't introduced skipped heading levels or
      "click here" link text.
- [ ] Confirm the Klaviyo footer embed (third-party) passes labels/contrast -
      it is the most likely AA failure on the page.
- [ ] Colour-contrast re-check on any new brand colour a CMS editor introduces.

## 2. Performance

Budgets (mobile, 4G, Moto G-class):

| Metric | Budget | How this build gets there |
|---|---|---|
| LCP | **≤ 2.0 s** | Hero image preloaded with `fetchpriority="high"`, critical CSS inlined, no render-blocking JS |
| CLS | **≤ 0.05** | Width/height on every image, no late-injected banners, sticky CTA is `position:fixed` |
| INP | **≤ 200 ms** | ~5 KB of JS total, delegated listeners, passive scroll handler |
| TBT | **≤ 150 ms** | No jQuery/Bootstrap/Slick on this page |
| Total page weight | **≤ 600 KB** | Images are the only heavy asset; logos lazy-loaded |
| Requests | **≤ 25** | Icons inlined as SVG, 2 CSS + 2 JS files |

Actions to complete at integration:
- [ ] Export hero as WebP with `<picture>` + JPG fallback and `srcset`
      (640/960/1600 w). Expected saving ~60%.
- [ ] Preload the two WOFF2 weights actually used; `font-display:swap` already set.
- [ ] Serve `cwc-*.css/js` with `Cache-Control: public, max-age=31536000, immutable`
      and hashed filenames.
- [ ] Enable Brotli/gzip.
- [ ] Confirm the legacy global bundle (Bootstrap/jQuery/Slick/Font Awesome) is
      **not** loaded on this page - that alone is ~400 KB. If the template forces
      it, this is the single biggest remaining win.
- [ ] Defer Tawk.to chat and Klaviyo until first interaction or 3 s idle.
- [ ] Re-run Lighthouse + CrUX after 28 days.

## 3. Cross-browser / device matrix

| Priority | Browser / device | Checks |
|---|---|---|
| P1 | Chrome + Safari, iPhone (iOS 16+), 375 & 414 px | Sticky CTA doesn't cover the footer; `tel:` dials; menu tap targets; form keyboard types (`tel`, `email`) |
| P1 | Chrome, Android (Samsung Internet too), 360-412 px | Same, plus `:has()` fallback (Samsung Internet < 20 lacks `:has()` - header phone visibility rule degrades gracefully, verify) |
| P1 | Chrome desktop 1440 / 1920 | Header pixel parity, hover submenus, hero split layout |
| P2 | Safari macOS | `color-mix()` support (Safari 16.2+); fallback = plain white hover |
| P2 | Firefox desktop | Focus outlines, grid gaps |
| P2 | Edge desktop | Same as Chrome |
| P3 | iPad 768/1024 | Burger threshold at 1100 px, two-column collapse |
| P3 | IE11 | **Not supported** - page degrades to a readable single column; confirm the client accepts this (live site already relies on modern CSS) |

Known-risk items to check first: `:has()` in the header rules (inherited from the
live stylesheet), `color-mix()` in the sticky-bar hover, `position:sticky` under
legacy CMS wrappers with `overflow:hidden` on an ancestor.


## 4. Pass-2 interactive components - accessibility & motion checks

### 4.1 `prefers-reduced-motion: reduce`

Set the OS/browser preference (Chrome DevTools → Rendering → Emulate CSS
`prefers-reduced-motion`) and confirm **all** of the following:

- [ ] Hero frost drift, shimmer top edge and the H1 underline draw are static.
- [ ] Scroll cue does not bob.
- [ ] `data-reveal` elements are simply visible - no fade or rise.
- [ ] Counters in the figures block show their final values instantly.
- [ ] Cost estimator result updates instantly (no count animation).
- [ ] Timeline connecting line is fully drawn on load.
- [ ] Logo marquee stops and renders as a static wrapped grid.
- [ ] Depot dots on the map do not pulse.
- [ ] Sticky CTA appears/disappears with no slide transition.
- [ ] Reading progress bar still tracks position but does not animate width.
- [ ] FAQ panels open/close with no height transition.

### 4.2 Keyboard operability

- [ ] Tab order runs: skip link → header → hero CTAs → quote form fields →
      consent → submit → scroll cue → trust proof points → breadcrumb →
      estimator inputs → estimator CTA → video card → plan CTAs → sector chips →
      postcode input + button → FAQ buttons → "still have a question" CTAs →
      final band CTAs → sticky CTA → footer.
- [ ] Estimator: both range sliders adjust with Arrow/Home/End; the paired number
      inputs accept typed values and both stay in sync.
- [ ] Sector chips are real `<button>`s - Enter and Space toggle them and
      `aria-pressed` flips.
- [ ] Trust proof points are reachable and activate with Enter/Space.
- [ ] FAQ: Enter/Space toggles; `aria-expanded` matches the visible state; a
      collapsed panel is not reachable by Tab or by the screen-reader cursor.
- [ ] Video poster card is a `<button>`; after activation focus moves to the
      `<video>` element.
- [ ] The sticky CTA never traps focus and never covers the focused element -
      tab to the final band CTAs and confirm they are not obscured.
- [ ] Every focused control shows a visible `:focus-visible` ring against both
      light and dark backgrounds.

### 4.3 Contrast (target: WCAG 2.1 AA, 4.5:1 body / 3:1 large text and UI)

- [ ] Dark problem band: card body text on `--cwc-night-800`.
- [ ] Ice-blue accents (`--cwc-ice`) are used for **borders, dots and large
      display text only** - verify no small body copy relies on it.
- [ ] Estimator result figure and its disclaimer paragraph on the night band.
- [ ] Comparison grid: the "Fixed Season Price" highlighted column header.
- [ ] Sector chips in default, hover, focus and `aria-pressed="true"` states.
- [ ] Final CTA band: headline, reassurance line and both button labels over the
      gradient + frost texture (test at the darkest and lightest points).
- [ ] Sticky CTA text and buttons.

### 4.4 Content integrity

- [ ] The estimator disclaimer is visible without interaction and states the
      figure is an illustration from the visitor's own numbers, the 4-hour
      assumption is labelled, and it is not a CWC claim, quote or estimate.
- [ ] No testimonials, star ratings, review counts or invented statistics
      anywhere on the page.
- [ ] FAQ copy still matches the FAQPage JSON-LD question and answer text
      one-for-one.
- [ ] Header and footer markup byte-identical to the pre-pass version.

### 4.5 No-JS behaviour

Disable JavaScript and confirm:

- [ ] All content is visible (no `data-reveal` element stays hidden).
- [ ] The cost estimator section is hidden entirely rather than broken.
- [ ] All FAQ panels are open.
- [ ] Counters show their correct final values.
- [ ] Timeline line is drawn; marquee shows a static logo grid.
- [ ] Every CTA is still a working link (`#quote`, `tel:`).

## Fix pass - verified at 1440x800 and 390x844 (headless, full-page scroll)

- [x] Reveal system fail-safe: threshold 0, rootMargin `0px 0px -5% 0px`, max 400ms /
      14px, stagger capped at 240ms, plus load + 1200ms + scroll sweeps. Zero
      elements below 0.9 opacity after a full scroll at either breakpoint.
- [x] Hero: H1, subhead, both CTAs and the top of the quote card visible above the
      fold at 1440x800; cinematic navy scrim replaces the flat green wash.
- [x] Assurance chips meet 4.5:1 on the hero footage.
- [x] "Why choose our gritting contractors" = 3 columns x 2 rows, equal heights.
- [x] Trust strip: accreditation image un-filtered on a light chip; proof points 2x2.
- [x] Coverage map on a clean white panel, shown whole; misplaced depot dots removed.
- [x] Counters settle on 40+, 35+, 0C, 100% even when scrolled past or backgrounded.
- [x] FAQ tail card renders; no dead space after the last question.
- [x] Floating CTA appears only after the hero and is suppressed over #quote,
      #costCalc, the coverage section and the final band.
- [x] Mobile 390x844: no horizontal scroll (scrollWidth == 390), sliders usable,
      sticky bar never covers the submit button.
- [x] Header/footer markup unchanged (diff vs includes is whitespace/comments only).
