feat(CG4d): Prisingskalkulator panel — GCP-spend slider, tier-highlight, ROI-estimat

This commit is contained in:
chrischristiansen-glitch 2026-06-12 16:47:27 +02:00
parent 4a1e23e077
commit 21efdc4e95

View File

@ -252,6 +252,37 @@
.md-viewer blockquote{border-left:3px solid var(--accent);padding:8px 16px;margin:12px 0;background:rgba(124,92,255,.06);border-radius:0 8px 8px 0;color:var(--text-dim)}
.md-viewer hr{border:none;border-top:1px solid var(--border);margin:20px 0}
.md-viewer-loading{display:flex;align-items:center;justify-content:center;height:200px;color:var(--text-faint);font-family:var(--mono);font-size:13px;gap:10px}
/* PRICING PANEL (CG4d) */
.pricing-hero{text-align:center;padding:32px 0 28px}
.pricing-hero-title{font-size:22px;font-weight:800;letter-spacing:-0.02em;margin-bottom:6px}
.pricing-hero-sub{font-size:13px;color:var(--text-dim)}
.slider-wrap{background:var(--bg-2);border:1px solid var(--border);border-radius:var(--radius);padding:24px 28px;margin-bottom:28px}
.slider-label-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
.slider-label{font-size:12px;font-weight:700;color:var(--text-faint);text-transform:uppercase;letter-spacing:0.06em}
.slider-value{font-size:22px;font-weight:800;letter-spacing:-0.02em;color:var(--accent-2)}
#spend-slider{width:100%;-webkit-appearance:none;appearance:none;height:6px;border-radius:3px;background:var(--bg-3);outline:none;cursor:pointer}
#spend-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:20px;height:20px;border-radius:50%;background:var(--accent);cursor:pointer;border:3px solid var(--bg);box-shadow:0 0 0 2px var(--accent)}
#spend-slider::-moz-range-thumb{width:20px;height:20px;border-radius:50%;background:var(--accent);cursor:pointer;border:3px solid var(--bg)}
.slider-ticks{display:flex;justify-content:space-between;margin-top:8px}
.slider-tick{font-size:10px;color:var(--text-faint);font-family:var(--mono)}
.tier-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:28px}
@media(max-width:800px){.tier-grid{grid-template-columns:repeat(2,1fr)}}
.tier-card{background:var(--bg-2);border:2px solid var(--border);border-radius:var(--radius);padding:18px 16px;transition:border-color .18s,background .18s,box-shadow .18s;cursor:default}
.tier-card.active{border-color:var(--accent);background:rgba(124,92,255,.08);box-shadow:0 0 0 1px var(--accent),0 4px 24px rgba(124,92,255,.18)}
.tier-name{font-size:13px;font-weight:800;letter-spacing:0.02em;margin-bottom:4px}
.tier-range{font-size:11px;color:var(--text-faint);font-family:var(--mono);margin-bottom:12px}
.tier-price{font-size:24px;font-weight:800;letter-spacing:-0.03em}
.tier-price-sub{font-size:10px;color:var(--text-faint);margin-top:2px}
.tier-card.active .tier-name{color:var(--accent)}
.tier-card.active .tier-price{color:var(--text)}
.roi-box{background:var(--bg-2);border:1px solid var(--border);border-radius:var(--radius);padding:24px 28px;display:flex;gap:28px;align-items:center;flex-wrap:wrap}
.roi-stat{flex:1;min-width:140px}
.roi-label{font-size:11px;color:var(--text-faint);text-transform:uppercase;letter-spacing:0.06em;font-weight:600;margin-bottom:6px}
.roi-value{font-size:32px;font-weight:800;letter-spacing:-0.03em;color:var(--green)}
.roi-sub{font-size:12px;color:var(--text-dim);margin-top:4px;line-height:1.5}
.roi-divider{width:1px;height:60px;background:var(--border);flex-shrink:0}
@media(max-width:600px){.roi-divider{display:none}.roi-box{gap:16px}}
</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
</head>
@ -278,6 +309,9 @@
<div class="nav-item" onclick="go('billing')" id="nav-billing">
<span class="nav-icon">💰</span> CostGuard
</div>
<div class="nav-item" onclick="go('pricing')" id="nav-pricing">
<span class="nav-icon">🧮</span> Kalkulator
</div>
<div class="nav-item" onclick="go('build')" id="nav-build">
<span class="nav-icon">🔨</span> Cloud Build
<div class="build-dot" id="sb-build-dot"></div>
@ -377,6 +411,60 @@
</div>
</div>
<!-- PRICING (CG4d) -->
<div class="panel" id="panel-pricing">
<div class="panel-head">
<div class="p-avatar" style="background:linear-gradient(135deg,#7c5cff,#00d4a8)">🧮</div>
<div>
<div class="p-title">Prisingskalkulator</div>
<div class="p-sub">CostGuard · ROI-estimat basert på GCP-spend</div>
</div>
</div>
<div class="panel-body">
<div class="pricing-hero">
<div class="pricing-hero-title">Hva koster CostGuard deg?</div>
<div class="pricing-hero-sub">Dra slideren til din månedlige GCP-spend — se tier og ROI øyeblikkelig</div>
</div>
<div class="slider-wrap">
<div class="slider-label-row">
<span class="slider-label">Månedlig GCP-spend</span>
<span class="slider-value" id="spend-display">$5 000</span>
</div>
<input type="range" id="spend-slider" min="0" max="100000" step="500" value="5000" oninput="updatePricing(this.value)">
<div class="slider-ticks">
<span class="slider-tick">$0</span>
<span class="slider-tick">$25k</span>
<span class="slider-tick">$50k</span>
<span class="slider-tick">$75k</span>
<span class="slider-tick">$100k</span>
</div>
</div>
<div class="tier-grid" id="tier-grid"></div>
<div class="roi-box">
<div class="roi-stat">
<div class="roi-label">Estimert månedlig besparelse</div>
<div class="roi-value" id="roi-savings">$750</div>
<div class="roi-sub" id="roi-savings-sub">15% av GCP-spend via optimalisering og varsler</div>
</div>
<div class="roi-divider"></div>
<div class="roi-stat">
<div class="roi-label">ROI — CostGuard betaler seg</div>
<div class="roi-value" id="roi-multiplier">0.75×</div>
<div class="roi-sub" id="roi-multiplier-sub">per måned · basert på $999/mnd</div>
</div>
<div class="roi-divider"></div>
<div class="roi-stat">
<div class="roi-label">Netto gevinst per år</div>
<div class="roi-value" id="roi-annual">$3 012</div>
<div class="roi-sub">Besparelse minus abonnement · 12 mnd</div>
</div>
</div>
</div>
</div>
<!-- BUILD -->
<div class="panel" id="panel-build">
<div class="panel-head">
@ -504,6 +592,7 @@ const MD_DOCS = [
const PANEL_META = {
jason: ['Jason', 'Gemini 2.5 Pro · ADK'],
billing: ['CostGuard', 'Token & kostnad · live'],
pricing: ['Prisingskalkulator', 'CostGuard · ROI-estimat'],
build: ['Cloud Build', 'Pipeline · propane-will-491900-m5'],
vms: ['VM-er', 'Compute Engine · us-central1-b'],
sites: ['Sites & Moduler', 'Alle Vauco-flater'],
@ -520,6 +609,67 @@ const SITES_DATA = [
{icon:'🔧', name:'Jason Chat', url:'https://opax.vauco.no/static/jason.html', desc:'Jason standalone UI', status:'live'},
]
// PRICING TIERS (CG4d — locked from MASTERPLAN)
const TIERS = [
{ name:'Starter', min:0, max:3000, price:499, color:'var(--accent-2)' },
{ name:'Guard', min:3000, max:15000, price:999, color:'var(--accent)' },
{ name:'Shield', min:15000, max:50000, price:1999, color:'var(--amber)' },
{ name:'Enterprise', min:50000, max:Infinity,price:3500, color:'var(--danger)' },
]
function fmtUSD(n){
if(n>=1000) return '$'+Math.round(n).toLocaleString('en-US')
return '$'+n
}
function activeTier(spend){
return TIERS.find(t => spend >= t.min && spend < t.max) || TIERS[TIERS.length-1]
}
function renderTierGrid(spend){
const active = activeTier(spend)
const grid = document.getElementById('tier-grid')
grid.innerHTML = TIERS.map(t => {
const isActive = t.name === active.name
const rangeStr = t.max === Infinity ? fmtUSD(t.min)+'+' : fmtUSD(t.min)+''+fmtUSD(t.max)
return `<div class="tier-card${isActive?' active':''}">
<div class="tier-name" style="color:${isActive?t.color:'var(--text-faint)'}">${t.name}</div>
<div class="tier-range">${rangeStr}/mnd</div>
<div class="tier-price" style="color:${isActive?t.color:'var(--text-dim)'}">${fmtUSD(t.price)}</div>
<div class="tier-price-sub">per måned</div>
</div>`
}).join('')
}
function updatePricing(val){
const spend = Number(val)
const tier = activeTier(spend)
document.getElementById('spend-display').textContent = fmtUSD(spend)
renderTierGrid(spend)
// ROI: 15% of spend saved via CostGuard optimisation
const savings = spend * 0.15
const net_monthly = savings - tier.price
const net_annual = net_monthly * 12
const multiplier = tier.price > 0 ? (savings / tier.price) : 0
document.getElementById('roi-savings').textContent = fmtUSD(Math.round(savings))
document.getElementById('roi-savings-sub').textContent = '15% av ' + fmtUSD(spend) + ' via optimalisering og varsler'
document.getElementById('roi-multiplier').textContent = multiplier.toFixed(2) + '×'
document.getElementById('roi-multiplier-sub').textContent = 'per måned · basert på ' + fmtUSD(tier.price) + '/mnd'
const annualEl = document.getElementById('roi-annual')
if(net_annual >= 0){
annualEl.textContent = fmtUSD(Math.round(net_annual))
annualEl.style.color = 'var(--green)'
} else {
annualEl.textContent = '' + fmtUSD(Math.round(Math.abs(net_annual)))
annualEl.style.color = 'var(--danger)'
}
}
// SESSION
function sid(){
let s = sessionStorage.getItem(SESSION_KEY)
@ -547,6 +697,13 @@ function go(name){
if(name==='sites') renderSites()
if(name==='terminal') initTerminal()
if(name==='md') renderMdGrid()
if(name==='pricing') initPricing()
}
function initPricing(){
const slider = document.getElementById('spend-slider')
renderTierGrid(Number(slider.value))
updatePricing(slider.value)
}
// BUILD STATUS POLL
@ -589,7 +746,7 @@ async function loadBilling(){
h+='<div class="sec-title">⚠️ Anomalier</div><div class="anomaly-list">'
anoms.slice(0,5).forEach(a=>{
const sev=(a.severity||'').toLowerCase()
h+=`<div class="anom"><div class="anom-icon">${sev==='high'?'🔴':'🟡'}</div><div class="anom-info"><div class="anom-title">${esc(a.service||a.type||'Ukjent')}</div><div class="anom-desc">${esc(a.description||a.message||'')}</div></div><div class="anom-badge ${sev==='high'?'high':''}">${esc(a.severity||'medium')}</div></div>`
h+=`<div class="anom"><div class="anom-icon">${sev==='high'?'🔴':'🟡'}</div><div class="anom-info"><div class="anom-title">${esc(a.service||a.type||'Ukjent')}</div><div class="anom-desc">${esc(a.description||a.message||'')}</div></div><div class="anom-badge ${sev==='high'?'high':''}"">${esc(a.severity||'medium')}</div></div>`
})
h+='</div>'
}else{ h+='<div class="sec-title" style="color:var(--green)">✅ Ingen anomalier</div>' }
@ -890,6 +1047,9 @@ function exportHistory(){
}
;(function(){ const h=lh();if(!h.length)return;h.forEach(m=>appendMsg(m.role,m.text,m.ts));document.getElementById('messages').scrollTop=99999 })()
inp.focus()
// Init pricing on load so slider is ready if user navigates directly
renderTierGrid(5000)
updatePricing(5000)
</script>
</body>
</html>
</html>