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" >
ui: no em dashes in anything a visitor or a crawler reads
Null's direction of 2026-09-10. Punctuation only: not one word changed.
Nine of them, and the share-image alt text was on every page twice:
src/components/SEO.jsx:4 the default og:image and twitter:image alt
index.html:25,:31 the same string, as template fallbacks
src/pages/Home.jsx:257,:290
src/pages/Services.jsx:99
src/pages/Contact.jsx:183
src/data/services.js:120 the wireless card on Home and the index
public/site.webmanifest:4 linked from every page
Thirteen more sit in code comments and reach no page, so they stay. The hours
range on /contact uses en dashes, which are a different character and are not
what was asked about.
This lands before the guards that reject an em dash in content or in built
HTML, because until now every built page carried one and those guards would
have gone red on arrival.
Verified against the build, not the source: no U+2014 in any file under dist/,
including the HTML, the webmanifest, the sitemap and the JS bundle.
Closes #222.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 04:26:21 -05:00
When you call , a human answers : not a ticket queue , not a chatbot . Real support from people who know your system .
2026-05-17 14:56:10 -05:00
< / 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" >
ui: no em dashes in anything a visitor or a crawler reads
Null's direction of 2026-09-10. Punctuation only: not one word changed.
Nine of them, and the share-image alt text was on every page twice:
src/components/SEO.jsx:4 the default og:image and twitter:image alt
index.html:25,:31 the same string, as template fallbacks
src/pages/Home.jsx:257,:290
src/pages/Services.jsx:99
src/pages/Contact.jsx:183
src/data/services.js:120 the wireless card on Home and the index
public/site.webmanifest:4 linked from every page
Thirteen more sit in code comments and reach no page, so they stay. The hours
range on /contact uses en dashes, which are a different character and are not
what was asked about.
This lands before the guards that reject an em dash in content or in built
HTML, because until now every built page carried one and those guards would
have gone red on arrival.
Verified against the build, not the source: no U+2014 in any file under dist/,
including the HTML, the webmanifest, the sitemap and the JS bundle.
Closes #222.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 04:26:21 -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