feat: Phase 5 SPA fixes, mobile menu, assets, and redesign planning

- Fix BrowserRouter → RouterProvider (routes were disconnected)
- Strip TS generics from .jsx files (Card, Badge, Dialog, Input, Textarea)
- Fix useToast import from sonner (Contact, Support)
- Merge mobile Sheet into Header (DialogTrigger outside Dialog)
- Add SPA catch-all route for client-side navigation
- Add CSP style-src for Google Fonts
- Copy all image assets to public/ (were 404)
- Replace placeholder logo with real Queue North logo
- Fix SheetContent positional CSS + install tailwindcss-animate
- Add visually hidden SheetTitle for accessibility
- Update README and FUTURE.md with Phase 5 redesign batches
- Add review.md (redesign assessment, exempt from git)
This commit is contained in:
null 2026-05-13 22:07:35 -05:00
parent c2d5873f08
commit c4985e37bc
48 changed files with 705 additions and 201 deletions

View File

@ -115,7 +115,7 @@ Version numbers correlate directly to the active phase:
- ~~Refined service/industry cards and CTA sections~~
- ~~Mobile-first layout polish~~
- **Phase 4 — Forms + Backend Hardening**: `0.4.x` 🔄 In Progress
- **Phase 4 — Forms + Backend Hardening**: `0.4.x` ✅ Complete
- ~~Contact and support forms fully wired to Express~~
- ~~SQLite persistence verified~~
- ~~Client-side validation + Sonner feedback~~
@ -124,11 +124,24 @@ Version numbers correlate directly to the active phase:
- ~~Rate limiting + security headers + CORS~~
- ~~Backend/API hardening as needed~~
- **Phase 5 — Verification + Release Readiness**: `0.5.x`
- Build/runtime verification
- Route and form testing
- **Phase 5 — Verification + Redesign**: `0.5.x` 🔄 In Progress
- ~~SPA router fix (BrowserRouter → RouterProvider)~~
- ~~TS generics stripped from .jsx files~~
- ~~Mobile menu Sheet/Dialog fix~~
- ~~DialogTitle accessibility fix~~
- ~~SPA catch-all route for client-side navigation~~
- ~~Image assets copied to public/ (were 404)~~
- ~~Real Queue North logo replacing placeholder~~
- ~~CSP updated for Google Fonts~~
- ~~Hamburger menu + SheetContent CSS fix~~
- ~~tailwindcss-animate installed and configured~~
- Hero section rewrite — B2B clarity, 8x8 partnership prominence
- Trust signals section — metrics, badges, certifications
- Services rewrite — business outcomes over technical jargon
- Why Queue North refinement — concrete differentiators
- Footer + CTA pass — contact paths everywhere
- Remaining P0/P1 audit fixes (Zoho, su-exec, email constraint)
- Accessibility checks
- Documentation and release cleanup
- Final push to `dev` for the completed phase
Patch versions increment for completed task batches after the full pipeline finishes. Dispatch a task batch, run it through the required agents, then push that completed batch once. Example: Docker task batch goes through Neo → Private Hudson → Bishop → Ripley, then pushes as `0.2.1`. Notes/tags should use the version number only.
@ -179,9 +192,23 @@ Do not increment the patch version for each individual agent inside the same bat
Notes, tags, and checkpoint labels should use only the version number, such as `0.2.1`.
## Design Source of Truth
## Design Direction
See [OVERHAUL_PLAN.md](./OVERHAUL_PLAN.md) for the full rebuild plan and Scarlett's design implementation brief.
Based on the redesign review (see `review.md`), the site should feel:
- **Modern, clean, stable** — not experimental, not hacker aesthetic
- **Business-first** — B2B UCaaS/IT partner, not a dev portfolio
- **Trust-forward** — 8x8 partnership, certifications, uptime SLAs front and center
- **Human but competent** — less corporate fluff, more concrete outcomes
Color palette evolution (not rip-and-replace):
- Keep navy dark base, add teal/cyan accents for depth and hierarchy
- Improve contrast and spacing
- Mobile-first — SMB decision-makers browse on phones
Reference brands: RingCentral, Cloudflare, Dialpad — modern but enterprise-trustworthy.
See [review.md](./review.md) for the full redesign assessment.
## Docker Deployment

