58ca4e68db
- Go backend API with full CRUD for all modules (CRM, Sales, Finance, HR, Legal, Marketing, Support, Purchase, Inventory, Projects, Automation, Analytics) - Rust analytics service with parallel report generation - C runtime with POSIX shared memory IPC - PostgreSQL schema with 30+ tables, full migrations - Redis cache, sessions, pub/sub - Kafka event streaming with Zookeeper - WebSocket hub for real-time updates - Automation engine with cron jobs, workflows, event triggers - JWT authentication, multi-tenant from start - Docker Compose with all services - Nginx reverse proxy with rate limiting - Integration tests passing - Feature gap analysis against Fortnox/Odoo/Visma Refs: BOC-001
137 lines
6.9 KiB
JavaScript
137 lines
6.9 KiB
JavaScript
|
||
function qxCalcUpdate(){
|
||
var m=parseInt(document.getElementById('calc-missions').value);
|
||
var r=parseInt(document.getElementById('calc-reward').value);
|
||
var loc=window.QX_CURRENT_LOCALE||{symbol:'€',currency:'EUR'};
|
||
var monthly=Math.round(m*r*4.3);
|
||
document.getElementById('calc-missions-val').textContent=m;
|
||
document.getElementById('calc-reward-val').textContent=loc.symbol+r;
|
||
document.getElementById('calc-output').textContent=loc.symbol+monthly;
|
||
}
|
||
document.addEventListener('qx:market',function(){qxCalcUpdate();});
|
||
|
||
|
||
|
||
// ── Glossary tooltip engine ───────────────────────────────────────────
|
||
const GLOSSARY = {
|
||
'Zoomer': { short: 'A verified field data collector on the quiXzoom platform.', slug: 'zoomer' },
|
||
'mission': { short: 'A paid photography assignment with defined location, scope and compensation.', slug: 'mission' },
|
||
'submission': { short: 'A completed set of photos submitted for AI review after a mission is executed.', slug: 'submission' },
|
||
'claim': { short: 'Reserving a mission — locks it exclusively for you within a defined time window.', slug: 'claim' },
|
||
'time window': { short: 'The duration a Zoomer has to complete and submit a claimed mission.', slug: 'time-window' },
|
||
'approval rate': { short: 'Percentage of submissions that pass AI quality review. Affects tier and mission access.', slug: 'approval-rate' },
|
||
'Stripe Connect': { short: 'The payment infrastructure that converts your QX Credit balance to cash and transfers it to your bank account via weekly payouts.', slug: 'stripe-connect' },
|
||
'KYC': { short: 'Know Your Customer — the one-time identity verification required before your first payout.', slug: 'kyc' },
|
||
'tier': { short: 'Your Zoomer level (Supplementary / Active / Professional) based on volume and approval rate.', slug: 'tier' },
|
||
'surge': { short: 'High-urgency missions deployed after events like storms or floods. Pay 2–5× standard rate.', slug: 'surge-mission' },
|
||
'AI review': { short: 'Automated quality and specification check run on every submission before payment.', slug: 'ai-review' },
|
||
'geo-tagged': { short: 'Images embedded with GPS coordinates — verified against mission location parameters.', slug: 'geo-tagged' },
|
||
'GDPR': { short: 'EU data protection regulation governing how quiXzoom handles your personal data.', slug: 'gdpr' },
|
||
'SLA': { short: 'Service Level Agreement — the delivery timeline and quality commitments made to orderers.', slug: 'sla' },
|
||
'API': { short: 'Application Programming Interface — how orderers programmatically access mission data.', slug: 'api' },
|
||
'webhook': { short: 'Automatic HTTP notification sent to orderers when a submission is approved.', slug: 'webhook' },
|
||
'IBAN': { short: 'International Bank Account Number — required for Stripe Connect payout setup.', slug: 'iban' },
|
||
'SEPA': { short: 'Single Euro Payments Area — the European bank transfer network used for EUR payouts.', slug: 'sepa' },
|
||
'VAT': { short: 'Value Added Tax — Zoomers are responsible for their own VAT obligations where applicable.', slug: 'vat' },
|
||
'payout': { short: 'Weekly transfer of QX Credits converted to cash, to your linked bank account. Requires reaching the minimum Credit threshold.', slug: 'payout' },
|
||
'rating': { short: 'Your accumulated quality score on the platform, affecting tier and mission access.', slug: 'rating' },
|
||
'specialisation': { short: 'A certified competency unlocking access to specific premium mission types.', slug: 'specialisation' },
|
||
'liveness check': { short: 'Selfie-based verification confirming a real person is behind the identity document.', slug: 'liveness-check' },
|
||
'AML': { short: 'Anti-Money Laundering regulation — the legal basis for KYC requirements on payment platforms.', slug: 'aml' },
|
||
};
|
||
|
||
const tip = document.createElement('div');
|
||
tip.className = 'gtooltip';
|
||
tip.innerHTML = '<span class="gtooltip-term"></span><span class="gtooltip-def"></span><a class="gtooltip-more">Full definition →</a>';
|
||
document.body.appendChild(tip);
|
||
|
||
document.addEventListener('mouseover', e => {
|
||
const el = e.target.closest('.gterm');
|
||
if (!el) return;
|
||
const key = el.dataset.gterm;
|
||
const g = GLOSSARY[key];
|
||
if (!g) return;
|
||
tip.querySelector('.gtooltip-term').textContent = key;
|
||
tip.querySelector('.gtooltip-def').textContent = g.short;
|
||
tip.querySelector('.gtooltip-more').href = '/glossary#' + g.slug;
|
||
tip.classList.add('visible');
|
||
});
|
||
|
||
document.addEventListener('mousemove', e => {
|
||
if (!tip.classList.contains('visible')) return;
|
||
const x = e.clientX + 12;
|
||
const y = e.clientY - 60;
|
||
tip.style.left = Math.min(x, window.innerWidth - 300) + 'px';
|
||
tip.style.top = Math.max(8, y) + 'px';
|
||
});
|
||
|
||
document.addEventListener('mouseout', e => {
|
||
if (!e.target.closest('.gterm')) return;
|
||
tip.classList.remove('visible');
|
||
});
|
||
|
||
|
||
|
||
|
||
// ── Scroll fade-in observer ───────────────────────────────────
|
||
(function(){
|
||
const obs = new IntersectionObserver((entries) => {
|
||
entries.forEach(e => {
|
||
if(e.isIntersecting){ e.target.classList.add('visible'); obs.unobserve(e.target); }
|
||
});
|
||
}, { threshold: 0.12 });
|
||
document.querySelectorAll('.fade-in').forEach(el => obs.observe(el));
|
||
// Staggered delay för card-groups
|
||
document.querySelectorAll('.steps-grid,.personas-grid,.stats-grid,.missions-grid,.earnings-grid').forEach(grid => {
|
||
grid.querySelectorAll('.fade-in').forEach((el,i) => {
|
||
el.style.transitionDelay = (i * 0.08) + 's';
|
||
});
|
||
});
|
||
})();
|
||
|
||
|
||
|
||
function toggleMenu(){
|
||
const h=document.getElementById('hamburger');
|
||
const m=document.getElementById('mobile-menu');
|
||
h.classList.toggle('open');
|
||
m.classList.toggle('open');
|
||
document.body.style.overflow=m.classList.contains('open')?'hidden':'';
|
||
}
|
||
function closeMenu(){
|
||
document.getElementById('hamburger').classList.remove('open');
|
||
document.getElementById('mobile-menu').classList.remove('open');
|
||
document.body.style.overflow='';
|
||
}
|
||
// Close menu on resize to desktop
|
||
window.addEventListener('resize',()=>{if(window.innerWidth>768)closeMenu();});
|
||
|
||
function toggleDrop(btn) {
|
||
const li = btn.closest('li');
|
||
const wasOpen = li.classList.contains('open');
|
||
// Stäng alla
|
||
document.querySelectorAll('.nav-links > li.open').forEach(el => el.classList.remove('open'));
|
||
if (!wasOpen) li.classList.add('open');
|
||
}
|
||
// Stäng dropdown vid klick utanför
|
||
document.addEventListener('click', e => {
|
||
if (!e.target.closest('.nav-links')) {
|
||
document.querySelectorAll('.nav-links > li.open').forEach(el => el.classList.remove('open'));
|
||
}
|
||
});
|
||
|
||
|
||
|
||
document.addEventListener('click', function(e){
|
||
var ms = document.getElementById('market-selector');
|
||
if(ms && !ms.contains(e.target)) ms.classList.remove('open');
|
||
});
|
||
|
||
/* Sprint 3: dynamic tax note */
|
||
document.addEventListener('qx:market', function(e){
|
||
var d = window.QX_LOCALES[e.detail.market];
|
||
if(!d) return;
|
||
var el = document.getElementById('qx-tax-note');
|
||
if(el) el.textContent = d.taxNote;
|
||
});
|