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

20 KiB
Raw Blame History

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

{
  "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.

// 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

<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>
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):

<!-- 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

<!-- 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