dev-api: add developer portal, OpenAPI spec, and Flatenbadet case study
- New /developers/ page with API docs, SDKs, pricing, use cases - OpenAPI 3.0 spec for Orders, Missions, Photos, Analytics - Case study: Glasskiosken i Flatenbadet — complete ROI analysis - Updated /order/ with recurring missions and frequency dropdown
This commit is contained in:
@@ -0,0 +1,492 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>quiXzoom Frilans Payout</title>
|
||||
<link rel="stylesheet" href="/_design-system/apple-native/tokens.css">
|
||||
<link rel="stylesheet" href="/_design-system/apple-native/components.css">
|
||||
<style>
|
||||
/* Apple iPhone Native Styles */
|
||||
:root {
|
||||
--ios-blue: #007AFF;
|
||||
--ios-green: #34C759;
|
||||
--ios-red: #FF3B30;
|
||||
--ios-gray6: #F2F2F7;
|
||||
--ios-gray5: #E5E5EA;
|
||||
--label-primary: #000000;
|
||||
--label-secondary: rgba(60,60,67,0.6);
|
||||
--label-tertiary: rgba(60,60,67,0.3);
|
||||
--system-background: #FFFFFF;
|
||||
--secondary-system-background: #F2F2F7;
|
||||
--separator: #C6C6C8;
|
||||
--ios-radius: 13px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--label-primary: #FFFFFF;
|
||||
--label-secondary: rgba(235,235,245,0.6);
|
||||
--system-background: #000000;
|
||||
--secondary-system-background: #1C1C1E;
|
||||
--separator: #38383A;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', system-ui, sans-serif;
|
||||
background: var(--ios-gray6);
|
||||
color: var(--label-primary);
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 430px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
padding-top: max(16px, env(safe-area-inset-top));
|
||||
padding-bottom: max(16px, env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: linear-gradient(135deg, var(--ios-blue), #5856D6);
|
||||
border-radius: var(--ios-radius);
|
||||
padding: 24px;
|
||||
color: white;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
opacity: 0.8;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
background: var(--system-background);
|
||||
border-radius: var(--ios-radius);
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 16px;
|
||||
border-bottom: 0.5px solid var(--separator);
|
||||
}
|
||||
|
||||
.card-header h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* Market Grid */
|
||||
.market-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.market-btn {
|
||||
aspect-ratio: 1;
|
||||
border: 2px solid var(--ios-gray5);
|
||||
border-radius: var(--ios-radius);
|
||||
background: var(--system-background);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s cubic-bezier(0.32, 0.72, 0, 1);
|
||||
}
|
||||
|
||||
.market-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.market-btn.selected {
|
||||
border-color: var(--ios-blue);
|
||||
background: rgba(0,122,255,0.1);
|
||||
}
|
||||
|
||||
.market-btn .flag {
|
||||
font-size: 32px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.market-btn .code {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--label-primary);
|
||||
}
|
||||
|
||||
/* Options */
|
||||
.option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 0.5px solid var(--separator);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.option:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.option:active {
|
||||
background: var(--ios-gray5);
|
||||
}
|
||||
|
||||
.option-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.option-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.option-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.option-desc {
|
||||
font-size: 13px;
|
||||
color: var(--label-secondary);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.option-check {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--separator);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.option.selected .option-check {
|
||||
background: var(--ios-blue);
|
||||
border-color: var(--ios-blue);
|
||||
}
|
||||
|
||||
.option.selected .option-check::after {
|
||||
content: '✓';
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Calculator */
|
||||
.calc-input {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.calc-input input {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--separator);
|
||||
border-radius: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
background: var(--system-background);
|
||||
color: var(--label-primary);
|
||||
}
|
||||
|
||||
.calc-btn {
|
||||
padding: 16px 24px;
|
||||
background: var(--ios-blue);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Breakdown */
|
||||
.breakdown-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0;
|
||||
border-bottom: 0.5px solid var(--separator);
|
||||
}
|
||||
|
||||
.breakdown-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.breakdown-row.net {
|
||||
font-weight: 600;
|
||||
color: var(--ios-green);
|
||||
}
|
||||
|
||||
.breakdown-row.fee {
|
||||
color: var(--ios-red);
|
||||
}
|
||||
|
||||
/* Primary Button */
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
padding: 18px;
|
||||
background: var(--ios-blue);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
margin-top: 16px;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Info Box */
|
||||
.info-box {
|
||||
background: rgba(0,122,255,0.08);
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
font-size: 13px;
|
||||
color: var(--label-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Spring physics for all transitions */
|
||||
* {
|
||||
transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<h1>Frilans Payout</h1>
|
||||
<p>Get paid without a company</p>
|
||||
</div>
|
||||
|
||||
<!-- Market Selection -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Select Market</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="market-grid">
|
||||
<button class="market-btn selected" onclick="selectMarket('SE')">
|
||||
<span class="flag">🇸🇪</span>
|
||||
<span class="code">SE</span>
|
||||
</button>
|
||||
<button class="market-btn" onclick="selectMarket('NO')">
|
||||
<span class="flag">🇳🇴</span>
|
||||
<span class="code">NO</span>
|
||||
</button>
|
||||
<button class="market-btn" onclick="selectMarket('DK')">
|
||||
<span class="flag">🇩🇰</span>
|
||||
<span class="code">DK</span>
|
||||
</button>
|
||||
<button class="market-btn" onclick="selectMarket('FI')">
|
||||
<span class="flag">🇫🇮</span>
|
||||
<span class="code">FI</span>
|
||||
</button>
|
||||
<button class="market-btn" onclick="selectMarket('DE')">
|
||||
<span class="flag">🇩🇪</span>
|
||||
<span class="code">DE</span>
|
||||
</button>
|
||||
<button class="market-btn" onclick="selectMarket('NL')">
|
||||
<span class="flag">🇳🇱</span>
|
||||
<span class="code">NL</span>
|
||||
</button>
|
||||
<button class="market-btn" onclick="selectMarket('US')">
|
||||
<span class="flag">🇺🇸</span>
|
||||
<span class="code">US</span>
|
||||
</button>
|
||||
<button class="market-btn" onclick="selectMarket('GB')">
|
||||
<span class="flag">🇬🇧</span>
|
||||
<span class="code">GB</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tax Handling -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Tax Handling</h2>
|
||||
</div>
|
||||
<div class="card-body" style="padding: 0;">
|
||||
<div class="option selected" onclick="selectTax('platform')">
|
||||
<div class="option-icon" style="background: rgba(0,122,255,0.1);">🏢</div>
|
||||
<div class="option-content">
|
||||
<div class="option-title">quiXzoom handles tax</div>
|
||||
<div class="option-desc">We withhold tax and send KU10</div>
|
||||
</div>
|
||||
<div class="option-check"></div>
|
||||
</div>
|
||||
<div class="option" onclick="selectTax('self')">
|
||||
<div class="option-icon" style="background: rgba(255,149,0,0.1);">📄</div>
|
||||
<div class="option-content">
|
||||
<div class="option-title">I handle tax myself</div>
|
||||
<div class="option-desc">You declare and pay tax yourself</div>
|
||||
</div>
|
||||
<div class="option-check"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payout Method -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Payout Method</h2>
|
||||
</div>
|
||||
<div class="card-body" style="padding: 0;">
|
||||
<div class="option selected" onclick="selectMethod('frilans')">
|
||||
<div class="option-icon" style="background: rgba(52,199,89,0.1);">💳</div>
|
||||
<div class="option-content">
|
||||
<div class="option-title">quiXzoom Frilans</div>
|
||||
<div class="option-desc">Direct to account • Weekly</div>
|
||||
</div>
|
||||
<div class="option-check"></div>
|
||||
</div>
|
||||
<div class="option" onclick="selectMethod('stripe')">
|
||||
<div class="option-icon" style="background: rgba(88,86,214,0.1);">⚡</div>
|
||||
<div class="option-content">
|
||||
<div class="option-title">Stripe Connect</div>
|
||||
<div class="option-desc">Fastest • 1-2 days</div>
|
||||
</div>
|
||||
<div class="option-check"></div>
|
||||
</div>
|
||||
<div class="option" onclick="selectMethod('bank')">
|
||||
<div class="option-icon" style="background: rgba(175,82,222,0.1);">🏦</div>
|
||||
<div class="option-content">
|
||||
<div class="option-title">Bank Transfer</div>
|
||||
<div class="option-desc">Traditional • 3-5 days</div>
|
||||
</div>
|
||||
<div class="option-check"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Calculator -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Calculator</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="calc-input">
|
||||
<input type="number" id="calcAmount" value="1000" placeholder="Amount">
|
||||
<button class="calc-btn" onclick="calculate()">Calculate</button>
|
||||
</div>
|
||||
|
||||
<div id="breakdown" style="display: none;">
|
||||
<div class="breakdown-row">
|
||||
<span>Gross</span>
|
||||
<span id="gross">1,000 kr</span>
|
||||
</div>
|
||||
<div class="breakdown-row fee">
|
||||
<span>Platform (15%)</span>
|
||||
<span>-150 kr</span>
|
||||
</div>
|
||||
<div class="breakdown-row fee">
|
||||
<span>Processing (6%)</span>
|
||||
<span>-60 kr</span>
|
||||
</div>
|
||||
<div class="breakdown-row fee">
|
||||
<span>Tax (30%)</span>
|
||||
<span>-237 kr</span>
|
||||
</div>
|
||||
<div class="breakdown-row net">
|
||||
<span>Net to you</span>
|
||||
<span id="net">553 kr</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<button class="btn-primary" onclick="saveSettings()">Save Settings</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// State
|
||||
let state = {
|
||||
market: 'SE',
|
||||
taxHandling: 'platform',
|
||||
payoutMethod: 'frilans'
|
||||
};
|
||||
|
||||
function selectMarket(code) {
|
||||
state.market = code;
|
||||
document.querySelectorAll('.market-btn').forEach(btn => {
|
||||
btn.classList.toggle('selected', btn.querySelector('.code').textContent === code);
|
||||
});
|
||||
}
|
||||
|
||||
function selectTax(handling) {
|
||||
state.taxHandling = handling;
|
||||
document.querySelectorAll('.option').forEach((opt, i) => {
|
||||
if (i < 2) opt.classList.toggle('selected', i === (handling === 'platform' ? 0 : 1));
|
||||
});
|
||||
}
|
||||
|
||||
function selectMethod(method) {
|
||||
state.payoutMethod = method;
|
||||
const methods = ['frilans', 'stripe', 'bank'];
|
||||
document.querySelectorAll('.option').forEach((opt, i) => {
|
||||
if (i >= 2) opt.classList.toggle('selected', methods[i-2] === method);
|
||||
});
|
||||
}
|
||||
|
||||
function calculate() {
|
||||
const amount = parseInt(document.getElementById('calcAmount').value) || 0;
|
||||
const platformFee = Math.round(amount * 0.15);
|
||||
const processingFee = Math.round(amount * 0.06);
|
||||
const taxAmount = state.taxHandling === 'platform' ? Math.round((amount - platformFee) * 0.30) : 0;
|
||||
const net = amount - platformFee - processingFee - taxAmount;
|
||||
|
||||
document.getElementById('gross').textContent = amount.toLocaleString() + ' kr';
|
||||
document.getElementById('net').textContent = net.toLocaleString() + ' kr';
|
||||
document.getElementById('breakdown').style.display = 'block';
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
alert('Settings saved!\n\nMarket: ' + state.market + '\nTax: ' + state.taxHandling + '\nMethod: ' + state.payoutMethod);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user