148
package-lock.json generated
View File

@ -1,14 +1,15 @@
{
"name": "queuenorth-website",
"version": "0.4.6",
"version": "0.4.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "queuenorth-website",
"version": "0.4.6",
"version": "0.4.8",
"dependencies": {
"@radix-ui/react-dialog": "^1.1.0",
"@radix-ui/react-visually-hidden": "^1.2.4",
"@tanstack/react-query": "^5.62.0",
"better-sqlite3": "^11.8.0",
"cors": "^2.8.6",
@ -20,6 +21,7 @@
"react-dom": "^19.0.0",
"react-router-dom": "^7.1.3",
"sonner": "^1.7.0",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2",
"zustand": "^5.0.3"
},
@ -40,7 +42,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@ -777,7 +778,6 @@
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@ -799,7 +799,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@ -809,14 +808,12 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@ -827,7 +824,6 @@
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
@ -841,7 +837,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
@ -851,7 +846,6 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
@ -1192,6 +1186,70 @@
}
}
},
"node_modules/@radix-ui/react-visually-hidden": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.4.tgz",
"integrity": "sha512-kaeiyGCe844dkb9AVF+rb4yTyb1LiLN/e3es3nLiRyN4dC8AduBYPMnnNlDjX2VDOcvDEiPnRNMJeWCfsX0txg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.1.4"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
"integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.2.4"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-slot": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
"integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
@ -1848,14 +1906,12 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
"dev": true,
"license": "MIT"
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
@ -1869,7 +1925,6 @@
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true,
"license": "MIT"
},
"node_modules/aria-hidden": {
@ -1975,7 +2030,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -2047,7 +2101,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
@ -2156,7 +2209,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@ -2217,7 +2269,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
@ -2242,7 +2293,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
@ -2296,7 +2346,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@ -2391,7 +2440,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
@ -2487,14 +2535,12 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true,
"license": "MIT"
},
"node_modules/dunder-proto": {
@ -2738,7 +2784,6 @@
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@ -2755,7 +2800,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
@ -2768,7 +2812,6 @@
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
"integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
"dev": true,
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
@ -2784,7 +2827,6 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
@ -2868,7 +2910,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@ -2964,7 +3005,6 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.3"
@ -3114,7 +3154,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
@ -3127,7 +3166,6 @@
"version": "2.16.2",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
"integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
"hasown": "^2.0.3"
@ -3143,7 +3181,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -3163,7 +3200,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@ -3176,7 +3212,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
@ -3186,7 +3221,6 @@
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
@ -3229,7 +3263,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
@ -3242,7 +3275,6 @@
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true,
"license": "MIT"
},
"node_modules/lru-cache": {
@ -3295,7 +3327,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
@ -3314,7 +3345,6 @@
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
@ -3394,7 +3424,6 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0",
@ -3406,7 +3435,6 @@
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"dev": true,
"funding": [
{
"type": "github",
@ -3471,7 +3499,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -3490,7 +3517,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@ -3542,7 +3568,6 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true,
"license": "MIT"
},
"node_modules/path-to-regexp": {
@ -3555,14 +3580,12 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
@ -3575,7 +3598,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -3585,7 +3607,6 @@
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@ -3595,7 +3616,6 @@
"version": "8.5.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -3624,7 +3644,6 @@
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"dev": true,
"license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.0.0",
@ -3642,7 +3661,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -3668,7 +3686,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
"integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -3711,7 +3728,6 @@
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -3737,7 +3753,6 @@
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
@ -3751,7 +3766,6 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true,
"license": "MIT"
},
"node_modules/prebuild-install": {
@ -3823,7 +3837,6 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true,
"funding": [
{
"type": "github",
@ -4034,7 +4047,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"pify": "^2.3.0"
@ -4058,7 +4070,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
@ -4081,7 +4092,6 @@
"version": "1.22.12",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
"integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@ -4103,7 +4113,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
"dev": true,
"license": "MIT",
"engines": {
"iojs": ">=1.0.0",
@ -4159,7 +4168,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
"funding": [
{
"type": "github",
@ -4441,7 +4449,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@ -4506,7 +4513,6 @@
"version": "3.35.1",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
"integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.2",
@ -4545,7 +4551,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -4558,7 +4563,6 @@
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
@ -4592,6 +4596,15 @@
"node": ">=14.0.0"
}
},
"node_modules/tailwindcss-animate": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
"integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
"license": "MIT",
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders"
}
},
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
@ -4624,7 +4637,6 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0"
@ -4634,7 +4646,6 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"dev": true,
"license": "MIT",
"dependencies": {
"thenify": ">= 3.1.0 < 4"
@ -4647,7 +4658,6 @@
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
@ -4664,7 +4674,6 @@
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
@ -4682,7 +4691,6 @@
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@ -4695,7 +4703,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
@ -4727,7 +4734,6 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/tslib": {

View File

@ -17,6 +17,7 @@
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.0",
"@radix-ui/react-visually-hidden": "^1.2.4",
"@tanstack/react-query": "^5.62.0",
"better-sqlite3": "^11.8.0",
"cors": "^2.8.6",
@ -28,6 +29,7 @@
"react-dom": "^19.0.0",
"react-router-dom": "^7.1.3",
"sonner": "^1.7.0",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2",
"zustand": "^5.0.3"
},

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 288 288">
<defs>
<style>
.cls-1 {
fill: #fff;
}
</style>
</defs>
<path class="cls-1" d="M172.73,132.13h-21.87l-5.04,9.59c-.69,1.49-1.83,3.16-1.83,3.16h-.23s-1.03-1.68-1.83-3.16l-5.04-9.59h-21.85l17.17,26.51-17.1,26.54h21.48l5.8-11.08c.57-1.03,1.37-3.02,1.37-3.02h.23s.8,1.99,1.37,3.02l5.89,11.08h21.5l-17.1-26.54,17.07-26.51h0Z"/>
<path class="cls-1" d="M75.88,171.67c-6.06,0-10.85-4.67-10.85-10.47,0-4.92,2.65-8.71,4.8-10.98,8.83,4.04,16.91,7.07,16.91,12.24,0,5.93-4.16,9.21-10.85,9.21h0ZM76.51,117.28c5.93,0,9.47,3.28,9.47,8.33,0,5.55-2.4,9.72-3.15,11.11-7.95-3.41-14.51-6.44-14.51-12.37,0-3.91,2.52-7.07,8.2-7.07h0ZM98.72,144.54c.88-1.14,8.83-10.85,8.83-20.57,0-16.79-13.76-26.12-30.54-26.12-21.08,0-30.29,12.87-30.29,26,0,7.7,3.41,13.13,8.2,17.29-2.78,2.15-12.49,10.35-12.49,21.96,0,14.39,11.48,28.02,33.44,28.02s33.44-13.76,33.44-27.51c0-9.09-4.54-14.89-10.6-19.06h0Z"/>
<path class="cls-1" d="M211.7,171.67c-6.06,0-10.85-4.67-10.85-10.47,0-4.92,2.65-8.71,4.8-10.98,8.83,4.04,16.91,7.07,16.91,12.24,0,5.93-4.17,9.21-10.85,9.21h0ZM212.33,117.28c5.93,0,9.47,3.28,9.47,8.33,0,5.55-2.4,9.72-3.15,11.11-7.95-3.41-14.51-6.44-14.51-12.37,0-3.91,2.52-7.07,8.2-7.07h0ZM234.54,144.54c.88-1.14,8.83-10.85,8.83-20.57,0-16.79-13.76-26.12-30.54-26.12-21.08,0-30.29,12.87-30.29,26,0,7.7,3.41,13.13,8.2,17.29-2.78,2.15-12.5,10.35-12.5,21.96,0,14.39,11.48,28.02,33.44,28.02s33.44-13.76,33.44-27.51c0-9.09-4.54-14.89-10.6-19.06h0Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
public/assets/Cabling.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
public/assets/Retail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
public/assets/Wireless.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
public/assets/contact.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
public/assets/hero-tech.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/assets/logo2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

BIN
public/assets/old-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
public/assets/support.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

8
public/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 346 KiB

373
review.md Normal file
View File

@ -0,0 +1,373 @@
# Queue North Website Redesign Strategy
# Core Problem
Current website branding feels:
* too abstract
* too technical
* too personal
* too experimental
The site currently resembles:
* a developer portfolio
* infrastructure hobby project
* underground tech blog
Instead of:
* a mature B2B UCaaS provider
* managed IT partner
* enterprise communications company
This creates trust friction immediately.
Business buyers need confidence within seconds.
---
# Business Positioning
Queue North should position itself as:
## Primary Identity
Reliable business communications and IT infrastructure partner for SMB and enterprise clients.
## Supporting Identity
Modern, technically competent, responsive, security conscious.
Not:
* hacker aesthetic
* underground engineering lab
* mysterious tech collective
---
# Recommended Brand Direction
## Desired Feel
The website should feel:
* modern
* clean
* stable
* operationally mature
* enterprise capable
* technically sharp
* trustworthy
Think:
* RingCentral
* Zoom
* Cloudflare
* Cisco Meraki
* Dialpad
* 8x8
* Microsoft business products
But less corporate and less soulless.
Human but competent.
---
# Homepage Structure
# 1. Hero Section
## Goal
Instant clarity.
User should immediately understand:
* what Queue North does
* who it serves
* why it matters
## Recommended Headline
Business communications and IT that actually work.
Alternative:
Modern UCaaS and managed IT for businesses that cannot afford downtime.
## Supporting Text
Queue North delivers cloud communications, networking, managed IT, and infrastructure support for SMBs and enterprise teams.
## CTA Buttons
* Schedule Consultation
* View Services
Optional secondary:
* Contact Support
---
# 2. Trust Signals Section
This section should appear immediately after hero.
## Include
* uptime guarantees
* support response times
* certifications
* vendor partnerships
* years in business
* client industries
* deployment count
* SLA metrics
## Example Metrics
* 99.99% uptime
* 24/7 support
* multi site deployments
* secure cloud infrastructure
* enterprise grade failover
This is critical.
B2B buyers purchase risk reduction, not technology.
---
# 3. Services Section
## Recommended Layout
Clean enterprise card grid.
## Service Categories
### UCaaS
* hosted VoIP
* business phones
* call routing
* conferencing
* remote workforce support
### Managed IT
* endpoint management
* helpdesk
* patching
* infrastructure monitoring
### Networking
* SD WAN
* VPN
* firewall management
* switching
* wireless deployments
### Security
* MFA
* endpoint protection
* backups
* compliance
* monitoring
Each card should explain business outcomes, not technical jargon.
Bad:
"Kubernetes managed SIP orchestration"
Good:
"Reliable business communications with centralized management and failover"
Humans love inventing incomprehensible wording and then wondering why sales calls disappear.
---
# 4. Industry Use Cases
Very important for B2B trust.
## Example Industries
* healthcare
* logistics
* retail
* manufacturing
* legal
* finance
* distributed offices
Each section should explain:
* operational problems
* compliance needs
* uptime requirements
* remote work needs
---
# 5. Why Queue North
## Focus On
* responsiveness
* reliability
* technical depth
* direct support
* proactive monitoring
* vendor neutrality
## Avoid
Generic corporate fluff like:
* innovative solutions
* digital transformation
* next generation synergy nonsense
Every B2B site writes this garbage and nobody believes any of it anymore.
---
# 6. Testimonials / Case Studies
Mandatory.
Enterprise buyers need validation.
## Include
* measurable outcomes
* reduced downtime
* migration success
* support quality
* deployment scale
Even 2 or 3 strong case studies massively improve credibility.
---
# 7. Support & Operations
This is where technical sophistication can appear.
## Good Technical Signals
* network operations center visuals
* uptime dashboards
* support workflows
* monitoring systems
* escalation paths
## Bad Technical Signals
* hacker visuals
* terminal cosplay
* random code snippets
* obscure infrastructure references
Technical competence should feel controlled and operational.
Not chaotic.
---
# Visual Design Recommendations
# Colors
## Base
* white
* dark slate
* muted blue
* graphite
## Accent
* blue
* teal
* restrained cyan
Avoid:
* neon green
* hacker black/red
* cyberpunk palettes
Those aesthetics destroy enterprise trust surprisingly fast.
---
# Typography
## Recommended
* Inter
* Geist
* IBM Plex Sans
Professional sans serif.
Monospace only for tiny UI accents if needed.
---
# Layout Style
## Use
* large spacing
* strong hierarchy
* clean sections
* restrained motion
* clear CTAs
## Avoid
* excessive animations
* overloaded visuals
* scrolling gimmicks
* terminal-first design
Enterprise sites should feel efficient.
---
# Recommended Technical Stack
## Best Option
### Astro or Next.js
With:
* Tailwind
* Framer Motion lightly used
* CMS integration
* fast performance
* accessibility focus
---
# Key Messaging Shift
## Current Impression
"Interesting technical person"
## Required Impression
"Reliable communications and IT partner for serious businesses"
That distinction changes everything about the design language.

View File

@ -59,7 +59,7 @@ const apiLimiter = rateLimit({
const cspDirectives = {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
fontSrc: ["'self'", 'https://fonts.gstatic.com'],
imgSrc: ["'self'", 'data:'],
connectSrc: ["'self'"],
@ -424,9 +424,19 @@ app.use((req, res, next) => {
next()
})
// Static file serving for SPA (falls through to SPA router)
// Static file serving for SPA
app.use(express.static(path.join(__dirname, '../dist')))
// SPA catch-all — serve index.html for any non-API, non-asset route
// This lets React Router handle client-side routing
app.get('*', (req, res, next) => {
// Skip API routes (already handled above) and requests for static assets
if (req.path.startsWith('/api/') || req.path.includes('.')) {
return next()
}
res.sendFile(path.join(__dirname, '../dist/index.html'))
})
// --- Request timeout middleware (30 seconds) ---
const REQUEST_TIMEOUT_MS = 30000

View File

@ -1,14 +1,12 @@
import { Outlet } from 'react-router-dom'
import Header from './components/layout/Header.jsx'
import Footer from './components/layout/Footer.jsx'
import MobileNav from './components/layout/MobileNav.jsx'
import './index.css'
function App() {
return (
<div className="min-h-screen flex flex-col font-sans bg-background text-text">
<Header />
<MobileNav />
<main className="flex-1">
<Outlet />
</main>
@ -17,4 +15,4 @@ function App() {
)
}
export default App
export default App

View File

@ -1,9 +1,11 @@
import { useState, useEffect } from 'react'
import { SheetTrigger } from '@/components/ui/Sheet'
import { Sheet, SheetTrigger, SheetContent, SheetTitle } from '@/components/ui/Sheet'
import * as VisuallyHidden from '@radix-ui/react-visually-hidden'
import { Link } from 'react-router-dom'
const Header = () => {
const [isScrolled, setIsScrolled] = useState(false)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
useEffect(() => {
const handleScroll = () => {
@ -23,65 +25,134 @@ const Header = () => {
{ name: 'Support', href: '/support' },
]
const serviceLinks = [
{ name: 'Unified Communications', href: '/services/unified-communications' },
{ name: 'Contact Center', href: '/services/contact-center' },
{ name: 'Managed Support', href: '/services/managed-support' },
{ name: 'Consulting & Training', href: '/services/consulting-training' },
{ name: 'Infrastructure Cabling', href: '/services/infrastructure-cabling' },
{ name: 'Wireless Access', href: '/services/wireless-access' },
{ name: 'Local Networking', href: '/services/local-networking' },
]
const industryLinks = [
{ name: 'Healthcare', href: '/industries/healthcare' },
{ name: 'Retail', href: '/industries/retail' },
{ name: 'Manufacturing', href: '/industries/manufacturing' },
{ name: 'Education & Finance', href: '/industries/education-finance' },
]
const closeMobileMenu = () => setMobileMenuOpen(false)
return (
<>
<header className={`sticky top-0 z-40 w-full transition-all duration-300 ${isScrolled ? 'bg-primary-navy shadow-md' : 'bg-primary-navy/95'}`}>
<div className="container mx-auto px-4">
<div className="flex h-16 items-center justify-between">
{/* Logo */}
<div className="flex items-center gap-3">
<img
src="/logo.svg"
alt="Queue North Technologies"
className="h-8 w-auto flex-shrink-0"
/>
<span className="font-bold text-xl text-white hidden sm:block tracking-tight">Queue North</span>
</div>
<header className={`sticky top-0 z-40 w-full transition-all duration-300 ${isScrolled ? 'bg-primary-navy shadow-md' : 'bg-primary-navy/95'}`}>
<div className="container mx-auto px-4">
<div className="flex h-16 items-center justify-between">
{/* Logo */}
<div className="flex items-center gap-3">
<img
src="/logo.svg"
alt="Queue North Technologies"
className="h-8 w-auto flex-shrink-0"
/>
<span className="font-bold text-xl text-white hidden sm:block tracking-tight">Queue North</span>
</div>
{/* Desktop Nav */}
<nav className="hidden md:flex items-center gap-6">
{navLinks.map((link) => (
<Link
key={link.name}
to={link.href}
className="text-sm font-medium text-navy-light hover:text-white transition-colors"
>
{link.name}
</Link>
))}
</nav>
{/* CTA Button */}
<div className="hidden md:block">
<Link to="/contact" className="inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 bg-primary-navy text-white hover:bg-primary-navy-dark transition-colors">
Request Consultation
{/* Desktop Nav */}
<nav className="hidden md:flex items-center gap-6">
{navLinks.map((link) => (
<Link
key={link.name}
to={link.href}
className="text-sm font-medium text-navy-light hover:text-white transition-colors"
>
{link.name}
</Link>
</div>
))}
</nav>
{/* Mobile Menu Toggle */}
<SheetTrigger asChild>
<button className="md:hidden p-2 text-white hover:text-cyan transition-colors focus:outline-none focus:ring-2 focus:ring-cyan rounded-md">
<span className="sr-only">Open menu</span>
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
</SheetTrigger>
{/* CTA Button */}
<div className="hidden md:block">
<Link to="/contact" className="inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 bg-primary-navy text-white hover:bg-primary-navy-dark transition-colors">
Request Consultation
</Link>
</div>
{/* Mobile Menu */}
<div className="md:hidden">
<Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>
<SheetTrigger asChild>
<button className="p-2 text-white hover:text-cyan transition-colors focus:outline-none focus:ring-2 focus:ring-cyan rounded-md">
<span className="sr-only">Open menu</span>
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</SheetTrigger>
<SheetContent side="right" className="w-[300px] sm:w-[350px] bg-primary-navy text-white">
<VisuallyHidden.Root asChild>
<SheetTitle>Navigation Menu</SheetTitle>
</VisuallyHidden.Root>
<div className="flex flex-col h-full">
<div className="flex items-center gap-3 mb-6">
<img src="/logo.svg" alt="Queue North" className="h-9 w-auto" />
<span className="font-bold text-xl">Queue North</span>
</div>
<nav className="flex flex-col space-y-6">
<div>
<h4 className="text-xs font-semibold uppercase tracking-wider text-navy-light mb-3">Primary</h4>
<ul className="space-y-2">
{navLinks.map((link) => (
<li key={link.name}>
<Link to={link.href} onClick={closeMobileMenu} className="block text-base font-medium text-navy-light hover:text-white transition-colors py-2">
{link.name}
</Link>
</li>
))}
</ul>
</div>
<div>
<h4 className="text-xs font-semibold uppercase tracking-wider text-navy-light mb-3">Services</h4>
<ul className="space-y-2">
{serviceLinks.map((service) => (
<li key={service.name}>
<Link to={service.href} onClick={closeMobileMenu} className="block text-sm text-navy-light hover:text-white transition-colors py-2 border-b border-white/10 last:border-0">
{service.name}
</Link>
</li>
))}
</ul>
</div>
<div>
<h4 className="text-xs font-semibold uppercase tracking-wider text-navy-light mb-3">Industries</h4>
<ul className="space-y-2">
{industryLinks.map((industry) => (
<li key={industry.name}>
<Link to={industry.href} onClick={closeMobileMenu} className="block text-sm text-navy-light hover:text-white transition-colors py-2 border-b border-white/10 last:border-0">
{industry.name}
</Link>
</li>
))}
</ul>
</div>
</nav>
<div className="mt-auto pt-6">
<Link to="/contact" onClick={closeMobileMenu} className="inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 w-full bg-primary-navy text-white hover:bg-primary-navy-dark transition-colors">
Request Consultation
</Link>
</div>
</div>
</SheetContent>
</Sheet>
</div>
</div>
</header>
</>
</div>
</header>
)
}
export default Header
export default Header

View File

@ -1,30 +1,27 @@
import * as React from 'react'
const Badge = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & {
variant?: 'default' | 'secondary' | 'outline' | 'success' | 'warning' | 'error'
}
>(({ className = '', variant = 'default', ...props }, ref) => {
const baseStyles = 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2'
const variants = {
default: 'border-transparent bg-primary-navy text-white hover:bg-primary-navy-dark',
secondary: 'border-transparent bg-section-alt text-text hover:bg-opacity-80',
outline: 'text-foreground',
success: 'border-transparent bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300',
warning: 'border-transparent bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300',
error: 'border-transparent bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300',
}
const Badge = React.forwardRef(
({ className = '', variant = 'default', ...props }, ref) => {
const baseStyles = 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2'
const variants = {
default: 'border-transparent bg-primary-navy text-white hover:bg-primary-navy-dark',
secondary: 'border-transparent bg-section-alt text-text hover:bg-opacity-80',
outline: 'text-foreground',
success: 'border-transparent bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300',
warning: 'border-transparent bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300',
error: 'border-transparent bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300',
}
return (
<div
ref={ref}
className={`${baseStyles} ${variants[variant]} ${className}`}
{...props}
/>
)
})
return (
<div
ref={ref}
className={`${baseStyles} ${variants[variant]} ${className}`}
{...props}
/>
)
}
)
Badge.displayName = 'Badge'
export { Badge }
export { Badge }

View File

@ -1,6 +1,6 @@
import * as React from 'react'
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
const Card = React.forwardRef(
({ className = '', ...props }, ref) => (
<div
ref={ref}
@ -11,7 +11,7 @@ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElemen
)
Card.displayName = 'Card'
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
const CardHeader = React.forwardRef(
({ className = '', ...props }, ref) => (
<div
ref={ref}
@ -22,7 +22,7 @@ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDiv
)
CardHeader.displayName = 'CardHeader'
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
const CardTitle = React.forwardRef(
({ className = '', ...props }, ref) => (
<h3
ref={ref}
@ -33,7 +33,7 @@ const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HT
)
CardTitle.displayName = 'CardTitle'
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
const CardDescription = React.forwardRef(
({ className = '', ...props }, ref) => (
<p
ref={ref}
@ -44,14 +44,14 @@ const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttribu
)
CardDescription.displayName = 'CardDescription'
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
const CardContent = React.forwardRef(
({ className = '', ...props }, ref) => (
<div ref={ref} className={`p-6 pt-0 ${className}`} {...props} />
)
)
CardContent.displayName = 'CardContent'
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
const CardFooter = React.forwardRef(
({ className = '', ...props }, ref) => (
<div
ref={ref}
@ -62,4 +62,4 @@ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDiv
)
CardFooter.displayName = 'CardFooter'
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@ -18,10 +18,8 @@ const DialogOverlay = ({ className, ...props }) => (
)
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className = '', children, ...props }, ref) => (
const DialogContent = React.forwardRef(
({ className = '', children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
@ -55,10 +53,8 @@ const DialogFooter = ({ className, ...props }) => (
)
DialogFooter.displayName = 'DialogFooter'
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className = '', ...props }, ref) => (
const DialogTitle = React.forwardRef(
({ className = '', ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={`text-lg font-semibold leading-none tracking-tight ${className}`}
@ -67,10 +63,8 @@ const DialogTitle = React.forwardRef<
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className = '', ...props }, ref) => (
const DialogDescription = React.forwardRef(
({ className = '', ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={`text-sm text-muted-foreground ${className}`}
@ -88,4 +82,4 @@ export {
DialogFooter,
DialogTitle,
DialogDescription,
}
}

View File

@ -1,6 +1,6 @@
import * as React from 'react'
const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
const Input = React.forwardRef(
({ className = '', type, ...props }, ref) => {
return (
<input
@ -14,4 +14,4 @@ const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLI
)
Input.displayName = 'Input'
export { Input }
export { Input }

View File

@ -17,14 +17,19 @@ const SheetOverlay = React.forwardRef(({ className, ...props }, ref) => (
))
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sideClasses = {
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
bottom: 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
right: 'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
}
const SheetContent = React.forwardRef(({ className, children, side = 'right', ...props }, ref) => (
<SheetPrimitive.Portal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={`fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out ${side === 'top' ? 'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top' : side === 'bottom' ? 'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom' : side === 'left' ? 'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left' : 'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right'} sm:rounded-lg ${className}`}
role="dialog"
aria-modal="true"
className={`fixed z-50 flex flex-col gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out ${sideClasses[side] ?? sideClasses.right} ${className ?? ''}`}
{...props}
>
{children}

View File

@ -1,6 +1,6 @@
import * as React from 'react'
const Textarea = React.forwardRef<HTMLTextAreaElement, React.TextareaHTMLAttributes<HTMLTextAreaElement>>(
const Textarea = React.forwardRef(
({ className = '', ...props }, ref) => {
return (
<textarea
@ -13,4 +13,4 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, React.TextareaHTMLAttribu
)
Textarea.displayName = 'Textarea'
export { Textarea }
export { Textarea }

View File

@ -1,8 +1,9 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import { RouterProvider } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { Toaster } from 'sonner'
import router from './router.jsx'
import App from './App.jsx'
const queryClient = new QueryClient({
@ -13,13 +14,14 @@ const queryClient = new QueryClient({
},
})
createRoot(document.getElementById('root')).render(
// Wrap the router with providers
const Root = () => (
<StrictMode>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<App />
<Toaster position="top-right" />
</BrowserRouter>
<RouterProvider router={router} />
<Toaster position="top-right" />
</QueryClientProvider>
</StrictMode>,
</StrictMode>
)
createRoot(document.getElementById('root')).render(<Root />)

View File

@ -1,6 +1,6 @@
import { useState } from 'react'
import { useMutation } from '@tanstack/react-query'
import { useToast } from 'sonner'
import { toast } from 'sonner'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Textarea } from '@/components/ui/Textarea'
@ -8,7 +8,6 @@ import { Select } from '@/components/ui/Select'
import { api } from '@/lib/api'
const Contact = () => {
const { toast } = useToast()
const [formState, setFormState] = useState({
company: '',
name: '',

View File

@ -1,6 +1,6 @@
import { useState } from 'react'
import { useMutation } from '@tanstack/react-query'
import { useToast } from 'sonner'
import { toast } from 'sonner'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Textarea } from '@/components/ui/Textarea'
@ -8,7 +8,6 @@ import { Select } from '@/components/ui/Select'
import { api } from '@/lib/api'
const Support = () => {
const { toast } = useToast()
const [formState, setFormState] = useState({
name: '',
company: '',

View File

@ -57,5 +57,5 @@ export default {
},
},
},
plugins: [],
plugins: [require('tailwindcss-animate')],
}