Files
boc/backups/2026-07-12-1023/quixzoom-landing-prod/assets/utils.js
T
Bernt 58ca4e68db feat(boc): Complete Business Operations Center v1.0
- 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
2026-07-12 12:41:35 +00:00

137 lines
6.9 KiB
JavaScript
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.
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 25× 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;
});