Files
boc/quixzoom-landing-fixed/request-mission.html
T
Bernt aee0f09db8 landvex: Fixar och tester klara för alla komponenter
- Datafabrik: Dockerfile fix, agentorkestrering fungerar
- Vision: Identify-modell, FAISS, OCR alla testade
- API: Alla 7 integrationstester passerade
- Upplösare: Entitetsupplösning verifierad
2026-07-05 06:41:32 +00:00

425 lines
19 KiB
HTML
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.
<!DOCTYPE html>
<html lang="sv">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>quiXzoom — Beställ ett uppdrag</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #f2f2f7;
--surface: #ffffff;
--surface2: #f2f2f7;
--border: rgba(0,0,0,0.1);
--accent: #ff3b30;
--accent2: #34c759;
--blue: #007aff;
--text: #1c1c1e;
--text2: #6e6e73;
--font: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', sans-serif;
}
@media (prefers-color-scheme: dark) {
:root { --bg:#0a0a0f; --surface:#1c1c1e; --surface2:#2c2c2e; --border:rgba(255,255,255,0.08); --text:#f5f5f7; --text2:#8e8e93; }
}
body { background: var(--bg); color: var(--text); font-family: var(--font); min-height: 100vh; padding: 0; }
/* Header */
.header {
background: var(--surface); border-bottom: 1px solid var(--border);
padding: 16px 20px; display: flex; align-items: center; gap: 12px;
position: sticky; top: 0; z-index: 100;
}
.logo { font-size: 20px; font-weight: 700; letter-spacing: -0.5px; }
.logo span { color: var(--accent); }
.header-title { font-size: 15px; color: var(--text2); }
/* Layout */
.container { max-width: 680px; margin: 0 auto; padding: 24px 16px 48px; }
/* Section */
.section { background: var(--surface); border-radius: 16px; padding: 20px; margin-bottom: 16px; }
.section-title { font-size: 17px; font-weight: 600; margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
/* Form fields */
.field { margin-bottom: 16px; }
.field:last-child { margin-bottom: 0; }
.field-label { font-size: 13px; font-weight: 500; color: var(--text2); margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.3px; }
.field-input {
width: 100%; background: var(--surface2); border: 1.5px solid var(--border);
border-radius: 12px; padding: 12px 14px; font-size: 15px; color: var(--text);
font-family: var(--font); transition: border-color 0.15s;
}
.field-input:focus { outline: none; border-color: var(--blue); }
.field-input::placeholder { color: var(--text2); }
textarea.field-input { resize: vertical; min-height: 80px; }
select.field-input { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%236e6e73' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 14px center; padding-right: 36px; }
/* Map picker */
#location-map { width: 100%; height: 220px; border-radius: 12px; overflow: hidden; margin-top: 10px; border: 1.5px solid var(--border); }
.location-addr { margin-top: 8px; font-size: 13px; color: var(--text2); display: flex; align-items: center; gap: 6px; }
.location-pin { color: var(--accent); font-size: 16px; }
/* Budget slider */
.slider-wrap { margin-top: 8px; }
.budget-display { font-size: 24px; font-weight: 700; color: var(--accent); margin-bottom: 10px; }
input[type="range"] {
-webkit-appearance: none; width: 100%; height: 4px; border-radius: 2px;
background: linear-gradient(to right, var(--accent) 0%, var(--accent) var(--pct, 20%), var(--surface2) var(--pct, 20%), var(--surface2) 100%);
outline: none; cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; width: 22px; height: 22px; border-radius: 50%;
background: var(--accent); box-shadow: 0 2px 8px rgba(0,0,0,0.25); cursor: pointer;
}
.budget-labels { display: flex; justify-content: space-between; font-size: 11px; color: var(--text2); margin-top: 4px; }
/* Camera count */
.camera-selector { display: flex; align-items: center; gap: 14px; }
.count-btn {
width: 36px; height: 36px; border-radius: 50%; background: var(--surface2);
border: 1.5px solid var(--border); font-size: 20px; cursor: pointer; display: flex;
align-items: center; justify-content: center; color: var(--text); transition: background 0.1s;
}
.count-btn:hover { background: var(--border); }
.count-val { font-size: 22px; font-weight: 700; min-width: 36px; text-align: center; }
/* Type selector */
.type-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.type-btn {
background: var(--surface2); border: 1.5px solid var(--border); border-radius: 12px;
padding: 12px 8px; text-align: center; cursor: pointer; transition: all 0.15s;
}
.type-btn:hover { border-color: var(--blue); }
.type-btn.active { background: rgba(0,122,255,0.1); border-color: var(--blue); }
.type-icon { font-size: 22px; margin-bottom: 4px; }
.type-label { font-size: 12px; font-weight: 500; }
/* Submit */
.submit-section { padding: 0 0 8px; }
.submit-btn {
width: 100%; padding: 16px; background: var(--accent); color: white; border: none;
border-radius: 16px; font-size: 17px; font-weight: 600; cursor: pointer;
font-family: var(--font); transition: opacity 0.15s; display: flex; align-items: center; justify-content: center; gap: 8px;
}
.submit-btn:hover:not(:disabled) { opacity: 0.88; }
.submit-btn:disabled { opacity: 0.45; cursor: not-allowed; }
/* Confirmation */
.confirm-screen { display: none; text-align: center; padding: 40px 20px; }
.confirm-icon { font-size: 64px; margin-bottom: 16px; }
.confirm-title { font-size: 26px; font-weight: 700; letter-spacing: -0.5px; margin-bottom: 8px; }
.confirm-sub { font-size: 15px; color: var(--text2); margin-bottom: 28px; line-height: 1.5; }
.mission-id-box {
background: var(--surface2); border: 1px solid var(--border); border-radius: 14px;
padding: 16px; margin-bottom: 24px;
}
.mission-id-label { font-size: 12px; color: var(--text2); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
.mission-id-val { font-size: 22px; font-weight: 700; font-feature-settings: 'tnum'; letter-spacing: 2px; }
.track-btn {
display: inline-block; padding: 14px 32px; background: var(--blue); color: white;
border-radius: 14px; font-size: 16px; font-weight: 600; text-decoration: none;
transition: opacity 0.15s;
}
.track-btn:hover { opacity: 0.88; }
@media (max-width: 640px) {
.hero h1 { font-size: 2rem !important; }
.hero p { font-size: 1rem !important; }
.card { padding: 1.5rem !important; }
.grid { grid-template-columns: 1fr !important; }
.btn { padding: 0.75rem 1.5rem !important; font-size: 1rem !important; }
}
</style>
<style id="wcag-contrast-enforcement">
/* AAMOS GLOBAL CONTRAST ENFORCEMENT — WCAG AA (4.5:1 minimum) */
/* Regel: Ingen text under 70% opacity. Aldrig. */
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
/* Fix common low-contrast patterns */
[style*="rgba(60,60,67,0.4)"] { color: rgba(60,60,67,0.65) !important; }
[style*="rgba(60,60,67,0.3)"] { color: rgba(60,60,67,0.65) !important; }
[style*="rgba(255,255,255,0.4)"] { color: rgba(255,255,255,0.72) !important; }
[style*="rgba(255,255,255,0.3)"] { color: rgba(255,255,255,0.72) !important; }
[style*="opacity: 0.4"] { opacity: 0.72 !important; }
[style*="opacity: 0.3"] { opacity: 0.72 !important; }
@media (max-width: 640px) {
.hero h1 { font-size: 2rem !important; }
.hero p { font-size: 1rem !important; }
.card { padding: 1.5rem !important; }
.grid { grid-template-columns: 1fr !important; }
.btn { padding: 0.75rem 1.5rem !important; font-size: 1rem !important; }
}
</style>
<script src="i18n.js"></script>
<meta property="og:type" content="website">
<meta property="og:title" content="quiXzoom — Beställ ett uppdrag">
<meta property="og:description" content="quiXzoom — Earn per Mission. No Platform Fee.">
<meta property="og:url" content="https://www.quixzoom.com/request-mission.html">
<meta property="og:image" content="https://www.quixzoom.com/images/mission-map.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="quiXzoom — request a mission in your area">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://www.quixzoom.com/images/mission-map.jpg">
<meta name="twitter:title" content="quiXzoom — Beställ ett uppdrag">
</head>
<body>
<div class="header">
<div class="logo">qui<span>X</span>zoom</div>
<div class="header-title">/ Beställ uppdrag</div>
</div>
<div class="container" id="form-container">
<!-- Location -->
<div class="section">
<div class="section-title">📍 Plats</div>
<div class="field">
<div class="field-label">Adress</div>
<input class="field-input" id="address" type="text" placeholder="Ange adress eller klicka på kartan…" oninput="updateAddress(this.value)"/>
</div>
<div id="location-map"></div>
<div class="location-addr">
<span class="location-pin">📌</span>
<span id="chosen-location">Klicka på kartan för att välja plats</span>
</div>
<input type="hidden" id="lat" value="7.9519"/>
<input type="hidden" id="lng" value="98.2849"/>
</div>
<!-- Mission details -->
<div class="section">
<div class="section-title">📋 Uppdragsdetaljer</div>
<div class="field">
<div class="field-label">Titel / Beskrivning av uppdrag</div>
<input class="field-input" id="title" type="text" placeholder="T.ex. Hotell-lobby fotografering…"/>
</div>
<div class="field">
<div class="field-label">Vad ska fotograferas/filmas?</div>
<textarea class="field-input" id="description" placeholder="Beskriv detaljerat vad du vill att Zoomern ska fånga. Inkludera specifika vinklar, motiv eller krav."></textarea>
</div>
<div class="field">
<div class="field-label">Typ av uppdrag</div>
<div class="type-grid">
<div class="type-btn active" data-type="photography" onclick="selectType(this)">
<div class="type-icon">📷</div>
<div class="type-label">Foto</div>
</div>
<div class="type-btn" data-type="video" onclick="selectType(this)">
<div class="type-icon">🎬</div>
<div class="type-label">Video</div>
</div>
<div class="type-btn" data-type="drone" onclick="selectType(this)">
<div class="type-icon">🚁</div>
<div class="type-label">Drone</div>
</div>
<div class="type-btn" data-type="livestream" onclick="selectType(this)">
<div class="type-icon">📡</div>
<div class="type-label">Livestream</div>
</div>
<div class="type-btn" data-type="360" onclick="selectType(this)">
<div class="type-icon">🔄</div>
<div class="type-label">360°</div>
</div>
<div class="type-btn" data-type="documentary" onclick="selectType(this)">
<div class="type-icon">🎥</div>
<div class="type-label">Dokumentär</div>
</div>
</div>
<input type="hidden" id="mission-type" value="photography"/>
</div>
</div>
<!-- Date & time -->
<div class="section">
<div class="section-title">📅 Datum & Tid</div>
<div class="field">
<div class="field-label">Startdatum och tid</div>
<input class="field-input" id="date-time" type="datetime-local"/>
</div>
</div>
<!-- Cameras -->
<div class="section">
<div class="section-title">📸 Antal Zoomers</div>
<div class="field">
<div class="field-label">Önskat antal kameror / Zoomers</div>
<div class="camera-selector">
<button class="count-btn" onclick="changeCount(-1)"></button>
<div class="count-val" id="camera-count">1</div>
<button class="count-btn" onclick="changeCount(1)">+</button>
<span style="color:var(--text2);font-size:13px;">Zoomer(s)</span>
</div>
</div>
</div>
<!-- Budget -->
<div class="section">
<div class="section-title">💰 Budget</div>
<div class="field">
<div class="field-label">Budget (SEK)</div>
<div class="slider-wrap">
<div class="budget-display" id="budget-display">5 000 kr</div>
<input type="range" id="budget-slider" min="500" max="50000" value="5000" step="500" oninput="updateBudget(this.value)"/>
<div class="budget-labels"><span>500 kr</span><span>50 000 kr</span></div>
</div>
<input type="hidden" id="budget" value="5000"/>
</div>
</div>
<!-- Contact -->
<div class="section">
<div class="section-title">👤 Dina uppgifter</div>
<div class="field">
<div class="field-label">Namn</div>
<input class="field-input" id="contact-name" type="text" placeholder="För- och efternamn"/>
</div>
<div class="field">
<div class="field-label">E-post</div>
<input class="field-input" id="contact-email" type="email" placeholder="din@email.com"/>
</div>
<div class="field">
<div class="field-label">Telefon (valfritt)</div>
<input class="field-input" id="contact-phone" type="tel" placeholder="+46 70 123 45 67"/>
</div>
</div>
<!-- Submit -->
<div class="submit-section">
<button class="submit-btn" id="submit-btn" onclick="submitMission()">
📤 Skicka beställning
</button>
<div style="text-align:center;font-size:12px;color:var(--text2);margin-top:10px;">
Du bekräftar att uppdraget följer quiXzooms riktlinjer. Vi återkommer inom 30 minuter.
</div>
</div>
</div>
<!-- Confirmation screen -->
<div class="confirm-screen" id="confirm-screen">
<div class="confirm-icon">🎉</div>
<div class="confirm-title">Uppdrag beställt!</div>
<div class="confirm-sub">Vi har tagit emot din beställning och matchar dig med rätt Zoomer. Du får en bekräftelse via e-post inom kort.</div>
<div class="mission-id-box">
<div class="mission-id-label">Uppdragets ID</div>
<div class="mission-id-val" id="confirm-mission-id"></div>
</div>
<a class="track-btn" id="track-link" href="#">🗺️ Följ uppdraget live</a>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
// ── Map ───────────────────────────────────────────────────────────────────
const map = L.map('location-map', { center: [7.9519, 98.2849], zoom: 12 });
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap',
}).addTo(map);
let marker = L.marker([7.9519, 98.2849], { draggable: true }).addTo(map);
marker.on('dragend', e => {
const pos = e.target.getLatLng();
updateCoords(pos.lat, pos.lng);
});
map.on('click', e => {
marker.setLatLng(e.latlng);
updateCoords(e.latlng.lat, e.latlng.lng);
});
function updateCoords(lat, lng) {
document.getElementById('lat').value = lat.toFixed(6);
document.getElementById('lng').value = lng.toFixed(6);
document.getElementById('chosen-location').textContent = `${lat.toFixed(5)}°N, ${lng.toFixed(5)}°E`;
}
function updateAddress(addr) {
// Geocoding could be added here
document.getElementById('chosen-location').textContent = addr || 'Klicka på kartan för att välja plats';
}
// ── Type selector ─────────────────────────────────────────────────────────
function selectType(el) {
document.querySelectorAll('.type-btn').forEach(b => b.classList.remove('active'));
el.classList.add('active');
document.getElementById('mission-type').value = el.dataset.type;
}
// ── Camera count ──────────────────────────────────────────────────────────
let cameraCount = 1;
function changeCount(delta) {
cameraCount = Math.max(1, Math.min(10, cameraCount + delta));
document.getElementById('camera-count').textContent = cameraCount;
}
// ── Budget slider ─────────────────────────────────────────────────────────
function updateBudget(val) {
document.getElementById('budget').value = val;
document.getElementById('budget-display').textContent = parseInt(val).toLocaleString('sv-SE') + ' kr';
const pct = ((val - 500) / (50000 - 500)) * 100;
document.getElementById('budget-slider').style.setProperty('--pct', pct + '%');
}
updateBudget(5000);
// ── Default date/time (tomorrow 10:00) ────────────────────────────────────
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(10, 0, 0, 0);
document.getElementById('date-time').value = tomorrow.toISOString().slice(0, 16);
// ── Submit ────────────────────────────────────────────────────────────────
async function submitMission() {
const title = document.getElementById('title').value.trim();
const contactName = document.getElementById('contact-name').value.trim();
const contactEmail = document.getElementById('contact-email').value.trim();
if (!title) { alert('Ange en titel för uppdraget.'); return; }
if (!contactEmail) { alert('Ange din e-postadress.'); return; }
const btn = document.getElementById('submit-btn');
btn.disabled = true;
btn.textContent = '⏳ Skickar…';
const payload = {
title,
lat: document.getElementById('lat').value,
lng: document.getElementById('lng').value,
description: document.getElementById('description').value,
type: document.getElementById('mission-type').value,
date_time: document.getElementById('date-time').value,
cameras: cameraCount,
budget: document.getElementById('budget').value,
customer: contactName || contactEmail,
contact_name: contactName,
contact_email: contactEmail,
contact_phone: document.getElementById('contact-phone').value,
};
try {
const r = await fetch('https://api.quixzoom.com/api/qz/missions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const d = await r.json();
if (d.ok && d.mission) {
document.getElementById('form-container').style.display = 'none';
document.getElementById('confirm-screen').style.display = 'block';
document.getElementById('confirm-mission-id').textContent = d.mission.id.toUpperCase();
document.getElementById('track-link').href = `/quixzoom/mission-map.html?mission=${d.mission.id}`;
} else {
btn.disabled = false;
btn.textContent = '📤 Skicka beställning';
alert(d.error || 'Något gick fel. Försök igen.');
}
} catch(e) {
btn.disabled = false;
btn.textContent = '📤 Skicka beställning';
alert('Nätverksfel. Kontrollera din anslutning.');
}
}
</script>
</body>
</html>