2026-05-18 13:55:06 -05:00
import SEO from '@/components/SEO'
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
import { websiteLd } from '@/lib/seo'
2026-05-17 16:39:54 -05:00
import { Card , CardContent , CardDescription , CardHeader , CardTitle } from '@/components/ui/Card'
2026-05-12 01:04:17 -05:00
import { services } from '@/data/services'
2026-05-12 02:31:23 -05:00
import { industries } from '@/data/industries'
2026-05-17 21:44:48 -05:00
import { Link } from 'react-router-dom'
2026-05-26 13:36:16 -05:00
import { ArrowRight , MapPin , MessageCircle , Users , LifeBuoy , GraduationCap , Link as LinkIcon , Wifi , Network , Headphones , UserCheck , Activity , ShieldCheck , HeartPulse , ShoppingCart , Factory , Landmark , Building2 } from 'lucide-react'
2026-05-17 15:41:22 -05:00
// Icon map for industries - converts icon string to lucide component
const industryIcons = {
'heart-pulse' : HeartPulse ,
'shopping-cart' : ShoppingCart ,
factory : Factory ,
landmark : Landmark ,
}
2026-05-12 01:04:17 -05:00
2026-05-25 18:33:19 -05:00
const serviceAccentStyles = [
{
card : 'border-t-[3px] border-t-primary-blue hover:border-primary-blue/50' ,
iconWrap : 'bg-sky-50' ,
icon : 'text-primary-blue' ,
link : 'text-primary-blue hover:text-primary-navy' ,
} ,
{
card : 'border-t-[3px] border-t-teal-500 hover:border-teal-500/50' ,
iconWrap : 'bg-teal-50' ,
icon : 'text-teal-600' ,
link : 'text-teal-700 hover:text-primary-navy' ,
} ,
{
card : 'border-t-[3px] border-t-cyan-400 hover:border-cyan-400/60' ,
iconWrap : 'bg-cyan-50' ,
icon : 'text-cyan-600' ,
link : 'text-cyan-700 hover:text-primary-navy' ,
} ,
{
card : 'border-t-[3px] border-t-accent-gold hover:border-accent-gold/60' ,
iconWrap : 'bg-amber-50' ,
icon : 'text-amber-600' ,
link : 'text-amber-700 hover:text-primary-navy' ,
} ,
]
const industryAccentStyles = [
{ card : 'border-t-[3px] border-t-rose-400 hover:border-rose-400/60' , iconWrap : 'bg-rose-50' , icon : 'text-rose-600' } ,
{ card : 'border-t-[3px] border-t-primary-blue hover:border-primary-blue/50' , iconWrap : 'bg-sky-50' , icon : 'text-primary-blue' } ,
{ card : 'border-t-[3px] border-t-accent-gold hover:border-accent-gold/60' , iconWrap : 'bg-amber-50' , icon : 'text-amber-600' } ,
{ card : 'border-t-[3px] border-t-teal-500 hover:border-teal-500/50' , iconWrap : 'bg-teal-50' , icon : 'text-teal-600' } ,
]
2026-05-12 01:04:17 -05:00
const Home = ( ) => {
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
// Single Organization entity — no LocalBusiness/address markup, since Queue North
// does not publish a physical location.
2026-05-17 20:03:42 -05:00
const organizationLd = {
'@context' : 'https://schema.org' ,
'@type' : 'Organization' ,
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
'@id' : 'https://queuenorth.com/#organization' ,
2026-05-17 20:03:42 -05:00
name : 'Queue North Technologies' ,
url : 'https://queuenorth.com' ,
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
image : 'https://queuenorth.com/assets/og-image.png' ,
telephone : '+1-321-730-8020' ,
2026-05-18 14:02:12 -05:00
logo : {
'@type' : 'ImageObject' ,
url : 'https://queuenorth.com/logo.png' ,
} ,
2026-05-27 14:43:50 -05:00
description : 'Veteran-owned 8x8 Certified Partner and Cisco Certified Partner providing business phone systems, UCaaS, contact center, IT support, and networking solutions.' ,
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
areaServed : {
'@type' : 'Country' ,
name : 'United States' ,
2026-05-17 20:03:42 -05:00
} ,
2026-05-18 14:02:12 -05:00
contactPoint : [
{
'@type' : 'ContactPoint' ,
telephone : '+1-321-730-8020' ,
contactType : 'customer service' ,
areaServed : 'US' ,
} ,
{
'@type' : 'ContactPoint' ,
telephone : '+1-888-656-2850' ,
contactType : 'customer service' ,
contactOption : 'TollFree' ,
areaServed : 'US' ,
} ,
] ,
sameAs : [
'https://www.linkedin.com/company/queue-north-technologies-llc' ,
'https://www.facebook.com/QueueNorth' ,
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
'https://www.instagram.com/queue_north/' ,
2026-05-18 14:02:12 -05:00
] ,
2026-05-17 20:03:42 -05:00
}
2026-05-12 01:04:17 -05:00
return (
2026-05-17 17:11:29 -05:00
< >
2026-05-18 13:55:06 -05:00
< SEO
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
title = "Business Phone, UCaaS & IT Services | Queue North"
description = "Veteran-owned 8x8 and Cisco Certified Partner delivering business phone systems, UCaaS, contact center, IT support, and networking. 25+ years."
2026-05-18 13:55:06 -05:00
url = "https://queuenorth.com"
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
jsonLd = { [ organizationLd , websiteLd ] }
2026-05-18 13:55:06 -05:00
/ >
2026-05-12 01:04:17 -05:00
{ /* Hero Section */ }
2026-05-18 13:12:18 -05:00
< section className = "relative isolate overflow-hidden bg-primary-navy text-white" >
< div className = "absolute inset-0 -z-10" >
< img
src = "/assets/hero-tech.webp"
alt = "Queue North technician working inside a communications rack"
className = "h-full w-full object-cover object-center"
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
loading = "eager"
fetchPriority = "high"
decoding = "async"
2026-05-18 13:12:18 -05:00
/ >
< div className = "absolute inset-0 bg-primary-navy/65" / >
< div className = "absolute inset-0 bg-gradient-to-r from-primary-navy via-primary-navy/90 to-primary-navy/25" / >
< / div >
< div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20 md:py-24 lg:py-28" >
2026-05-26 12:26:13 -05:00
< div className = "max-w-5xl" >
2026-05-18 13:12:18 -05:00
< div className = "inline-flex items-center gap-2 rounded-md border border-white/20 bg-white/10 px-3 py-2 text-xs font-semibold uppercase tracking-wide text-primary-cyan" >
Veteran - owned communications and infrastructure partner
2026-05-12 01:04:17 -05:00
< / div >
2026-05-26 12:31:26 -05:00
< h1 className = "mt-6 max-w-4xl text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold leading-[1.05]" >
Business communications built for uptime , support , and accountability .
2026-05-18 13:12:18 -05:00
< / h1 >
< p className = "mt-6 text-lg md:text-2xl text-white/80 max-w-2xl leading-relaxed" >
Business phone , contact center , network , and IT support built around one accountable implementation partner .
< / p >
< div className = "mt-8 flex flex-col sm:flex-row gap-3" >
fix(a11y): visible focus indicator, aria-expanded, and six Label-in-Name failures
Batch 10, and only three of its six issues were real. The other three are
answered on the tracker with the arithmetic.
#189 — focus indicator. Footer and Home carried zero focus styling across 17
links. Added one global :focus-visible rule rather than sprinkling classes,
because the issue's own scope was "all interactive <a> and <Link> elements".
Not the ring the issue asked for. It suggested ring-primary-cyan; #22D3EE is
6.26:1 on navy and 2.38:1 on white, so on this light-first design that ring
would have failed WCAG 1.4.11 across most of the site. Two rings instead — white
inside, navy outside — so the white carries the dark bands and the navy carries
the light sections. Worst case across every background in the palette is
13.62:1. box-shadow so both rings follow each element's own border-radius, plus
a transparent outline for forced-colors mode.
#192 — aria-expanded={isOpen} and aria-controls on the mobile nav trigger, and
the id on SheetContent it now points at. Confirmed as "false" in the
prerendered HTML.
#193 — INVERTED. The issue asked for aria-labels to be added consistently to
hero CTAs. The aria-labels already there were WCAG 2.5.3 Label in Name failures,
Level A: "Schedule a consultation" is not a superset of the visible "Schedule
Consultation", so a voice-control user saying what they see cannot activate the
link. Doing what the issue asked would have spread a Level A failure. Removed
the six that broke it; kept the eight that genuinely add context and do contain
their visible text, including the icon-only header logo link that needs one.
Audit now reports 0 failures across Home, Services, Footer and Header.
Verified: npm run build; the ring is in the built CSS, aria-expanded="false" and
id="mobile-nav-content" are in the prerendered HTML, all six removed labels are
absent from it, and the visible CTA text still renders.
NOT verified: nothing has been walked keyboard-only in a browser, and no screen
reader has been used. That gap is recorded in docs/qa/ClaudeQACoverage.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 02:31:13 -05:00
< Link to = "/contact#contact-form" className = "inline-flex w-full sm:w-auto h-11 items-center justify-center gap-2 rounded-md bg-white px-5 text-sm font-semibold text-primary-navy hover:bg-section-alt transition-colors" >
2026-05-18 13:12:18 -05:00
Schedule Consultation
< ArrowRight className = "h-4 w-4" aria - hidden = "true" / >
< / Link >
fix(a11y): visible focus indicator, aria-expanded, and six Label-in-Name failures
Batch 10, and only three of its six issues were real. The other three are
answered on the tracker with the arithmetic.
#189 — focus indicator. Footer and Home carried zero focus styling across 17
links. Added one global :focus-visible rule rather than sprinkling classes,
because the issue's own scope was "all interactive <a> and <Link> elements".
Not the ring the issue asked for. It suggested ring-primary-cyan; #22D3EE is
6.26:1 on navy and 2.38:1 on white, so on this light-first design that ring
would have failed WCAG 1.4.11 across most of the site. Two rings instead — white
inside, navy outside — so the white carries the dark bands and the navy carries
the light sections. Worst case across every background in the palette is
13.62:1. box-shadow so both rings follow each element's own border-radius, plus
a transparent outline for forced-colors mode.
#192 — aria-expanded={isOpen} and aria-controls on the mobile nav trigger, and
the id on SheetContent it now points at. Confirmed as "false" in the
prerendered HTML.
#193 — INVERTED. The issue asked for aria-labels to be added consistently to
hero CTAs. The aria-labels already there were WCAG 2.5.3 Label in Name failures,
Level A: "Schedule a consultation" is not a superset of the visible "Schedule
Consultation", so a voice-control user saying what they see cannot activate the
link. Doing what the issue asked would have spread a Level A failure. Removed
the six that broke it; kept the eight that genuinely add context and do contain
their visible text, including the icon-only header logo link that needs one.
Audit now reports 0 failures across Home, Services, Footer and Header.
Verified: npm run build; the ring is in the built CSS, aria-expanded="false" and
id="mobile-nav-content" are in the prerendered HTML, all six removed labels are
absent from it, and the visible CTA text still renders.
NOT verified: nothing has been walked keyboard-only in a browser, and no screen
reader has been used. That gap is recorded in docs/qa/ClaudeQACoverage.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 02:31:13 -05:00
< Link to = "/services" className = "inline-flex w-full sm:w-auto h-11 items-center justify-center rounded-md border border-white/45 px-5 text-sm font-semibold text-white hover:bg-white/10 transition-colors" >
2026-05-18 13:12:18 -05:00
View Services
< / Link >
2026-05-12 01:04:17 -05:00
< / div >
< / div >
< / div >
< / section >
2026-05-18 13:12:18 -05:00
{ /* Partner Proof */ }
< section className = "bg-white border-b border-border py-6" >
< div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" >
2026-05-27 12:51:08 -05:00
< div className = "grid grid-cols-2 gap-x-4 gap-y-6 lg:grid-cols-4" >
< div className = "flex flex-col items-center gap-2 lg:flex-row lg:gap-3 lg:justify-start" >
2026-05-27 13:28:13 -05:00
< span className = "flex h-16 w-20 shrink-0 items-center justify-center rounded-md border border-border bg-white p-2" >
2026-05-25 18:11:51 -05:00
< img
src = "/assets/brand/8x8-logo-dark-gray.png"
alt = "8x8 Certified Partner logo"
2026-05-27 13:28:13 -05:00
className = "h-full w-full object-contain"
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
loading = "lazy"
decoding = "async"
2026-05-25 18:11:51 -05:00
/ >
2026-05-18 13:12:18 -05:00
< / span >
2026-05-27 12:51:08 -05:00
< span className = "text-sm font-semibold leading-tight text-primary-navy text-center lg:text-left" > 8 x8 Certified Partner < / span >
2026-05-17 22:05:08 -05:00
< / div >
2026-05-27 12:51:08 -05:00
< div className = "flex flex-col items-center gap-2 lg:flex-row lg:gap-3 lg:justify-start" >
2026-05-27 21:35:12 -05:00
< span className = "flex h-16 w-20 shrink-0 items-center justify-center rounded-md border border-border bg-white p-1 overflow-hidden" >
2026-05-25 18:11:51 -05:00
< img
2026-05-26 12:22:11 -05:00
src = "/assets/brand/cisco-partner-logo-midnight.svg"
alt = "Cisco Partner certification logo"
2026-05-27 21:35:12 -05:00
className = "h-full w-full object-contain scale-[1.5]"
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
loading = "lazy"
decoding = "async"
2026-05-25 18:11:51 -05:00
/ >
2026-05-18 13:12:18 -05:00
< / span >
2026-05-27 12:51:08 -05:00
< span className = "text-sm font-semibold leading-tight text-primary-navy text-center lg:text-left" > Cisco Certified Partner < / span >
2026-05-17 22:05:08 -05:00
< / div >
2026-05-27 12:51:08 -05:00
< div className = "flex flex-col items-center gap-2 lg:flex-row lg:gap-3 lg:justify-start" >
2026-06-14 16:08:29 -05:00
< span className = "flex h-16 w-20 shrink-0 items-center justify-center rounded-md border border-border bg-white p-1" >
< img
2026-06-14 16:17:42 -05:00
src = "/assets/brand/veteran-owned-certified-mark.webp"
alt = "SBA logo for Veteran-Owned Certified"
2026-06-14 16:08:29 -05:00
className = "h-full w-full object-contain"
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
loading = "lazy"
decoding = "async"
2026-06-14 16:08:29 -05:00
/ >
2026-05-18 13:12:18 -05:00
< / span >
2026-06-14 16:08:29 -05:00
< span className = "text-sm font-semibold leading-tight text-primary-navy text-center lg:text-left" > Veteran - Owned Certified < / span >
2026-05-17 14:45:27 -05:00
< / div >
2026-05-27 12:51:08 -05:00
< div className = "flex flex-col items-center gap-2 lg:flex-row lg:gap-3 lg:justify-start" >
2026-05-27 13:28:13 -05:00
< span className = "font-numeric flex h-16 w-20 shrink-0 items-center justify-center rounded-md border border-border bg-white text-2xl font-semibold text-primary-navy" >
2026-05-26 13:36:16 -05:00
25 +
< / span >
2026-05-27 12:51:08 -05:00
< span className = "text-sm font-semibold leading-tight text-primary-navy text-center lg:text-left" > Years Experience < / span >
2026-05-17 14:45:27 -05:00
< / div >
< / div >
< / div >
< / section >
2026-05-12 01:04:17 -05:00
{ /* Services Section */ }
< section className = "bg-background py-16 md:py-24" >
2026-05-17 17:11:29 -05:00
< div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" >
2026-05-12 01:04:17 -05:00
< div className = "text-center mb-12" >
2026-05-17 14:49:01 -05:00
< h2 className = "text-3xl md:text-4xl font-bold text-primary-navy mb-4" > What We Handle < / h2 >
2026-05-12 01:04:17 -05:00
< p className = "text-xl text-soft-text max-w-2xl mx-auto" >
2026-05-17 14:49:01 -05:00
From phones to firewalls , we keep your business running
2026-05-12 01:04:17 -05:00
< / p >
< / div >
< div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" >
2026-05-25 18:33:19 -05:00
{ services . map ( ( service , index ) => {
const accent = serviceAccentStyles [ index % serviceAccentStyles . length ]
return (
< Card key = { service . id } className = { ` h-full transition-all hover:shadow-md ${ accent . card } ` } >
< CardHeader >
< div className = "flex items-center gap-3 mb-2" >
< div className = { ` ${ accent . iconWrap } p-2 rounded-md ` } aria - hidden = "true" >
{ service . icon === 'message-circle' && < MessageCircle className = { ` w-6 h-6 ${ accent . icon } ` } / > }
{ service . icon === 'users' && < Users className = { ` w-6 h-6 ${ accent . icon } ` } / > }
{ service . icon === 'life-buoy' && < LifeBuoy className = { ` w-6 h-6 ${ accent . icon } ` } / > }
{ service . icon === 'graduation-cap' && < GraduationCap className = { ` w-6 h-6 ${ accent . icon } ` } / > }
{ service . icon === 'link' && < LinkIcon className = { ` w-6 h-6 ${ accent . icon } ` } / > }
{ service . icon === 'wifi' && < Wifi className = { ` w-6 h-6 ${ accent . icon } ` } / > }
{ service . icon === 'network' && < Network className = { ` w-6 h-6 ${ accent . icon } ` } / > }
< / div >
< CardTitle className = "text-left text-primary-navy text-xl" aria - label = { service . name } > { service . name } < / CardTitle >
< / div >
< CardDescription className = "font-medium text-teal-900" aria - label = { service . homeDesc } > { service . homeDesc } < / CardDescription >
< / CardHeader >
< CardContent >
< div className = "flex flex-col gap-2" >
< Link to = { ` /services/ ${ service . id } ` } className = { ` inline-flex items-center gap-1 text-sm font-semibold ${ accent . link } ` } aria - label = { ` Learn more about ${ service . name } ` } >
Learn more
< ArrowRight className = "h-4 w-4" aria - hidden = "true" / >
< / Link >
2026-05-17 14:49:01 -05:00
< / div >
2026-05-25 18:33:19 -05:00
< / CardContent >
< / Card >
)
} ) }
2026-05-12 01:04:17 -05:00
< / div >
< / div >
< / section >
{ /* Why Queue North */ }
< section className = "bg-section-alt py-16 md:py-24" >
2026-05-17 17:11:29 -05:00
< div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" >
2026-05-12 01:04:17 -05:00
< div className = "text-center mb-12" >
< h2 className = "text-3xl md:text-4xl font-bold text-primary-navy mb-4" > Why Queue North < / h2 >
2026-05-17 15:07:28 -05:00
< p className = "text-xl text-soft-text max-w-2xl mx-auto mb-6" >
2026-05-17 14:56:10 -05:00
Four concrete differentiators that set us apart
2026-05-12 01:04:17 -05:00
< / p >
2026-05-17 15:07:28 -05:00
< div >
fix(a11y): visible focus indicator, aria-expanded, and six Label-in-Name failures
Batch 10, and only three of its six issues were real. The other three are
answered on the tracker with the arithmetic.
#189 — focus indicator. Footer and Home carried zero focus styling across 17
links. Added one global :focus-visible rule rather than sprinkling classes,
because the issue's own scope was "all interactive <a> and <Link> elements".
Not the ring the issue asked for. It suggested ring-primary-cyan; #22D3EE is
6.26:1 on navy and 2.38:1 on white, so on this light-first design that ring
would have failed WCAG 1.4.11 across most of the site. Two rings instead — white
inside, navy outside — so the white carries the dark bands and the navy carries
the light sections. Worst case across every background in the palette is
13.62:1. box-shadow so both rings follow each element's own border-radius, plus
a transparent outline for forced-colors mode.
#192 — aria-expanded={isOpen} and aria-controls on the mobile nav trigger, and
the id on SheetContent it now points at. Confirmed as "false" in the
prerendered HTML.
#193 — INVERTED. The issue asked for aria-labels to be added consistently to
hero CTAs. The aria-labels already there were WCAG 2.5.3 Label in Name failures,
Level A: "Schedule a consultation" is not a superset of the visible "Schedule
Consultation", so a voice-control user saying what they see cannot activate the
link. Doing what the issue asked would have spread a Level A failure. Removed
the six that broke it; kept the eight that genuinely add context and do contain
their visible text, including the icon-only header logo link that needs one.
Audit now reports 0 failures across Home, Services, Footer and Header.
Verified: npm run build; the ring is in the built CSS, aria-expanded="false" and
id="mobile-nav-content" are in the prerendered HTML, all six removed labels are
absent from it, and the visible CTA text still renders.
NOT verified: nothing has been walked keyboard-only in a browser, and no screen
reader has been used. That gap is recorded in docs/qa/ClaudeQACoverage.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 02:31:13 -05:00
< Link to = "/contact#contact-form" className = "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary-navy text-white hover:bg-primary-navy-dark transition-colors" >
2026-05-17 15:07:28 -05:00
Request Consultation
2026-05-17 21:44:48 -05:00
< / Link >
2026-05-17 15:07:28 -05:00
< / div >
2026-05-12 01:04:17 -05:00
< / div >
2026-05-17 14:56:10 -05:00
< div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6" >
2026-05-25 18:33:19 -05:00
< Card className = "bg-white/80 border-t-[3px] border-t-primary-blue" >
2026-05-17 14:56:10 -05:00
< CardContent className = "pt-6" >
2026-05-25 18:33:19 -05:00
< div className = "bg-sky-50 text-primary-blue p-3 rounded-md mb-4 inline-flex" >
2026-05-17 14:56:10 -05:00
< Headphones className = "w-6 h-6" / >
< / div >
< h3 className = "text-lg font-semibold text-primary-navy mb-2" > Responsiveness < / h3 >
< p className = "text-sm text-soft-text" >
When you call , a human answers — not a ticket queue , not a chatbot . Real support from people who know your system .
< / p >
< / CardContent >
< / Card >
2026-05-25 18:33:19 -05:00
< Card className = "bg-white/80 border-t-[3px] border-t-teal-500" >
2026-05-17 14:56:10 -05:00
< CardContent className = "pt-6" >
2026-05-25 18:33:19 -05:00
< div className = "bg-teal-50 text-teal-600 p-3 rounded-md mb-4 inline-flex" >
2026-05-17 14:56:10 -05:00
< UserCheck className = "w-6 h-6" / >
< / div >
< h3 className = "text-lg font-semibold text-primary-navy mb-2" > Direct Support < / h3 >
< p className = "text-sm text-soft-text" >
No account managers between you and the engineer solving your problem . You talk to the team that designed and deployed your system .
2026-05-12 01:04:17 -05:00
< / p >
< / CardContent >
< / Card >
2026-05-25 18:33:19 -05:00
< Card className = "bg-white/80 border-t-[3px] border-t-cyan-400" >
2026-05-17 14:56:10 -05:00
< CardContent className = "pt-6" >
2026-05-25 18:33:19 -05:00
< div className = "bg-cyan-50 text-cyan-600 p-3 rounded-md mb-4 inline-flex" >
2026-05-17 14:56:10 -05:00
< Activity className = "w-6 h-6" / >
< / div >
< h3 className = "text-lg font-semibold text-primary-navy mb-2" > Proactive Monitoring < / h3 >
< p className = "text-sm text-soft-text" >
We catch problems before you notice them . 24 / 7 monitoring means we ' re often resolving issues before your phone rings .
2026-05-12 01:04:17 -05:00
< / p >
< / CardContent >
< / Card >
2026-05-25 18:33:19 -05:00
< Card className = "bg-white/80 border-t-[3px] border-t-accent-gold" >
2026-05-17 14:56:10 -05:00
< CardContent className = "pt-6" >
2026-05-25 18:33:19 -05:00
< div className = "bg-amber-50 text-amber-600 p-3 rounded-md mb-4 inline-flex" >
2026-05-17 14:56:10 -05:00
< ShieldCheck className = "w-6 h-6" / >
< / div >
< h3 className = "text-lg font-semibold text-primary-navy mb-2" > Vendor Neutrality < / h3 >
< p className = "text-sm text-soft-text" >
2026-05-17 15:28:12 -05:00
As an 8 x8 and Cisco Certified Partner , we recommend what works best for you — not what pays the highest commission . We 've tested the alternatives so you don' t have to .
2026-05-12 01:04:17 -05:00
< / p >
< / CardContent >
< / Card >
< / div >
< / div >
< / section >
{ /* Industries Section */ }
< section className = "bg-background py-16 md:py-24" >
2026-05-17 17:11:29 -05:00
< div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" >
2026-05-12 01:04:17 -05:00
< div className = "text-center mb-12" >
< h2 className = "text-3xl md:text-4xl font-bold text-primary-navy mb-4" > Industries We Serve < / h2 >
< p className = "text-xl text-soft-text max-w-2xl mx-auto" >
Tailored solutions for healthcare , retail , manufacturing , and more
< / p >
< / div >
< div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6" >
2026-05-25 18:33:19 -05:00
{ industries . map ( ( industry , index ) => {
2026-05-17 21:41:01 -05:00
const IconComponent = industryIcons [ industry . icon ] || Building2
2026-05-25 18:33:19 -05:00
const accent = industryAccentStyles [ index % industryAccentStyles . length ]
2026-05-17 15:41:22 -05:00
return (
2026-05-25 18:33:19 -05:00
< Card key = { industry . id } className = { ` h-full transition-all hover:shadow-md ${ accent . card } ` } >
2026-05-17 15:41:22 -05:00
< CardContent className = "p-6" >
2026-05-25 18:33:19 -05:00
< div className = { ` w-14 h-14 rounded-md ${ accent . iconWrap } flex items-center justify-center mb-4 ` } aria - hidden = "true" >
< IconComponent className = { ` w-8 h-8 ${ accent . icon } ` } / >
2026-05-17 15:41:22 -05:00
< / div >
2026-05-25 18:33:19 -05:00
< h3 className = "text-left text-xl font-semibold text-primary-navy mb-3" aria - label = { industry . name } > { industry . name } < / h3 >
2026-05-17 22:47:03 -05:00
< p className = "text-sm text-soft-text mb-4" aria - label = { industry . homeDesc || 'Industry-specific solutions designed to address your unique challenges and requirements.' } > { industry . homeDesc || 'Industry-specific solutions designed to address your unique challenges and requirements.' } < / p >
2026-05-18 13:12:18 -05:00
< Link to = { ` /industries/ ${ industry . id } ` } className = "inline-flex items-center gap-1 text-sm font-semibold text-primary-navy hover:text-primary-blue" aria - label = { ` Learn more about ${ industry . name } industry solutions ` } >
Learn more
< ArrowRight className = "h-4 w-4" aria - hidden = "true" / >
2026-05-17 21:44:48 -05:00
< / Link >
2026-05-17 15:41:22 -05:00
< / CardContent >
< / Card >
)
} ) }
2026-05-12 01:04:17 -05:00
< / div >
< / div >
< / section >
2026-05-17 20:51:16 -05:00
{ /* Final CTA - Free Migration Offer */ }
< section className = "bg-section-alt py-16 md:py-24" >
2026-05-17 17:11:29 -05:00
< div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center" >
2026-05-17 20:51:16 -05:00
< h2 className = "text-3xl md:text-4xl font-bold text-primary-navy mb-6" >
What we ' ll help you do
2026-05-12 01:04:17 -05:00
< / h2 >
2026-05-17 20:51:16 -05:00
< div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8" >
2026-05-26 17:08:55 -05:00
< div className = "flex items-start gap-3 justify-center sm:justify-start" >
2026-05-18 13:12:18 -05:00
< div className = "bg-primary-navy/10 p-2 rounded-md flex-shrink-0" >
2026-05-17 20:51:16 -05:00
< MapPin className = "w-5 h-5 text-primary-navy" / >
< / div >
2026-05-26 17:08:55 -05:00
< p className = "text-center sm:text-left text-sm md:text-base text-soft-text" >
2026-05-17 20:51:16 -05:00
Identify the features you actually need
< / p >
< / div >
2026-05-26 17:08:55 -05:00
< div className = "flex items-start gap-3 justify-center sm:justify-start" >
2026-05-18 13:12:18 -05:00
< div className = "bg-teal-500/10 p-2 rounded-md flex-shrink-0" >
2026-05-17 20:51:16 -05:00
< Users className = "w-5 h-5 text-teal-600" / >
< / div >
2026-05-26 17:08:55 -05:00
< p className = "text-center sm:text-left text-sm md:text-base text-soft-text" >
2026-05-17 20:51:16 -05:00
Align solutions with operations and budget
< / p >
< / div >
2026-05-26 17:08:55 -05:00
< div className = "flex items-start gap-3 justify-center sm:justify-start" >
2026-05-18 13:12:18 -05:00
< div className = "bg-teal-500/10 p-2 rounded-md flex-shrink-0" >
2026-05-17 20:51:16 -05:00
< Network className = "w-5 h-5 text-teal-600" / >
< / div >
2026-05-26 17:08:55 -05:00
< p className = "text-center sm:text-left text-sm md:text-base text-soft-text" >
2026-05-17 20:51:16 -05:00
Plan deployment , migration , and training
< / p >
< / div >
2026-05-26 17:08:55 -05:00
< div className = "flex items-start gap-3 justify-center sm:justify-start" >
2026-05-18 13:12:18 -05:00
< div className = "bg-teal-600/10 p-2 rounded-md flex-shrink-0" >
2026-05-17 20:51:16 -05:00
< ShieldCheck className = "w-5 h-5 text-teal-600" / >
< / div >
2026-05-26 17:08:55 -05:00
< p className = "text-center sm:text-left text-sm md:text-base text-teal-700 font-semibold" >
2026-05-17 20:51:16 -05:00
Ask how you qualify for our free migration
< / p >
< / div >
< / div >
< p className = "text-lg text-soft-text mb-8 max-w-2xl mx-auto" >
Share a few details and we ' ll provide clear direction .
2026-05-12 01:04:17 -05:00
< / p >
fix(a11y): visible focus indicator, aria-expanded, and six Label-in-Name failures
Batch 10, and only three of its six issues were real. The other three are
answered on the tracker with the arithmetic.
#189 — focus indicator. Footer and Home carried zero focus styling across 17
links. Added one global :focus-visible rule rather than sprinkling classes,
because the issue's own scope was "all interactive <a> and <Link> elements".
Not the ring the issue asked for. It suggested ring-primary-cyan; #22D3EE is
6.26:1 on navy and 2.38:1 on white, so on this light-first design that ring
would have failed WCAG 1.4.11 across most of the site. Two rings instead — white
inside, navy outside — so the white carries the dark bands and the navy carries
the light sections. Worst case across every background in the palette is
13.62:1. box-shadow so both rings follow each element's own border-radius, plus
a transparent outline for forced-colors mode.
#192 — aria-expanded={isOpen} and aria-controls on the mobile nav trigger, and
the id on SheetContent it now points at. Confirmed as "false" in the
prerendered HTML.
#193 — INVERTED. The issue asked for aria-labels to be added consistently to
hero CTAs. The aria-labels already there were WCAG 2.5.3 Label in Name failures,
Level A: "Schedule a consultation" is not a superset of the visible "Schedule
Consultation", so a voice-control user saying what they see cannot activate the
link. Doing what the issue asked would have spread a Level A failure. Removed
the six that broke it; kept the eight that genuinely add context and do contain
their visible text, including the icon-only header logo link that needs one.
Audit now reports 0 failures across Home, Services, Footer and Header.
Verified: npm run build; the ring is in the built CSS, aria-expanded="false" and
id="mobile-nav-content" are in the prerendered HTML, all six removed labels are
absent from it, and the visible CTA text still renders.
NOT verified: nothing has been walked keyboard-only in a browser, and no screen
reader has been used. That gap is recorded in docs/qa/ClaudeQACoverage.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 02:31:13 -05:00
< Link to = "/contact#contact-form" className = "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-6 bg-primary-navy text-white hover:bg-primary-navy-dark transition-colors" >
2026-05-12 01:04:17 -05:00
Request Consultation
2026-05-17 21:44:48 -05:00
< / Link >
2026-05-12 01:04:17 -05:00
< / div >
< / section >
2026-05-17 17:11:29 -05:00
< / >
2026-05-12 01:04:17 -05:00
)
}
export default Home