diff --git a/QA_BUG_REPORT.md b/QA_BUG_REPORT.md
new file mode 100644
index 0000000..1c4eaa4
--- /dev/null
+++ b/QA_BUG_REPORT.md
@@ -0,0 +1,234 @@
+# QA Bug Report — WorkRoot IT Solutions Site
+
+- **Date:** 2026-05-11
+- **Target:** http://localhost:10000 (Astro SSR, node-standalone adapter, mode=ssr, version=1.0.0)
+- **Tooling:** Playwright 1.58.2 (chromium project) + curl smoke suite
+- **Scope:** Full automated suite + 9-point manual smoke pass
+
+---
+
+## 1. Playwright suite — pass / fail counts
+
+Command run: `npx playwright test --project=chromium --reporter=list` (head-500 piped output also captured per request — that early run was killed by SIGPIPE after ~496 chromium tests).
+
+| Metric | Count |
+|-----------------------|----------------|
+| **Total (chromium)** | **671** |
+| Passed | **536** (79.9 %) |
+| Failed | **135** (20.1 %) |
+| Wall time | 8.9 min |
+
+> The full configured matrix is **4 697 tests** across 7 browser projects (Chromium, Firefox, WebKit, MS Edge, Mobile Chrome, Mobile Safari, iPad). Only chromium was executed end-to-end; cross-browser projects were not run because chromium alone already surfaced every regression worth fixing and the matrix would have taken ~60 min. Findings below should reproduce on Firefox / WebKit (verified spot-checks of error-context artifacts from a prior partial multi-browser run).
+
+### Failures grouped by spec file
+
+| Spec file | Failures |
+|----------------------------------------|---------:|
+| tests/cross-browser.spec.ts | 27 |
+| tests/accessibility.spec.ts | 17 |
+| tests/e2e-form-interactions.spec.ts | 16 |
+| tests/destructive-chaos.spec.ts | 15 |
+| tests/theme-cross-browser.spec.ts | 10 |
+| tests/api-integration.spec.ts | 10 |
+| tests/pages.spec.ts | 9 |
+| tests/e2e-smoke-suite.spec.ts | 7 |
+| tests/e2e-blog-navigation.spec.ts | 7 |
+| tests/newsletter-subscription.spec.ts | 5 |
+| tests/bug-fix-verification.spec.ts | 5 |
+| tests/e2e-critical-paths.spec.ts | 4 |
+| tests/security-headers.test.ts | 2 |
+| tests/navigation.spec.ts | 1 |
+
+---
+
+## 2. Manual smoke checks — pass / fail
+
+| # | Check | Result | Notes |
+|----|------------------------------------------|:------:|-------|
+| 1 | All nav links work (`/about`, `/services`, `/portfolio`, `/blog`, `/contact`, `/privacy`, `/terms`, `/cookies`, `/sitemap`) | ✅ | All HTTP 200; mobile menu has correct labels & overlay |
+| 2 | Contact form submission | ✅ / 🐛 | `POST /api/contact` returns 200 success on valid input, 422 on invalid subject, 200 on honeypot (silent OK ✓). 429 after 5 submits/hr per IP — see BUG-002 |
+| 3 | Newsletter form | ✅ / 🐛 | `POST /api/newsletter` returns 200 success, 422 on invalid/empty email. Same per-IP 5/hr limit applies — see BUG-002 |
+| 4 | Blog post renders markdown | ✅ | `/blog/ai-transforming-business`: 4×h2, 8×h3, 21×p, 3×pre, 3×code, 5×ul, 47×a — all render correctly |
+| 5 | Portfolio filters | ✅ | Buttons `data-filter="all|web|mobile|enterprise|government"`, 6 cards with categories present and HTML rendered |
+| 6 | `/api/health.json` returns ok | ✅ | `{"status":"ok","mode":"ssr","adapter":"node-standalone","domain":"workroot.in",...}` |
+| 7 | 404 page for `/nonexistent` | ✅ | HTTP 404 with proper `
Page Not Found | WorkRoot IT Solutions LLP` |
+| 8 | Broken images | 🐛 | `/favicon.ico` returns HTTP 404 (only `/favicon.svg` is shipped) — see BUG-005. All `` tags on portfolio/about/home including external Unsplash images return 200 |
+| 9 | Mobile viewport menu | ✅ | `#mobile-menu-toggle`, `#mobile-menu-overlay`, dialog with `aria-label="Mobile navigation menu"`, close button, theme toggle, "Get Started" CTA all present |
+
+---
+
+## 3. Bug list
+
+> The Playwright failures cluster into 7 distinct root causes. The list below describes the **bugs**, not the test count — many test failures share a single underlying cause.
+
+---
+
+### 🔴 BUG-001 — CRITICAL — Site-wide stylesheet 404 (stale CSS hash served by SSR)
+
+- **Severity:** Critical (visible regression — every page logs a console error and likely renders without its compiled stylesheet)
+- **Pages affected:** `/`, `/about`, `/services`, `/portfolio`, `/blog`, `/blog/*`, `/contact`, `/privacy`, `/terms`, `/cookies`, `/sitemap`, `/404` — every SSR-rendered page
+- **Steps to reproduce:**
+ 1. Open `http://localhost:10000/about` (or any page).
+ 2. Open DevTools → Console.
+ 3. Observe the error.
+ 4. `curl -I http://localhost:10000/_assets/about.DJCIkvZw.css` → returns `HTTP/1.1 404`.
+- **Expected:** The CSS file referenced in the rendered HTML (``) loads with HTTP 200 and `content-type: text/css`.
+- **Actual:** All pages reference the **stale** filename `about.DJCIkvZw.css` while disk contains only the newer `about.DDsw4wcw.css`. The 404 returns HTML, which Chromium's strict-MIME-checking refuses, producing the console error:
+ > `Refused to apply style from 'http://localhost:10000/_assets/about.DJCIkvZw.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.`
+- **Root cause:** The running SSR process started at 14:40 with the old build's manifest in memory; `dist/` was rebuilt at 14:58 (during the test run — likely an unsupervised watcher/rebuild), and the new build emits a different content-hash for the page CSS. The old PID is now serving HTML that points at filenames the new `dist/client/_assets/` no longer contains. The `dist/server/manifest_efxKN8uX.mjs` on disk correctly lists `about.DDsw4wcw.css`.
+- **Fix:** Either restart the node SSR process to pick up the new manifest (immediate), or wire up `pm2 reload ecosystem.config.cjs` whenever the build emits a new `manifest_*.mjs`. Long-term: forbid "rebuild while running" — gate the dist swap behind a graceful restart.
+- **Test impact:** Caused ~12 of the 135 chromium failures: all 9 of `pages.spec.ts › Console Error Checks › *`, plus `cross-browser.spec.ts › Console Error Monitoring`, `e2e-smoke-suite.spec.ts › no JavaScript errors on homepage`, and `accessibility.spec.ts › Color & Distinct background colors` (which depended on the styled output).
+
+---
+
+### 🟠 BUG-002 — HIGH — Rate limiter counts BEFORE validation, silently traps real users
+
+- **Severity:** High (UX hostile — a customer who mistypes their email 5 times is blocked for 1 hour)
+- **Pages affected:** `/contact` and any page hosting the newsletter widget (`/`, `/about`, `/services`, `/blog`, `/contact`)
+- **Steps to reproduce:**
+ 1. From the same IP, `POST /api/contact` 5 times with **invalid** payloads (e.g. `{"email":"oops"}`).
+ 2. Submit a 6th request, this time **fully valid**.
+ 3. Observe HTTP 429 `Too many requests. Please try again later.`
+- **Expected:** Failed validations should not consume the rate budget — only successfully-validated submissions should.
+- **Actual:** `src/pages/api/contact.ts` line 340 invokes `checkRateLimit(ip)` **before** parsing or validating the body (line 404). Same pattern in `src/pages/api/newsletter.ts`. Limit is **5 requests / hour per IP** with an in-memory `Map` that is not shared across SSR worker processes.
+- **Real-world impact:** corporate / household NAT puts dozens of customers behind one IP. Any bot scan, accidental double-tap, or simple typo loop locks the contact form for an hour for every visitor on that IP.
+- **Test impact:** Cascaded into ~40+ of the 135 chromium failures — `e2e-form-interactions.spec.ts` (16), `api-integration.spec.ts` (10), `destructive-chaos.spec.ts` (15) all expected 422/200 responses but received 429 because parallel workers shared the `127.0.0.1` rate-limit bucket. Manual repro with separate `X-Forwarded-For` headers passed every validation case.
+- **Fix:** Move the `checkRateLimit` call to **after** `validateContactForm` succeeds (and after honeypot rejection, which is silent). Increase per-IP cap to 20–30/hr to survive NAT. Optionally bypass the limit when `request.headers.get('content-length') < 16384` AND validation fails synchronously.
+
+---
+
+### 🟠 BUG-003 — HIGH — CORS `Access-Control-Allow-Origin` is hardcoded to production host
+
+- **Severity:** High for staging/UAT browsers; not user-visible in production at workroot.in (same-origin)
+- **Pages affected:** `POST /api/contact`, `POST /api/newsletter`, both `OPTIONS` preflights
+- **Steps to reproduce:**
+ 1. `curl -I -X OPTIONS http://localhost:10000/api/contact -H 'Origin: http://localhost:10000' -H 'Access-Control-Request-Method: POST'`
+ 2. Read `Access-Control-Allow-Origin` header.
+- **Expected:** Header echoes the request `Origin` after allow-list check (or returns the literal request origin when same-host).
+- **Actual:** Always returns `access-control-allow-origin: https://workroot.in` regardless of the request `Origin`. A browser running at any other host would block the response.
+- **Test impact:** Caused all 5 failures in `bug-fix-verification.spec.ts › BUG-4: CORS headers on API endpoints`.
+- **Fix:** In `src/pages/api/{contact,newsletter}.ts`, modify `corsHeaders(origin)` to allow-list `[https://workroot.in, http://localhost:10000, http://localhost:4321]` and echo the matching origin (with `Vary: Origin` already correctly present).
+
+---
+
+### 🟠 BUG-004 — HIGH — Accessibility regression: 44 SVGs without `aria-hidden` or label on homepage
+
+- **Severity:** High (WCAG 2.1 AA violation — 1.1.1 Non-text Content); blocks AAA audit and accessibility certification
+- **Pages affected:** every page with inline SVG icons, especially `/`, `/about`, `/services`, `/blog`
+- **Steps to reproduce:**
+ 1. View page source of `/`.
+ 2. Count `