Files
boc/reports/i18n-illustration-spec-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

500 lines
20 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 Spec — Internationalisation, Locale-Aware Sandbox & Visual Storytelling
**Landvex × quiXzoom**
**Datum:** 2026-06-20
**Uppdragsgivare:** Erik Svensson
**Status:** Produktionskrav — levereras till designteam + engineering
---
## DEL 1 — ELIMINERA ALLA SVENSKA STANDARDARTEFAKTER
### Princip
Standardupplevelsen är engelska + marknadens lokala kontext. Sverige är en marknad bland 20+, inte ett default.
### Inventering — svenska artefakter som måste bort
| Artefakt | Nuläge | Krav | Fil/yta |
|---|---|---|---|
| Exempeladress | "Storgatan 14, Stockholm" (floating card) | Dynamisk per marknad | quiXzoom homepage hero |
| Exempelvaluta i schema | `priceCurrency: "SEK"` (fixat) | EUR/USD som global default | quiXzoom index.html |
| Skatteinfo | "Grundavdrag ~17 500 SEK/year" som primärt exempel | Marknadsdynamisk eller bort från hero-kontext | quiXzoom FAQ-tabs |
| Juridisk text | `terms.html` på svenska (lang="sv") | Engelsk default, sv-variant under `/sv/` | quiXzoom/terms.html |
| `zoomer-start.html` | `<html lang="sv">`, "Bli Zoomer", hela texten på svenska | Primärsida ska vara engelska | nginx index-priority |
| Exempelnamn | Implicita svenska namn i onboarding-data | Marknadsneutrala eller dynamiska | |
| Karta/geo-default | Stockholmscentrerad demo-karta | Geo-IP eller marknadsselektion | quiXzoom map.html |
| Landvex exempeldata | Stockholmsvärden i intelligence-tabeller | Dynamisk per vald stad/marknad | Landvex cities/* |
### Implementation — Locale Architecture
```
DEFAULT LOCALE: en-001 (World English)
DEFAULT MARKET: none selected → prompt user
URL-struktur:
/ → en, marknadsneutral
/markets/se/ → sv-SE, svenska artefakter
/markets/de/ → de-DE, tyska artefakter
/markets/us/ → en-US, amerikanska artefakter
/markets/th/ → th-TH, thailändska artefakter
Cookie: qx_locale=de-DE (persisteras vid val)
HTTP: Accept-Language läses → föreslår marknad, aldrig tvingar
```
### Locale Data Bundle — per marknad
```json
{
"de-DE": {
"currency": "EUR",
"currencySymbol": "€",
"exampleAddress": "Maximilianstraße 12, München",
"exampleCompany": "Müller GmbH",
"examplePerson": "Hans Müller",
"exampleLegalId": "HRB 123456 AG München",
"examplePhone": "+49 89 12345678",
"examplePostalCode": "80539",
"exampleCity": "München",
"taxThreshold": "22.000 € (Kleinunternehmerregelung)",
"payoutRail": "SEPA",
"dateFormat": "DD.MM.YYYY",
"numberFormat": "1.234,56",
"mapCenter": [11.5820, 48.1351],
"missionExamples": [
{ "title": "Fassadeninspektion", "location": "Schwabing, München", "reward": 14 },
{ "title": "Parkplatzkartierung", "location": "Maxvorstadt", "reward": 9 }
]
},
"en-US": {
"currency": "USD",
"currencySymbol": "$",
"exampleAddress": "1234 Main St, Houston TX",
"exampleCompany": "Acme Corp",
"examplePerson": "James Wilson",
"exampleLegalId": "EIN 12-3456789",
"examplePhone": "+1 713 555 0123",
"examplePostalCode": "77002",
"exampleCity": "Houston",
"taxThreshold": "$400/year (self-employment threshold)",
"payoutRail": "ACH",
"dateFormat": "MM/DD/YYYY",
"numberFormat": "1,234.56",
"mapCenter": [-95.3698, 29.7604],
"missionExamples": [
{ "title": "Facade Survey", "location": "Midtown Houston", "reward": 18 },
{ "title": "Parking Lot Audit", "location": "Downtown Houston", "reward": 12 }
]
},
"th-TH": {
"currency": "THB",
"currencySymbol": "฿",
"exampleAddress": "123 Sukhumvit Rd, Khlong Toei, Bangkok",
"exampleCompany": "Thai Inspire Co., Ltd.",
"examplePerson": "Somchai Jaidee",
"exampleLegalId": "0105561012345",
"examplePhone": "+66 2 123 4567",
"examplePostalCode": "10110",
"exampleCity": "Bangkok",
"taxThreshold": "฿1,800,000/year (VAT threshold)",
"payoutRail": "PromptPay",
"dateFormat": "DD/MM/YYYY",
"numberFormat": "1,234.56",
"mapCenter": [100.5018, 13.7563],
"missionExamples": [
{ "title": "Shop Sign Survey", "location": "Silom, Bangkok", "reward": 120 },
{ "title": "Facade Condition", "location": "Chatuchak", "reward": 85 }
]
},
"ar-AE": {
"currency": "AED",
"currencySymbol": "د.إ",
"exampleAddress": "Al Wasl Road, Jumeirah, Dubai",
"exampleCompany": "Al Noor Trading LLC",
"exampleLegalId": "Commercial License 1234567",
"examplePhone": "+971 4 123 4567",
"exampleCity": "Dubai",
"payoutRail": "SWIFT",
"dateFormat": "DD/MM/YYYY",
"mapCenter": [55.2708, 25.2048],
"rtl": true,
"missionExamples": [
{ "title": "Building Facade Inspection", "location": "Business Bay, Dubai", "reward": 55 }
]
}
}
```
### Sandbox-propagering — tekniskt krav
Varje demo/sandbox-vy MÅSTEläsa från locale bundle. Inga hårdkodade värden.
```javascript
// Fel — hårdkodat
const demoCard = {
address: "Storgatan 14, Stockholm",
reward: "285 kr",
company: "Stockholms Stad"
};
// Rätt — dynamiskt
const locale = getLocale(); // "de-DE" från cookie/URL/GeoIP
const demoCard = {
address: localeData[locale].missionExamples[0].location,
reward: formatCurrency(localeData[locale].missionExamples[0].reward, locale),
company: localeData[locale].exampleCompany
};
```
**Propageras till:**
- Hero floating mission card
- Demo-kartan (mapCenter + exempelmarkeringar)
- Onboarding-flow exempeldata
- Earnings-kalkylator
- FAQ skatteinfo (visas bara för vald marknad)
- Landvex intelligence-tabeller (exempelrader)
- City pages (redan marknadsseparerade — korrekt arkitektur)
---
## DEL 2 — SANDBOX FÖLJER VALD MARKNAD
### Flöde
```
Användaren landar på quixzoom.com
GeoIP → föreslår marknad (banner: "Looks like you're in Germany. Switch to DE →")
Användaren väljer (eller behåller default)
Cookie qx_market=DE sätts
ALL demo-data propageras: adresser, valutor, namn, karta, skatteinfo, juridik
Onboarding → "How much can I earn in Germany?"
→ Visar tyska exempel, EUR, tyska skatttrösklar
```
### Market Selector Component
```html
<div class="market-selector" role="navigation" aria-label="Select your market">
<button class="market-btn" data-market="current">
<img src="/flags/{{current}}.svg" alt="" width="20" height="14" aria-hidden="true">
<span>{{currentMarketName}}</span>
<svg aria-hidden="true"><!-- chevron --></svg>
</button>
<div class="market-dropdown" role="listbox">
<button role="option" data-market="SE">🇸🇪 Sweden</button>
<button role="option" data-market="DE">🇩🇪 Germany</button>
<button role="option" data-market="US">🇺🇸 United States</button>
<button role="option" data-market="GB">🇬🇧 United Kingdom</button>
<button role="option" data-market="TH">🇹🇭 Thailand</button>
<button role="option" data-market="AE">🇦🇪 UAE</button>
<button role="option" data-market="NL">🇳🇱 Netherlands</button>
<button role="option" data-market="FR">🇫🇷 France</button>
<!-- +13 markets at launch -->
</div>
</div>
```
```javascript
document.querySelectorAll('[data-market]').forEach(btn => {
btn.addEventListener('click', () => {
const market = btn.dataset.market;
document.cookie = `qx_market=${market};path=/;max-age=31536000`;
updateAllLocaleContent(market); // triggers locale bundle swap
updateMap(localeData[market].mapCenter);
updateDemoCard(localeData[market].missionExamples[0]);
updateFAQTax(localeData[market]);
});
});
```
---
## DEL 3 — VISUELLT STORYTELLING
### Princip
> **Användaren ska förstå vad sidan gör inom 3 sekunder, innan de läser ett enda stycke.**
### Nuläge vs mål
| Yta | Nuläge | Mål |
|---|---|---|
| Hero | Text + telefon-mockup | Animated workflow: Mission → Zoomer → Verified data → Payout |
| How it works | 4 textblock | 4 illustrerade steg med process-ikoner + connectors |
| Tier pricing | 3 textkort | 3 illustrerade nivåkort med visuell differentiering |
| Landvex homepage | Textblock + dark hero | Contradictions-diagram + data-flow-illustration |
| Landvex methodology | Texttunga steg | Visuell pipeline: Observation → AI → Score → Report |
| Onboarding | Formulär | Illustrated steps med progress indicator |
| Empty states | Ingen data/text | Custom illustration per kontext |
### Illustration-system — specifikation
#### Stil
```
Stil: Flat / geometric, linjär
Färgpalett: --color-accent #0066FF + white + #0a0a0a + accent-green #00C853
Stroke: 2px consistent
Ikonstil: 24×24px grid, rounded corners 2px
Animationer: CSS-only, prefers-reduced-motion respected
Format: SVG (inline för animering, externa för statiska)
```
#### Prioriterade illustrationer att producera (ordning)
**quiXzoom — 8 illustrationer**
```
QX-ILL-01: Mission Flow (hero)
Visar: Briefcase icon → Map pin → Camera → Checkmark → Coins
Animation: left-to-right flow, 3s loop
Plats: Hero, ovanför/bredvid telefon-mockup
QX-ILL-02: How it Works — 4 steg
Steg 1: Browse Map → pin-ikon på karta
Steg 2: Claim Mission → handskaknings-ikon
Steg 3: Photograph → kamera-ikon
Steg 4: Get Paid → plånbok + check-ikon
Format: Horisontell rad desktop, vertikal stack mobil
QX-ILL-03: Tier Differentiation
Supplementary: singel person, 2-3 per vecka
Active: person + ryggsäck, 8-12 per vecka
Professional: person + utrustning + badge
QX-ILL-04: Identity Verification (onboarding)
Shield + ID-kort + checkmark
QX-ILL-05: Zoomer Profile Empty State
Person med kamera, "Your first mission awaits"
QX-ILL-06: Mission Approved
Checkmark-animation + mynt-ikon + "Credits added"
QX-ILL-07: Earnings Calculator
Slider → graf → utbetalningsikon
QX-ILL-08: Global Presence
Världskarta med markerade launch-länder, pulsande pins
```
**Landvex — 6 illustrationer**
```
LV-ILL-01: Contradiction Detection (hero)
Visar: Officiell rapport (dokument-ikon) ← → Faktisk observation (kamera-ikon)
Mittenpil med "≠" (contradiction marker)
Animation: dokumentet och kameran "pulsar" mot mitten
LV-ILL-02: Intelligence Pipeline (methodology)
Observation → Collection → AI Analysis → Scoring → Report → Decision
6 noder, connectors, ikoner per steg
LV-ILL-03: Control Intelligence Output
Dashboard-mockup med score (0-100 gauge) + trend + alert
LV-ILL-04: Data Coverage Map
Polygon-overlay på karta + coverage percentage
"94% commercial facade coverage, Södermalm, updated <30 days"
LV-ILL-05: Enterprise Client Flow
Request → Deploy → Collect → Analyse → Deliver
LV-ILL-06: Zoomer Network (quiXzoom → Landvex bridge)
Många prickar (Zoomers) → Aggregation-nod → Intelligence-output
Visuellt förklarar relationen mellan QX och LV
```
### Content Ratio Target
```
Per sida (desktop, above-the-fold):
Hero: 60% visual / 40% text
Feature section: 50% visual / 50% text
Proof/social: 30% visual / 70% text (social proof är text-tungt by nature)
CTA section: 70% visual / 30% text
Globalt per sida:
illustrations + UI: 40%
data + charts: 20%
text: 40%
```
### Workflow Diagram — quiXzoom How it Works
SVG-spec för QX-ILL-02 (inline, production-ready):
```svg
<!-- QX-ILL-02: How it Works — 4 Steps -->
<svg viewBox="0 0 800 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="How quiXzoom works: Browse, Claim, Photograph, Get Paid">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0066FF" opacity="0.4"/>
</marker>
</defs>
<!-- Step 1: Browse -->
<g transform="translate(60,60)">
<circle r="32" fill="#0066FF" opacity="0.1"/>
<circle r="32" fill="none" stroke="#0066FF" stroke-width="1.5" opacity="0.3"/>
<!-- Map pin icon -->
<path d="M0-14 C-8-14 -14-8 -14 0 C-14 10 0 20 0 20 C0 20 14 10 14 0 C14-8 8-14 0-14Z" fill="none" stroke="#0066FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="0" cy="0" r="4" fill="#0066FF"/>
<!-- Label -->
<text y="50" text-anchor="middle" font-family="system-ui,sans-serif" font-size="13" fill="#3a3a3a" font-weight="600">Browse</text>
<text y="64" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#6B6B6B">Find a mission near you</text>
</g>
<!-- Arrow 1→2 -->
<line x1="105" y1="60" x2="195" y2="60" stroke="#0066FF" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.4" marker-end="url(#arrow)"/>
<!-- Step 2: Claim -->
<g transform="translate(230,60)">
<circle r="32" fill="#0066FF" opacity="0.1"/>
<circle r="32" fill="none" stroke="#0066FF" stroke-width="1.5" opacity="0.3"/>
<!-- Handshake/check icon -->
<path d="M-10-6 L-4 0 L10-8" fill="none" stroke="#0066FF" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="-14" y="-14" width="28" height="28" rx="6" fill="none" stroke="#0066FF" stroke-width="1.5" opacity="0.5"/>
<text y="50" text-anchor="middle" font-family="system-ui,sans-serif" font-size="13" fill="#3a3a3a" font-weight="600">Claim</text>
<text y="64" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#6B6B6B">Accept the brief</text>
</g>
<!-- Arrow 2→3 -->
<line x1="275" y1="60" x2="365" y2="60" stroke="#0066FF" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.4" marker-end="url(#arrow)"/>
<!-- Step 3: Photograph -->
<g transform="translate(400,60)">
<circle r="32" fill="#0066FF" opacity="0.1"/>
<circle r="32" fill="none" stroke="#0066FF" stroke-width="1.5" opacity="0.3"/>
<!-- Camera icon -->
<rect x="-14" y="-8" width="28" height="20" rx="3" fill="none" stroke="#0066FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="0" cy="2" r="6" fill="none" stroke="#0066FF" stroke-width="2"/>
<path d="M-6-8 L-4-12 L4-12 L6-8" fill="none" stroke="#0066FF" stroke-width="1.5" stroke-linecap="round"/>
<text y="50" text-anchor="middle" font-family="system-ui,sans-serif" font-size="13" fill="#3a3a3a" font-weight="600">Photograph</text>
<text y="64" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#6B6B6B">Submit your photos</text>
</g>
<!-- Arrow 3→4 -->
<line x1="445" y1="60" x2="535" y2="60" stroke="#0066FF" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.4" marker-end="url(#arrow)"/>
<!-- Step 4: Get Paid -->
<g transform="translate(570,60)">
<circle r="32" fill="#00C853" opacity="0.12"/>
<circle r="32" fill="none" stroke="#00C853" stroke-width="1.5" opacity="0.4"/>
<!-- Coin/check icon -->
<circle cx="0" cy="0" r="12" fill="none" stroke="#00C853" stroke-width="2"/>
<path d="M-5 0 L-2 3 L6-4" fill="none" stroke="#00C853" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<text y="50" text-anchor="middle" font-family="system-ui,sans-serif" font-size="13" fill="#3a3a3a" font-weight="600">Get Paid</text>
<text y="64" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#6B6B6B">Credits paid weekly</text>
</g>
</svg>
```
### Landvex — Contradiction Detection Hero Illustration
```svg
<!-- LV-ILL-01: Contradiction Detection -->
<svg viewBox="0 0 600 200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Landvex identifies contradictions between reported reality and observed reality">
<defs>
<style>
@media (prefers-reduced-motion: no-preference) {
.pulse-left { animation: pulseLeft 2s ease-in-out infinite; }
.pulse-right { animation: pulseRight 2s ease-in-out 1s infinite; }
@keyframes pulseLeft { 0%,100%{opacity:.7} 50%{opacity:1} }
@keyframes pulseRight { 0%,100%{opacity:.7} 50%{opacity:1} }
}
</style>
</defs>
<!-- LEFT: Official Report -->
<g transform="translate(100,100)" class="pulse-left">
<rect x="-50" y="-60" width="100" height="120" rx="8" fill="#111" stroke="rgba(255,255,255,0.1)" stroke-width="1"/>
<!-- Doc lines -->
<line x1="-30" y1="-30" x2="30" y2="-30" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
<line x1="-30" y1="-16" x2="20" y2="-16" stroke="rgba(255,255,255,0.2)" stroke-width="1.5" stroke-linecap="round"/>
<line x1="-30" y1="-4" x2="25" y2="-4" stroke="rgba(255,255,255,0.2)" stroke-width="1.5" stroke-linecap="round"/>
<line x1="-30" y1="8" x2="15" y2="8" stroke="rgba(255,255,255,0.2)" stroke-width="1.5" stroke-linecap="round"/>
<!-- Green checkmark = "official says OK" -->
<circle cx="0" cy="32" r="14" fill="#00C853" opacity="0.15"/>
<path d="M-6 32 L-2 36 L8 26" fill="none" stroke="#00C853" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<text y="70" text-anchor="middle" font-family="system-ui,sans-serif" font-size="12" fill="rgba(255,255,255,0.5)">Official report</text>
<text y="83" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="rgba(255,255,255,0.3)">No issues found</text>
</g>
<!-- CENTER: Contradiction marker -->
<g transform="translate(300,100)">
<circle r="28" fill="#0066FF" opacity="0.08"/>
<circle r="28" fill="none" stroke="#0066FF" stroke-width="1" opacity="0.3"/>
<!-- ≠ symbol -->
<text text-anchor="middle" dy="7" font-size="28" font-weight="900" fill="#0066FF" opacity="0.8"></text>
<!-- Left arrow -->
<line x1="-60" y1="0" x2="-35" y2="0" stroke="#0066FF" stroke-width="1.5" stroke-dasharray="3 2" opacity="0.4"/>
<!-- Right arrow -->
<line x1="35" y1="0" x2="60" y2="0" stroke="#0066FF" stroke-width="1.5" stroke-dasharray="3 2" opacity="0.4"/>
</g>
<!-- RIGHT: Field Observation -->
<g transform="translate(500,100)" class="pulse-right">
<rect x="-50" y="-60" width="100" height="120" rx="8" fill="#111" stroke="rgba(255,255,255,0.1)" stroke-width="1"/>
<!-- Camera icon -->
<rect x="-24" y="-20" width="48" height="34" rx="4" fill="none" stroke="rgba(255,255,255,0.4)" stroke-width="1.5"/>
<circle cx="0" cy="-3" r="10" fill="none" stroke="rgba(255,255,255,0.4)" stroke-width="1.5"/>
<path d="-10-20 L-8-26 L8-26 L10-20" fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="1.5" stroke-linecap="round"/>
<!-- Red warning = "field says different" -->
<circle cx="0" cy="32" r="14" fill="#FF3B30" opacity="0.15"/>
<line x1="0" y1="25" x2="0" y2="34" stroke="#FF3B30" stroke-width="2.5" stroke-linecap="round"/>
<circle cx="0" cy="39" r="1.5" fill="#FF3B30"/>
<text y="70" text-anchor="middle" font-family="system-ui,sans-serif" font-size="12" fill="rgba(255,255,255,0.5)">Field observation</text>
<text y="83" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="rgba(255,255,255,0.3)">Defects detected</text>
</g>
</svg>
```
---
## IMPLEMENTATION — PRIORITETSORDNING
### Sprint 1 (12 veckor): Locale architecture
1. Skapa `locale-data.js` bundle med 6 marknader (SE, DE, US, GB, TH, AE)
2. Implementera market selector component
3. Koppla floating mission card i hero till locale bundle
4. Koppla demo-kartan till locale mapCenter
5. Sätt GeoIP-banner (max 1 fråga per session, kan stängas)
### Sprint 2 (23 veckor): Illustrationer
1. QX-ILL-01: Mission flow animation (hero)
2. QX-ILL-02: How it works — 4 steg (inline SVG, klar ovan)
3. LV-ILL-01: Contradiction detection (inline SVG, klar ovan)
4. LV-ILL-02: Intelligence pipeline
5. QX-ILL-03: Tier cards
6. QX-ILL-08: Global presence map
### Sprint 3 (34 veckor): Full propagation
1. Onboarding-flow lokalisering
2. Skatteinfo per marknad (FAQ-tabs)
3. Earnings calculator med lokal valuta
4. zoomer-start.html → engelska default + locale-aware
5. Resterende illustrationer (QX-ILL-04 through 07, LV-ILL-03 through 06)
---
## LEVERANS TILL DESIGNTEAM
**Fil:** `/home/bernt/.openclaw/workspace/reports/i18n-illustration-spec-2026-06-20.md`
**Innehåller:**
- Fullständig locale-arkitektur med 4 marknaders data-bundles
- Market selector component (HTML + JS)
- Illustration-spec: 14 illustrationer, stil-guide, prioritetsordning
- 2 production-ready SVG-illustrationer (QX-ILL-02 + LV-ILL-01)
- Content ratio targets per sidtyp
- 3-sprints implementation roadmap