Files
boc/reports/design-audit-2026-06-20.md
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

609 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Design Audit — quiXzoom × Landvex
**Date:** 2026-06-20
**Auditor:** Bernt (Principal Product Designer / Design Systems Lead)
**Scope:** quiXzoom.com + Landvex.com — production-grade responsive, accessibility, consistency, enterprise-readiness
---
## AUDIT HEADER
| Property | quiXzoom | Landvex |
|---|---|---|
| Stack | Vanilla CSS + Vanilla JS | Vanilla CSS + Vanilla JS |
| Stylesheet | /styles.css (single file) | Inline `<style>` (no external CSS file) |
| Framework | None | None |
| CSS approach | Custom properties + BEM-like classes | Custom properties + utility classes |
| Overflow elements (390px) | 3 real + 1 false positive | 15+ (footer = 2859px wide) |
| Overflow elements (1440px) | 0 | 2859px footer |
| Mobile-menu bug | `#mobile-menu` visible on desktop | N/A |
| Social link icons | ❌ quiXzoom: raw URLs | ✅ Landvex: SVG icons |
**Totals:**
- 🔴 Critical: 4
- 🟠 Major: 6
- 🟡 Minor: 4
---
## 🔴 CRITICAL DEFECTS
---
### C-001 — `#mobile-menu` visible on desktop (quiXzoom)
- **URL:** https://www.quixzoom.com/
- **Viewport:** 1440px desktop
- **Selector:** `#mobile-menu`
- **Computed CSS:** `display: block; height: 26px; position: static; top: 69px`
- **Rotorsak:** `#mobile-menu` lacks a `display: none` rule at viewport > 768px. The element renders as a second nav row below the sticky header, showing all nav links as unstyled plain text.
- **Visual impact:** Desktop nav appears broken — two rows, second row shows raw link text without styling.
**Fix:**
```css
/* In styles.css — add to desktop breakpoint (already exists for .hamburger) */
@media (min-width: 769px) {
#mobile-menu {
display: none !important;
}
}
```
---
### C-002 — Hamburger button 4×4px rendered on desktop (quiXzoom)
- **URL:** https://www.quixzoom.com/
- **Viewport:** 1440px desktop
- **Selector:** `button.hamburger`
- **Computed CSS:** `display: block; width: 4px; height: 4px`
- **Rotorsak:** `.hamburger` is not hidden at desktop breakpoints. It renders as a 4×4px invisible dot in the nav right area — a non-functional touch target that confuses layout.
**Fix:**
```css
@media (min-width: 769px) {
.hamburger {
display: none;
}
}
```
---
### C-003 — `.footer-links` 2859px overflow (Landvex)
- **URL:** https://www.landvex.com/
- **Viewport:** All viewports (780px measured, same at 390px and 1440px)
- **Selector:** `.footer-links`
- **Computed CSS:** `display: flex; flex-wrap: nowrap; gap: 24px; width: auto`
- **Rotorsak:** 34 `<a>` children in a single `flex nowrap` row. Total width: 34 × ~50px + 33 × 24px gap ≈ 2492px. Causes entire page horizontal scroll.
**Fix:**
```css
.footer-links {
flex-wrap: wrap;
gap: 12px 24px; /* row-gap 12px, column-gap 24px */
}
```
---
### C-004 — `.footer-legal-links` overflow on mobile (quiXzoom)
- **URL:** https://www.quixzoom.com/
- **Viewport:** 390px mobile (right: 476px, 86px outside viewport)
- **Selector:** `.footer-legal-links`
- **Computed CSS:** `display: flex; flex-wrap: nowrap; gap: 20px; width: auto`
- **Rotorsak:** 7 link items × ~50px + 6 × 20px gap = 470px in a 390px viewport. No wrapping.
**Fix:**
```css
.footer-legal-links {
flex-wrap: wrap;
gap: 8px 16px;
justify-content: center;
}
```
---
## 🟠 MAJOR DEFECTS
---
### M-001 — quiXzoom social footer links = raw URLs (quiXzoom)
- **URL:** https://www.quixzoom.com/
- **Viewport:** All
- **Selector:** `footer a[href*="linkedin"], footer a[href*="twitter"], footer a[href*="instagram"]`
- **Computed CSS:** N/A — content issue
- **Rotorsak:** Social links render as bare `https://www.linkedin.com/company/quixzoom` text — no icon, no label. Landvex has SVG icons for the same links. Cross-site inconsistency + enterprise credibility failure.
**Fix (HTML — quiXzoom footer social links):**
```html
<!-- Replace bare <a href="https://www.linkedin.com/company/quixzoom">https://...</a> with: -->
<a href="https://www.linkedin.com/company/quixzoom" aria-label="quiXzoom on LinkedIn" rel="noopener noreferrer" target="_blank">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"/>
<rect x="2" y="9" width="4" height="12"/><circle cx="4" cy="4" r="2"/>
</svg>
<span class="sr-only">LinkedIn</span>
</a>
<a href="https://x.com/quixzoom" aria-label="quiXzoom on X" rel="noopener noreferrer" target="_blank">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.743l7.73-8.835L1.254 2.25H8.08l4.253 5.622zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
</svg>
<span class="sr-only">X (Twitter)</span>
</a>
```
```css
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border: 0;
}
```
---
### M-002 — Landvex desktop nav has no navigation links
- **URL:** https://www.landvex.com/
- **Viewport:** 1440px desktop
- **Selector:** `nav` (Landvex)
- **Computed CSS:** Nav renders logo + "Get in touch" CTA only — no navigation links visible
- **Rotorsak:** Navigation items are not present in the rendered nav HTML on desktop. Either rendered only in a hidden mobile menu (not verified), or missing entirely from the template.
**Fix:** Add primary navigation links to Landvex desktop nav:
```html
<nav>
<a href="/" class="nav-logo">LandveX</a>
<ul class="nav-links">
<li><a href="/methodology/">Methodology</a></li>
<li><a href="/verticals/">Solutions</a></li>
<li><a href="/cities/">Cities</a></li>
<li><a href="/about/">About</a></li>
</ul>
<a href="/contact/" class="nav-cta">Get in touch</a>
</nav>
```
```css
@media (min-width: 769px) {
.nav-links {
display: flex;
gap: 32px;
list-style: none;
}
}
```
---
### M-003 — Stat rows do not wrap at < 480px (quiXzoom)
- **URL:** https://www.quixzoom.com/
- **Viewport:** 390px mobile
- **Selector:** `.hstat` parent container (hero stats: "1280 / 20+ / 0%")
- **Rotorsak:** Stat items in flex row, no wrap defined. At 390px the 3-column row is borderline; at 320px it breaks.
**Fix:**
```css
.hero-stats, /* or whichever parent wraps .hstat elements */
[class*="stat-row"] {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 16px;
}
```
---
### M-004 — `.community-pipeline-badge` white-space:nowrap on mobile (quiXzoom)
- **URL:** https://www.quixzoom.com/
- **Viewport:** 390px mobile
- **Selector:** `.community-pipeline-badge`
- **Computed CSS:** `white-space: nowrap; display: block; width: 315px`
- **Rotorsak:** Badge text "Field observation → Mission → Verified data" is `nowrap` and 315px wide. At 320px this overflows. Parent `.community-pipeline` is 343px — also exceeds 320px viewport.
**Fix:**
```css
.community-pipeline-badge {
white-space: normal;
overflow-wrap: anywhere;
}
.community-pipeline {
max-width: 100%;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
```
---
### M-005 — Focus styles absent on interactive elements (both sites)
- **URL:** https://www.quixzoom.com/ + https://www.landvex.com/
- **Viewport:** All
- **Selector:** `a, button, input, select, textarea` (all interactive elements)
- **Computed CSS at :focus-visible:** `outline-width: 0px; box-shadow: none`
- **Rotorsak:** No `:focus-visible` styles defined. WCAG 2.2 SC 2.4.11 (Focus Appearance) requires visible focus indicators.
**Fix:**
```css
/* Add to both sites' stylesheets */
:focus-visible {
outline: 2px solid #0066FF;
outline-offset: 2px;
border-radius: 4px;
}
/* Suppress for mouse users only */
:focus:not(:focus-visible) {
outline: none;
}
```
---
### M-006 — Footer 34-link flat list — no semantic grouping (Landvex)
- **URL:** https://www.landvex.com/
- **Viewport:** All
- **Selector:** `.footer-links`
- **Rotorsak:** 34 links in a single flat `<div>` with no grouping, no `<nav>` landmark, no section headers. Enterprise footers require grouped columns (Legal / Product / Company / Cities).
**Fix (HTML structure):**
```html
<footer>
<div class="footer-grid">
<nav aria-label="Legal" class="footer-col">
<h3 class="footer-col-title">Legal</h3>
<a href="/privacy/">Privacy</a>
<a href="/terms/">Terms</a>
<a href="/cookie-policy/">Cookies</a>
<a href="/dpa/">DPA</a>
<a href="/compliance/">Compliance</a>
</nav>
<nav aria-label="Product" class="footer-col">
<h3 class="footer-col-title">Product</h3>
<a href="/methodology/">Methodology</a>
<a href="/data-quality/">Data Quality</a>
<a href="/coverage/">Coverage</a>
<a href="/sla/">SLA</a>
</nav>
<nav aria-label="Cities" class="footer-col">
<h3 class="footer-col-title">Cities</h3>
<a href="/cities/stockholm/">Stockholm</a>
<!-- etc -->
</nav>
<nav aria-label="Company" class="footer-col">
<h3 class="footer-col-title">Company</h3>
<a href="/about/">About</a>
<a href="/careers/">Careers</a>
<a href="/blog/">Insights</a>
<a href="/press/">Press</a>
</nav>
</div>
</footer>
```
```css
.footer-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 40px;
}
.footer-col {
display: flex;
flex-direction: column;
gap: 8px;
}
.footer-col-title {
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted, #6B6B6B);
margin-bottom: 8px;
}
```
---
## 🟡 MINOR DEFECTS
---
### m-001 — `html,body` missing `overflow-x: clip` safety net (both sites)
- **URL:** Both
- **Rotorsak:** After root-cause fixes, `overflow-x: clip` should be added as final protection.
**Fix (add AFTER root causes fixed above):**
```css
html,
body {
overflow-x: clip;
}
```
---
### m-002 — Milestone chain label mismatch (quiXzoom)
- **URL:** https://www.quixzoom.com/
- **Rotorsak:** DOM contains `.community-pipeline-badge` with text "10 → 25 → 50 → 100 → 250 → 500" (not "10K" as described in PDF). The text body references "World Builder at 10,000" — the badge does not show full chain.
**Fix:** Badge should show abbreviated chain or scroll on mobile:
```css
.community-pipeline-badge {
white-space: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scroll-snap-type: x mandatory;
max-width: 100%;
}
```
---
### m-003 — Viewport scrollbar width causes 15px layout shift (quiXzoom 1440px)
- **URL:** https://www.quixzoom.com/
- **Viewport:** 1440px
- **Rotorsak:** `document.documentElement.scrollWidth: 1425` vs `window.innerWidth: 1440` — 15px difference = scrollbar width eating into layout. Container uses `max-width: 1160px + 24px padding` = 1208px. Not a real overflow, but can cause CLS.
**Fix:**
```css
.container {
padding-inline: max(24px, calc((100vw - 1160px) / 2));
}
/* Or use scrollbar-gutter to reserve space */
html {
scrollbar-gutter: stable;
}
```
---
### m-004 — Hero sub-text references old wording (quiXzoom)
- **URL:** https://www.quixzoom.com/
- **Selector:** `.hero-sub` (p tag in hero)
- **Computed content:** "Earn QX Credits per approved submission. Weekly payouts — no platform fee. Launching August 1, 2026."
- **Rotorsak:** "Weekly payouts" is the old wording — was updated in index.html but may not have been invalidated in CDN cache.
**Fix:** CDN cache invalidation for `/` and verify live origin serves updated copy.
---
## ACCESSIBILITY REPORT (WCAG 2.2 AA)
### A-001 — No :focus-visible on any interactive element (CRITICAL)
- **WCAG SC:** 2.4.11 Focus Appearance (AA, new in 2.2)
- **Selector:** All `a, button, input, select, textarea`
- **Fix:** See M-005 above
### A-002 — Hamburger button has no accessible label (quiXzoom)
- **Selector:** `button.hamburger`
- **Issue:** No `aria-label`, no visible text, no `aria-expanded`
- **Fix:**
```html
<button class="hamburger" aria-label="Open navigation" aria-expanded="false" aria-controls="mobile-menu">
<!-- SVG icon -->
</button>
```
```js
// Toggle aria-expanded on click
document.querySelector('.hamburger').addEventListener('click', function() {
const expanded = this.getAttribute('aria-expanded') === 'true';
this.setAttribute('aria-expanded', String(!expanded));
});
```
### A-003 — `<main>` id="main-content" present, skip-link present (quiXzoom) ✅
- Skip-to-main-content link exists (`top: -100px`, activated on focus). Correct.
### A-004 — Touch targets ≥ 44×44px
- Hamburger: 38×32px (quiXzoom) — below WCAG 2.2 minimum 44×44px
- **Fix:**
```css
.hamburger {
min-width: 44px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
```
---
## CONSISTENCY MATRIX
| Element | quiXzoom homepage | quiXzoom /earn/ | Landvex homepage | Status |
|---|---|---|---|---|
| Nav: desktop links | ✅ (.nav-links visible) | Unknown | ❌ No nav links | INCONSISTENT |
| Nav: mobile hamburger | ✅ 38×32px | Unknown | Unknown | — |
| Footer: social icons | ❌ Raw URLs | Unknown | ✅ SVG icons | INCONSISTENT |
| Footer: legal links | 7 links, flex nowrap | Unknown | 34 links, flex nowrap | SAME BUG |
| Footer: copyright | Not extracted | Unknown | "© 2026 Landvex Inc" | — |
| Credits value (hero) | 1280 (fixed) | 1280 | N/A | CONSISTENT ✅ |
| Currency | USD ($28 floating card) | Unknown | N/A | OK ✅ |
| Dark theme bg | #0a0a0a | Unknown | #0a0a0a | CONSISTENT ✅ |
| Accent blue | #0066FF | Unknown | #0066FF | CONSISTENT ✅ |
---
## DESIGN TOKEN SYSTEM
```css
/* =============================================
SHARED TOKEN SYSTEM — quiXzoom + Landvex
Apply to :root in both sites' stylesheets
============================================= */
:root {
/* === COLORS === */
--color-bg-primary: #0a0a0a;
--color-bg-secondary: #111111;
--color-bg-surface: #181818;
--color-bg-white: #FFFFFF;
--color-bg-off: #F7F7F5;
--color-accent: #0066FF;
--color-accent-dark: #0052CC;
--color-accent-pale: rgba(0, 102, 255, 0.07);
--color-success: #00C853;
--color-warning: #FF9800;
--color-error: #FF3B30;
--color-text-primary: #FFFFFF;
--color-text-body: #3a3a3a;
--color-text-muted: #6B6B6B;
--color-text-dim: #888888;
--color-text-dark: #0a0a0a;
--color-border: rgba(255, 255, 255, 0.07);
--color-border-light: rgba(0, 0, 0, 0.1);
/* === TYPOGRAPHY === */
--font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Helvetica Neue', sans-serif;
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.375rem; /* 22px */
--text-2xl: 1.75rem; /* 28px */
--text-3xl: 2.25rem; /* 36px */
--text-4xl: 3rem; /* 48px */
--text-hero: clamp(2.75rem, 6vw, 5rem);
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--font-weight-extrabold: 800;
--font-weight-black: 900;
--line-height-tight: 1.1;
--line-height-snug: 1.3;
--line-height-normal: 1.65;
--line-height-relaxed: 1.8;
--measure: 65ch; /* max line length for body text */
/* === SPACING (4px base) === */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-10: 40px;
--space-12: 48px;
--space-16: 64px;
--space-20: 80px;
--space-24: 96px;
/* === BORDER RADIUS === */
--radius-sm: 8px;
--radius-md: 14px;
--radius-lg: 24px;
--radius-xl: 32px;
--radius-full: 100px;
/* === SHADOWS === */
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
--shadow-md: 0 8px 24px rgba(0, 0, 0, 0.12);
--shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.14);
--shadow-hover: 0 16px 48px rgba(0, 0, 0, 0.14);
/* === BREAKPOINTS (use in @media queries) === */
/* --bp-xs: 320px */
/* --bp-sm: 390px */
/* --bp-md: 768px */
/* --bp-lg: 1024px */
/* --bp-xl: 1280px */
/* --bp-2xl: 1440px */
/* --bp-3xl: 1920px */
/* === LAYOUT === */
--container-max: 1160px;
--container-pad: 24px;
--nav-height: 68px;
--section-pad: 96px;
/* === TRANSITIONS === */
--transition-base: 0.22s cubic-bezier(0.25, 0.46, 0.45, 0.94);
--transition-fast: 0.15s ease;
}
```
---
## IMPLEMENTATION ROADMAP
### Priority 1 — Overflow & Layout Crashes (fix immediately)
| ID | Fix | File | Est |
|---|---|---|---|
| C-001 | Hide `#mobile-menu` on desktop | quiXzoom/styles.css | 2 min |
| C-002 | Hide `.hamburger` on desktop | quiXzoom/styles.css | 1 min |
| C-003 | `flex-wrap: wrap` on `.footer-links` | Landvex/index.html inline style | 1 min |
| C-004 | `flex-wrap: wrap` on `.footer-legal-links` | quiXzoom/styles.css | 1 min |
| m-001 | Add `overflow-x: clip` to html/body | Both | 1 min |
**Total: ~6 minutes of CSS edits.**
### Priority 2 — Cross-site inconsistencies
| ID | Fix | File | Est |
|---|---|---|---|
| M-001 | Replace raw social URLs with SVG icons | quiXzoom footer | 20 min |
| M-002 | Add desktop nav links to Landvex | Landvex/index.html | 30 min |
| M-006 | Restructure Landvex footer into columns | Landvex/index.html | 45 min |
### Priority 3 — Accessibility failures (WCAG 2.2 AA)
| ID | Fix | File | Est |
|---|---|---|---|
| A-001/M-005 | Add `:focus-visible` styles globally | Both stylesheets | 5 min |
| A-002 | Add `aria-label` + `aria-expanded` to hamburger | quiXzoom/index.html | 5 min |
| A-004 | Expand hamburger touch target to 44×44px | quiXzoom/styles.css | 2 min |
### Priority 4 — Enterprise polish
| ID | Fix | Est |
|---|---|---|
| M-003 | Stat rows → auto-fit grid | 10 min |
| M-004 | Community pipeline badge white-space fix | 5 min |
| m-002/m-003 | Scrollbar gutter + layout polish | 10 min |
| Token system | Apply CSS variables to both sites | 2h |
| Footer columns | Landvex grouped footer nav | 45 min |
---
## GLOBAL SAFETY NET (add last, after all root causes fixed)
```css
/* Add to BOTH sites — last rule in stylesheet */
html,
body {
overflow-x: clip;
}
```
**Note:** `overflow-x: clip` (not `hidden`) preserves sticky positioning while preventing horizontal scroll. Apply only after root causes are fixed — never as primary solution.