Some checks failed
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled
CI / test (pull_request) Has been cancelled
CI / golangci-lint (pull_request) Has been cancelled
Security Scan / backend-security (pull_request) Has been cancelled
Security Scan / frontend-security (pull_request) Has been cancelled
Port Pricing.html verbatim to Vue: hero with preview pill, 4-tier grid, custom-amount slider, PricingCalculator subcomponent, works-everywhere grid, FAQ accordions, final CTA. Full zh/en pricing namespace (~200 keys each). SOON chip on Scale priority feature; zero-log FAQ uses inline parenthetical. Drop $5 bonus line; Enterprise → mailto, Binding/tiers → /register, docs link → /register/docs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
118 lines
5.9 KiB
Vue
118 lines
5.9 KiB
Vue
<template>
|
|
<div class="calc">
|
|
<div class="calc-left">
|
|
<div class="section-kicker">{{ $t('pricing.calc.kicker') }}</div>
|
|
<h3>{{ $t('pricing.calc.title') }}</h3>
|
|
<p class="sub">{{ $t('pricing.calc.sub') }}</p>
|
|
<div class="calc-controls">
|
|
<div class="slider-row">
|
|
<div class="s-top">
|
|
<span>{{ $t('pricing.calc.reqLabel') }}</span>
|
|
<span class="val">{{ reqValFormatted }}</span>
|
|
</div>
|
|
<input type="range" min="500" max="50000" step="500" v-model.number="req">
|
|
</div>
|
|
<div class="slider-row">
|
|
<div class="s-top">
|
|
<span>{{ $t('pricing.calc.tokLabel') }}</span>
|
|
<span class="val">{{ tokValFormatted }}</span>
|
|
</div>
|
|
<input type="range" min="500" max="10000" step="500" v-model.number="tok">
|
|
</div>
|
|
<div class="slider-row">
|
|
<div class="s-top">
|
|
<span>{{ $t('pricing.calc.mixLabel') }}</span>
|
|
<span class="val">{{ mix }}%</span>
|
|
</div>
|
|
<input type="range" min="0" max="100" step="10" v-model.number="mix">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="calc-right">
|
|
<div class="breakdown">
|
|
<div class="line"><span class="lab">{{ $t('pricing.calc.monthlyTok') }}</span><span class="v">{{ monthlyTokFmt }}</span></div>
|
|
<div class="line"><span class="lab">{{ $t('pricing.calc.officialCost') }}</span><span class="v">${{ officialCostFmt }}</span></div>
|
|
<div class="line"><span class="lab">{{ $t('pricing.calc.puroCost') }}</span><span class="v">${{ puroCostFmt }}</span></div>
|
|
<div class="line savings"><span class="lab">{{ $t('pricing.calc.savings') }}</span><span class="v">${{ saveFmt }} · {{ savePct }}%</span></div>
|
|
</div>
|
|
<div class="total-line">
|
|
<div>
|
|
<div class="lab">{{ $t('pricing.calc.recLabel') }}</div>
|
|
<div class="rec-note">{{ recNote }}</div>
|
|
</div>
|
|
<div class="big"><span class="curr">$</span>{{ recAmt }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
const { t } = useI18n()
|
|
|
|
const req = ref(5000)
|
|
const tok = ref(3000)
|
|
const mix = ref(50)
|
|
|
|
const reqValFormatted = computed(() => req.value.toLocaleString())
|
|
const tokValFormatted = computed(() => tok.value.toLocaleString())
|
|
|
|
const monthlyTok = computed(() => req.value * tok.value * 30)
|
|
const official = computed(() => {
|
|
const avg = (mix.value / 100) * 6 + (1 - mix.value / 100) * 3
|
|
return (monthlyTok.value / 1e6) * avg
|
|
})
|
|
const puro = computed(() => official.value * 0.3)
|
|
const save = computed(() => official.value - puro.value)
|
|
const savePct = computed(() => Math.round((save.value / official.value) * 100))
|
|
|
|
function fmtNum(n: number): string {
|
|
if (n >= 1e9) return (n / 1e9).toFixed(1) + 'B'
|
|
if (n >= 1e6) return (n / 1e6).toFixed(0) + 'M'
|
|
if (n >= 1e3) return (n / 1e3).toFixed(1) + 'k'
|
|
return String(n)
|
|
}
|
|
function fmtMoney(n: number): string {
|
|
return Math.round(n).toLocaleString('en-US')
|
|
}
|
|
|
|
const monthlyTokFmt = computed(() => fmtNum(monthlyTok.value))
|
|
const officialCostFmt = computed(() => fmtMoney(official.value))
|
|
const puroCostFmt = computed(() => fmtMoney(puro.value))
|
|
const saveFmt = computed(() => fmtMoney(save.value))
|
|
const recAmt = computed(() => Math.ceil(puro.value))
|
|
const recNote = computed(() => {
|
|
if (puro.value < 30) return t('pricing.calc.recStarter')
|
|
if (puro.value < 80) return t('pricing.calc.recPro')
|
|
return t('pricing.calc.recScale')
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.calc { border: 1px solid var(--border); border-radius: var(--r-xl); background: radial-gradient(600px 300px at 0% 0%, rgba(34,211,238,0.06), transparent 60%), radial-gradient(600px 300px at 100% 100%, rgba(168,85,247,0.06), transparent 60%), rgba(15, 23, 42, 0.4); padding: 32px 36px; display: grid; grid-template-columns: 1fr 1fr; gap: 40px; align-items: center; }
|
|
.calc-left h3 { font-size: 22px; font-weight: 700; letter-spacing: -0.01em; margin-bottom: 8px; }
|
|
.calc-left .sub { color: var(--text-2); font-size: 14px; line-height: 1.55; margin-bottom: 22px; }
|
|
.calc-controls { display: flex; flex-direction: column; gap: 14px; }
|
|
.slider-row .s-top { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 6px; align-items: baseline; }
|
|
.slider-row .s-top .val { font-family: var(--font-mono); font-weight: 700; color: var(--cyan); }
|
|
.slider-row input[type=range] { -webkit-appearance: none; width: 100%; height: 4px; background: var(--border); border-radius: 2px; outline: none; }
|
|
.slider-row input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%; background: var(--cyan); cursor: pointer; box-shadow: 0 0 0 4px rgba(34,211,238,0.15); }
|
|
.calc-right { background: rgba(2, 6, 23, 0.6); border: 1px solid var(--border); border-radius: var(--r-lg); padding: 28px; }
|
|
.calc-right .breakdown { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }
|
|
.calc-right .line { display: flex; justify-content: space-between; font-size: 13px; }
|
|
.calc-right .line .lab { color: var(--text-2); }
|
|
.calc-right .line .v { font-family: var(--font-mono); color: var(--text-0); }
|
|
.calc-right .line.savings .v { color: var(--green); }
|
|
.calc-right .total-line { padding-top: 14px; border-top: 1px dashed var(--border); display: flex; justify-content: space-between; align-items: baseline; }
|
|
.calc-right .total-line .lab { font-size: 12px; color: var(--text-3); font-family: var(--font-mono); text-transform: uppercase; letter-spacing: 0.14em; }
|
|
.calc-right .rec-note { font-size: 12px; color: var(--text-3); margin-top: 4px; }
|
|
.calc-right .total-line .big { font-family: var(--font-mono); font-size: 28px; font-weight: 800; color: var(--cyan); letter-spacing: -0.02em; }
|
|
.calc-right .total-line .big .curr { font-size: 14px; color: var(--text-3); font-weight: 500; margin-right: 2px; }
|
|
|
|
@media (max-width: 960px) {
|
|
.calc { grid-template-columns: 1fr; }
|
|
}
|
|
</style>
|