feat(pricing): add PricingView + calculator with bilingual i18n
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
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>
This commit is contained in:
117
frontend/src/components/puro/PricingCalculator.vue
Normal file
117
frontend/src/components/puro/PricingCalculator.vue
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<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>
|
||||||
@@ -5633,6 +5633,7 @@ export default {
|
|||||||
landing: {
|
landing: {
|
||||||
nav: {
|
nav: {
|
||||||
products: 'Products',
|
products: 'Products',
|
||||||
|
pricing: 'Pricing',
|
||||||
docs: 'Docs',
|
docs: 'Docs',
|
||||||
login: 'Sign in',
|
login: 'Sign in',
|
||||||
signup: 'Free trial →',
|
signup: 'Free trial →',
|
||||||
@@ -5712,6 +5713,209 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pricing: {
|
||||||
|
nav: {
|
||||||
|
products: 'Products',
|
||||||
|
pricing: 'Pricing',
|
||||||
|
docs: 'Docs',
|
||||||
|
login: 'Sign in',
|
||||||
|
signup: 'Free trial →',
|
||||||
|
},
|
||||||
|
hero: {
|
||||||
|
kicker: '// pricing · top up · pay as you go · never expires',
|
||||||
|
previewPill: '// preview · final pricing TBD at launch',
|
||||||
|
title1: 'Top up once,',
|
||||||
|
titleAccent: 'works across',
|
||||||
|
title2: 'all platforms',
|
||||||
|
sub: 'One credit balance works across Claude / ChatGPT / Gemini pools. We turn your subscription quota into real API credits — {discount} cheaper than official APIs.',
|
||||||
|
subDiscount: 'up to 70%',
|
||||||
|
underline: 'Credits never expire · Alipay / WeChat / USDT supported · No hidden fees',
|
||||||
|
},
|
||||||
|
soonChip: 'SOON',
|
||||||
|
tiers: {
|
||||||
|
starter: {
|
||||||
|
flag: 'STARTER',
|
||||||
|
tierLabel: 'tier · 01',
|
||||||
|
headline: 'Dip your toes in, get connected',
|
||||||
|
credit: 'Top up ${creditAmount} → get {creditBonus} credits',
|
||||||
|
creditAmount: '9.9',
|
||||||
|
creditBonus: '$12',
|
||||||
|
discountTag: 'vs. official API · from 5× cheaper',
|
||||||
|
cta: 'Top up →',
|
||||||
|
features: {
|
||||||
|
allModels: 'All models / all pools',
|
||||||
|
oneKey: 'API key',
|
||||||
|
rpm60: '60 RPM rate limit',
|
||||||
|
log7: 'Basic logs (7-day retention)',
|
||||||
|
noBYOS: 'Bring your own subscription',
|
||||||
|
noTeam: 'Team / multi-user collaboration',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pro: {
|
||||||
|
flag: '◆ RECOMMENDED',
|
||||||
|
tierLabel: 'tier · 02',
|
||||||
|
headline: 'Power users · Best value',
|
||||||
|
credit: 'Top up ${creditAmount} → get {creditBonus} credits',
|
||||||
|
creditAmount: '29.9',
|
||||||
|
creditBonus: '$45',
|
||||||
|
discountTag: 'vs. official API · 3–7× cheaper',
|
||||||
|
cta: 'Buy Pro →',
|
||||||
|
features: {
|
||||||
|
allModels: 'All models / all pools',
|
||||||
|
threeKeys: 'API keys · separate budgets',
|
||||||
|
rpm120: '120 RPM rate limit',
|
||||||
|
log30: 'Call logs (30-day retention)',
|
||||||
|
byos: 'Bring your own subscription (unlimited)',
|
||||||
|
failover: 'Multi-account failover scheduling',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
flag: '⚡ LIMITED +100%',
|
||||||
|
tierLabel: 'tier · 03',
|
||||||
|
headline: 'Small teams / long-haul projects',
|
||||||
|
credit: 'Top up ${creditAmount} → get {creditBonus} credits',
|
||||||
|
creditAmount: '99',
|
||||||
|
creditBonus: '$198',
|
||||||
|
discountTag: 'vs. official API · 2–5× cheaper',
|
||||||
|
cta: 'Top up →',
|
||||||
|
features: {
|
||||||
|
proAll: 'All Pro capabilities',
|
||||||
|
tenKeys: 'API keys · separate budgets',
|
||||||
|
rpm300: '300 RPM rate limit',
|
||||||
|
log90: 'Call logs (90-day retention)',
|
||||||
|
priorityCount: '',
|
||||||
|
priority: 'Priority-weighted scheduling',
|
||||||
|
community: 'Slack / Discord community support',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
flag: 'CUSTOM',
|
||||||
|
tierLabel: 'tier · 04',
|
||||||
|
headline: 'Custom amount · top up on demand',
|
||||||
|
creditPrefix: 'Get approx.',
|
||||||
|
bonusPrefix: '+',
|
||||||
|
discountTag: 'Discount tier matched automatically by amount',
|
||||||
|
cta: 'Custom amount →',
|
||||||
|
features: {
|
||||||
|
neverExpire: 'Credits never expire',
|
||||||
|
proAll: 'All Pro capabilities',
|
||||||
|
tiered: 'Tiered +21% ~ +100%',
|
||||||
|
payment: 'Alipay / WeChat / USDT',
|
||||||
|
preview: 'Drag slider to preview bonus',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
enterprise: {
|
||||||
|
title: 'Enterprise · Custom plans',
|
||||||
|
desc: 'Dedicated subscription pool, SLA, compliance audit, private deployment, invoice billing. Starting at >$500/mo.',
|
||||||
|
cta: 'Contact us →',
|
||||||
|
},
|
||||||
|
binding: {
|
||||||
|
title: 'Already subscribed? Connect it.',
|
||||||
|
desc: 'Have Claude Max / ChatGPT Pro? Register free, bind your subscription, and only pay for PURO routing — {price} per request.',
|
||||||
|
price: '$0.0008/request',
|
||||||
|
cta: 'Connect my subscription →',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
calc: {
|
||||||
|
kicker: '// cost estimator',
|
||||||
|
previewPill: '// estimated · for reference only',
|
||||||
|
title: 'How much could you save?',
|
||||||
|
sub: 'Estimate your monthly spend on PURO vs. official APIs based on your usage. Numbers update as you move the sliders.',
|
||||||
|
reqLabel: 'Daily requests',
|
||||||
|
tokLabel: 'Avg tokens per request',
|
||||||
|
mixLabel: 'Claude share',
|
||||||
|
monthlyTok: 'Monthly token usage',
|
||||||
|
officialCost: 'Official API cost',
|
||||||
|
puroCost: 'PURO cost (incl. +50% bonus)',
|
||||||
|
savings: 'Savings',
|
||||||
|
recLabel: 'Suggested top-up',
|
||||||
|
recStarter: '≈ Starter tier covers it',
|
||||||
|
recPro: '≈ Pro tier · 1 month',
|
||||||
|
recScale: '≈ Scale tier · 1 month',
|
||||||
|
},
|
||||||
|
works: {
|
||||||
|
kicker: '// works everywhere',
|
||||||
|
title: 'One key, every tool',
|
||||||
|
sub: 'Any tool that supports a custom {baseUrl} or the OpenAI / Anthropic API works with PURO out of the box.',
|
||||||
|
baseUrl: 'base_url',
|
||||||
|
tools: {
|
||||||
|
claudeCode: 'Claude Code',
|
||||||
|
cursor: 'Cursor',
|
||||||
|
cline: 'Cline',
|
||||||
|
rooCode: 'Roo Code',
|
||||||
|
continueTag: 'Continue',
|
||||||
|
openaiSdk: 'OpenAI SDK',
|
||||||
|
anthropicSdk: 'Anthropic SDK',
|
||||||
|
openWebui: 'Open WebUI',
|
||||||
|
langchain: 'LangChain',
|
||||||
|
llamaIndex: 'LlamaIndex',
|
||||||
|
zed: 'Zed',
|
||||||
|
more: 'More…',
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
claudeCode: 'ANTHROPIC_BASE_URL',
|
||||||
|
cursor: 'Custom model',
|
||||||
|
cline: 'OpenAI compat.',
|
||||||
|
rooCode: 'OpenAI compat.',
|
||||||
|
continueTag: 'config.yaml',
|
||||||
|
openaiSdk: 'Python / Node',
|
||||||
|
anthropicSdk: 'Native Claude',
|
||||||
|
openWebui: 'Custom base',
|
||||||
|
langchain: 'LLM node',
|
||||||
|
llamaIndex: 'Model router',
|
||||||
|
zed: 'Assistant',
|
||||||
|
more: '60+ tools',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
faq: {
|
||||||
|
kicker: '// frequently asked',
|
||||||
|
title: 'You might be wondering',
|
||||||
|
noAnswer: "Can't find an answer? {contact} · We usually reply within 2 hours.",
|
||||||
|
contact: 'Email us ↗',
|
||||||
|
q1: 'How is PURO different from an API relay or proxy?',
|
||||||
|
a1: 'A relay just forwards official API requests — the price depends on how much balance you prepay. PURO is different: we let you turn your existing Claude Pro / ChatGPT Plus subscription into an API. The $20/month you\'re already paying no longer has to live in the official chat UI — it feeds Cursor, Claude Code, and any SDK through a unified API. We also offer a pay-per-use official API fallback pool, and you can mix both modes freely.',
|
||||||
|
q2: 'Will running API calls through my subscription get me banned?',
|
||||||
|
a2: 'We automatically pace requests per subscription and failover to other pool members if rate limits trigger. In practice, PURO\'s call pattern is less likely to flag risk controls than copy-pasting large conversations in the official client. When you bind multiple subscriptions, each account\'s RPM stays well within safe thresholds. All credentials are AES-256 encrypted, and requests never transit third-party infrastructure.',
|
||||||
|
q3: 'Do credits expire? Can I get a refund?',
|
||||||
|
a3Part1: 'Credits never expire.',
|
||||||
|
a3Part2: "You can save them up and use them months later. Full refund within 7 days of first top-up if no calls were made; after that, 85% of remaining credits are refunded. See our",
|
||||||
|
a3Link: 'refund policy',
|
||||||
|
a3Part3: '.',
|
||||||
|
q4: 'What payment methods are supported?',
|
||||||
|
a4: 'Domestic (CN): Alipay · WeChat Pay. International: Stripe credit card · USDT (TRC20 / ERC20) · PayPal. Enterprise top-ups support invoice and bank transfer with CNY receipts.',
|
||||||
|
q5: 'How many subscriptions can one PURO account bind?',
|
||||||
|
a5StarterLabel: 'Starter tier:',
|
||||||
|
a5Starter: 'Binding your own subscriptions is not supported',
|
||||||
|
a5ProLabel: 'Pro tier and above:',
|
||||||
|
a5Pro: 'Unlimited — you can bind 10 ChatGPT Plus + 3 Claude Pro accounts and schedule them all together',
|
||||||
|
a5EnterpriseLabel: 'Enterprise:',
|
||||||
|
a5Enterprise: 'Supports cross-team shared pools with org-level isolation',
|
||||||
|
q6: 'What happens if a subscription hits its rate limit?',
|
||||||
|
a6: "PURO's scheduler marks the throttled subscription as cooling and temporarily removes it from the pool. The same request is immediately failed over to another healthy subscription — callers typically experience no interruption. You can see each subscription's current status and remaining quota in the Dashboard.",
|
||||||
|
q7: 'How precise is billing? What if I go over my limit?',
|
||||||
|
a7: 'Billed per actual token count × model rate, accurate to 4 decimal places. Each API key can have an independent monthly budget cap — once hit, requests return 402 Payment Required and no further charges accumulate. The same 402 applies when your account balance is exhausted. Dashboard sends 80% / 95% reminder emails.',
|
||||||
|
q8: 'Will my data be used for training?',
|
||||||
|
a8Part1: 'No.',
|
||||||
|
a8Part2: 'All requests are used solely for routing — no content is stored or persisted (only metadata like model, token count, and latency is retained for billing and logs). Pro tier and above can optionally enable "zero-log mode" (planned), where we record nothing, not even request IDs.',
|
||||||
|
q9: 'Can I self-host PURO?',
|
||||||
|
a9: 'Enterprise tier supports Docker / K8s private deployment with separate control plane and data plane. Licensed as an annual subscription with upgrades and technical support included.',
|
||||||
|
a9Link: 'Contact us →',
|
||||||
|
q10: 'What models are supported? Will new models be added?',
|
||||||
|
a10: 'Currently covers Claude (Sonnet 4.5 / Opus 4 / Haiku 4.5), ChatGPT (GPT-5 / GPT-5 Codex / GPT-4.1), Gemini (2.5 Pro / 2.5 Flash). When official providers release new models, we typically go live within',
|
||||||
|
a10Link: 'docs',
|
||||||
|
a10Part2: '. Full model list available in the docs.',
|
||||||
|
},
|
||||||
|
finalCta: {
|
||||||
|
kicker: '// ready to start',
|
||||||
|
title: 'Get your first sk-puro-* key in 5 minutes',
|
||||||
|
subtitle: 'Connect your first subscription and you\'re ready.',
|
||||||
|
ctaPrimary: 'Sign up free →',
|
||||||
|
ctaDocs: 'View docs',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
docs: {
|
docs: {
|
||||||
nav: {
|
nav: {
|
||||||
products: 'Product',
|
products: 'Product',
|
||||||
|
|||||||
@@ -5826,6 +5826,7 @@ export default {
|
|||||||
landing: {
|
landing: {
|
||||||
nav: {
|
nav: {
|
||||||
products: '产品',
|
products: '产品',
|
||||||
|
pricing: '定价',
|
||||||
docs: '文档',
|
docs: '文档',
|
||||||
login: '登录',
|
login: '登录',
|
||||||
signup: '免费试用 →',
|
signup: '免费试用 →',
|
||||||
@@ -5905,6 +5906,209 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pricing: {
|
||||||
|
nav: {
|
||||||
|
products: '产品',
|
||||||
|
pricing: '定价',
|
||||||
|
docs: '文档',
|
||||||
|
login: '登录',
|
||||||
|
signup: '免费试用 →',
|
||||||
|
},
|
||||||
|
hero: {
|
||||||
|
kicker: '// pricing · 充多少 · 用多少 · 永不过期',
|
||||||
|
previewPill: '// preview · 最终定价以开售为准',
|
||||||
|
title1: '一次充值,',
|
||||||
|
titleAccent: '全平台',
|
||||||
|
title2: '通用',
|
||||||
|
sub: '同一份积分可以用在 Claude / ChatGPT / Gemini 任意池上。我们把你的订阅额度变成真正的 API 余额 —— 相比官方 API 便宜 {discount}。',
|
||||||
|
subDiscount: '至多 70%',
|
||||||
|
underline: '余额永不过期 · 支持支付宝 / 微信 / USDT · 无隐藏订阅费',
|
||||||
|
},
|
||||||
|
soonChip: 'SOON',
|
||||||
|
tiers: {
|
||||||
|
starter: {
|
||||||
|
flag: 'STARTER',
|
||||||
|
tierLabel: 'tier · 01',
|
||||||
|
headline: '先尝尝鲜,跑通接入',
|
||||||
|
credit: '充 ${creditAmount} → 得 {creditBonus} 积分',
|
||||||
|
creditAmount: '9.9',
|
||||||
|
creditBonus: '$12',
|
||||||
|
discountTag: '相当于官方 API · 0.5 折起',
|
||||||
|
cta: '充值 →',
|
||||||
|
features: {
|
||||||
|
allModels: '可用所有模型 / 所有池',
|
||||||
|
oneKey: '个 API Key',
|
||||||
|
rpm60: '60 RPM 速率限制',
|
||||||
|
log7: '基础日志(7 天保留)',
|
||||||
|
noBYOS: '自带订阅接入',
|
||||||
|
noTeam: '团队 / 多人协作',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pro: {
|
||||||
|
flag: '◆ 推荐',
|
||||||
|
tierLabel: 'tier · 02',
|
||||||
|
headline: '个人重度用户 · 最划算',
|
||||||
|
credit: '充 ${creditAmount} → 得 {creditBonus} 积分',
|
||||||
|
creditAmount: '29.9',
|
||||||
|
creditBonus: '$45',
|
||||||
|
discountTag: '相当于官方 API · 3-7 折',
|
||||||
|
cta: '立即充值 →',
|
||||||
|
features: {
|
||||||
|
allModels: '可用所有模型 / 所有池',
|
||||||
|
threeKeys: '个 API Key · 独立预算',
|
||||||
|
rpm120: '120 RPM 速率限制',
|
||||||
|
log30: '调用日志(30 天保留)',
|
||||||
|
byos: '自带订阅接入(无限个)',
|
||||||
|
failover: '多账号 failover 调度',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
flag: '⚡ 限时 +100%',
|
||||||
|
tierLabel: 'tier · 03',
|
||||||
|
headline: '小团队 / 长跑项目',
|
||||||
|
credit: '充 ${creditAmount} → 得 {creditBonus} 积分',
|
||||||
|
creditAmount: '99',
|
||||||
|
creditBonus: '$198',
|
||||||
|
discountTag: '相当于官方 API · 2-5 折',
|
||||||
|
cta: '充值 →',
|
||||||
|
features: {
|
||||||
|
proAll: '所有 Pro 能力',
|
||||||
|
tenKeys: '个 API Key · 独立预算',
|
||||||
|
rpm300: '300 RPM 速率限制',
|
||||||
|
log90: '调用日志(90 天保留)',
|
||||||
|
priorityCount: '',
|
||||||
|
priority: '请求优先级加权调度',
|
||||||
|
community: 'Slack / Discord 群组支持',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
flag: 'CUSTOM',
|
||||||
|
tierLabel: 'tier · 04',
|
||||||
|
headline: '自定义金额 · 按需充值',
|
||||||
|
creditPrefix: '得约',
|
||||||
|
bonusPrefix: '+',
|
||||||
|
discountTag: '根据金额阶梯自动匹配折扣',
|
||||||
|
cta: '定制充值 →',
|
||||||
|
features: {
|
||||||
|
neverExpire: '积分永不过期',
|
||||||
|
proAll: 'Pro 全部能力',
|
||||||
|
tiered: '阶梯 +21% ~ +100%',
|
||||||
|
payment: '支付宝 / 微信 / USDT',
|
||||||
|
preview: '拖动滑块预览赠送',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
enterprise: {
|
||||||
|
title: 'Enterprise · 企业定制',
|
||||||
|
desc: '专属订阅池、SLA、合规审计、私有化部署、发票结算。规模 >$500/月起可申请。',
|
||||||
|
cta: '联系商务 →',
|
||||||
|
},
|
||||||
|
binding: {
|
||||||
|
title: '已有订阅?直接接入',
|
||||||
|
desc: '有 Claude Max / ChatGPT Pro?免费注册后绑定,只为 PURO 路由费买单 —— 按次 {price}。',
|
||||||
|
price: '$0.0008/request',
|
||||||
|
cta: '接入我的订阅 →',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
calc: {
|
||||||
|
kicker: '// cost estimator',
|
||||||
|
previewPill: '// estimated · 以实际计费为准',
|
||||||
|
title: '算算你能省多少?',
|
||||||
|
sub: '按你的使用场景,对比 PURO 和官方 API 的月度花费差。数字会根据你选的场景自动更新。',
|
||||||
|
reqLabel: '日均请求数',
|
||||||
|
tokLabel: '平均每请求 tokens',
|
||||||
|
mixLabel: 'Claude 占比',
|
||||||
|
monthlyTok: '月度 tokens 消耗',
|
||||||
|
officialCost: '官方 API 价格',
|
||||||
|
puroCost: 'PURO 价格(含 +50% 赠送)',
|
||||||
|
savings: '节省',
|
||||||
|
recLabel: '建议充值',
|
||||||
|
recStarter: '≈ Starter 档够用',
|
||||||
|
recPro: '≈ Pro 档 1 个月',
|
||||||
|
recScale: '≈ Scale 档 · 1 个月',
|
||||||
|
},
|
||||||
|
works: {
|
||||||
|
kicker: '// works everywhere',
|
||||||
|
title: '一个 key,所有工具通用',
|
||||||
|
sub: '只要支持自定义 {baseUrl} 或 OpenAI / Anthropic API,都能直接接入 PURO。',
|
||||||
|
baseUrl: 'base_url',
|
||||||
|
tools: {
|
||||||
|
claudeCode: 'Claude Code',
|
||||||
|
cursor: 'Cursor',
|
||||||
|
cline: 'Cline',
|
||||||
|
rooCode: 'Roo Code',
|
||||||
|
continueTag: 'Continue',
|
||||||
|
openaiSdk: 'OpenAI SDK',
|
||||||
|
anthropicSdk: 'Anthropic SDK',
|
||||||
|
openWebui: 'Open WebUI',
|
||||||
|
langchain: 'LangChain',
|
||||||
|
llamaIndex: 'LlamaIndex',
|
||||||
|
zed: 'Zed',
|
||||||
|
more: '更多…',
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
claudeCode: 'ANTHROPIC_BASE_URL',
|
||||||
|
cursor: '自定义模型',
|
||||||
|
cline: 'OpenAI 兼容',
|
||||||
|
rooCode: 'OpenAI 兼容',
|
||||||
|
continueTag: 'config.yaml',
|
||||||
|
openaiSdk: 'Python / Node',
|
||||||
|
anthropicSdk: '原生 Claude',
|
||||||
|
openWebui: '自定义 base',
|
||||||
|
langchain: 'LLM 节点',
|
||||||
|
llamaIndex: '模型路由',
|
||||||
|
zed: 'Assistant',
|
||||||
|
more: '60+ 工具',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
faq: {
|
||||||
|
kicker: '// frequently asked',
|
||||||
|
title: '你可能想问的',
|
||||||
|
noAnswer: '没找到答案?{contact} · 通常 2 小时内回复。',
|
||||||
|
contact: '发邮件给我们 ↗',
|
||||||
|
q1: 'PURO 和 API 中转站 / API 代理有什么不同?',
|
||||||
|
a1: '中转站只是把官方 API 请求转一手,价格取决于你预付多少 balance。PURO 的不同是 —— 我们让你把已有的 Claude Pro / ChatGPT Plus 订阅变成 API。你原本就在付的 $20/月,不再只能在官网聊天里用,而是通过统一 API 喂给 Cursor、Claude Code、任何 SDK。同时我们也提供按量充值的官方 API 备用池,两种模式可以混用。',
|
||||||
|
q2: '用订阅跑 API 会不会被封号?',
|
||||||
|
a2: '我们会自动控制每个订阅的请求节奏,并在触发限流时把请求 failover 到池子里的其他订阅。实际上 PURO 的调用模式比你在官方客户端直接复制粘贴大段对话更不容易触发风控。你绑定多个订阅时,单个账号的 RPM 会被压到足够安全的阈值内。另外所有凭证用 AES-256 加密存储,请求链路不经过第三方。',
|
||||||
|
q3: '积分会过期吗?可以退款吗?',
|
||||||
|
a3Part1: '积分永不过期。',
|
||||||
|
a3Part2: '你可以攒着慢慢用 —— 包括几个月都不用。首次充值 7 天内未产生任何调用可全额退款,之后按剩余积分 85% 比例退。详见',
|
||||||
|
a3Link: '退款政策',
|
||||||
|
a3Part3: '。',
|
||||||
|
q4: '支持哪些支付方式?',
|
||||||
|
a4: '国内:支付宝 · 微信支付。国际:Stripe 信用卡 · USDT (TRC20 / ERC20) · PayPal。企业充值支持 Invoice 对公打款,人民币开票。',
|
||||||
|
q5: '一个 PURO 账号可以绑定多少个订阅?',
|
||||||
|
a5StarterLabel: 'Starter 档:',
|
||||||
|
a5Starter: '不支持绑定自带订阅',
|
||||||
|
a5ProLabel: 'Pro 档及以上:',
|
||||||
|
a5Pro: '无限制,你可以把 10 个 ChatGPT Plus + 3 个 Claude Pro 一起绑上去,统一调度',
|
||||||
|
a5EnterpriseLabel: 'Enterprise:',
|
||||||
|
a5Enterprise: '支持跨团队共享池,按组织维度隔离',
|
||||||
|
q6: '如果某个订阅触发限流了会怎样?',
|
||||||
|
a6: 'PURO 的调度器会把受限的订阅自动标记为 cooling 状态,暂时从池子里摘除。同一请求会立刻被 failover 到池内其他健康订阅上 —— 调用方通常感受不到中断。你可以在 Dashboard 看到每个订阅的当前状态和剩余配额。',
|
||||||
|
q7: '计费精度?超量会怎么办?',
|
||||||
|
a7: '按实际 token 数 + 模型单价计费,精度到 4 位小数。每个 API Key 可设置独立月度预算,达到后返回 402 Payment Required,不会继续扣费。账户总余额不足时同样会返回 402,且 Dashboard 有 80% / 95% 两级提醒邮件。',
|
||||||
|
q8: '数据会被用于训练吗?',
|
||||||
|
a8Part1: '不会。',
|
||||||
|
a8Part2: '所有请求仅用于路由转发,不入库、不留存内容(仅保留元数据如模型、token 数、延迟,用于计费和日志)。Pro 档及以上可选开启"零日志模式"(规划中),我们连请求 ID 都不记录。',
|
||||||
|
q9: '可以私有化部署吗?',
|
||||||
|
a9: 'Enterprise 档支持 Docker / K8s 私有化部署,控制面和数据面可以分开。授权按年订阅,包含升级和技术支持。',
|
||||||
|
a9Link: '联系商务 →',
|
||||||
|
q10: '支持哪些模型?会跟进新模型吗?',
|
||||||
|
a10: '当前覆盖 Claude(Sonnet 4.5 / Opus 4 / Haiku 4.5)、ChatGPT(GPT-5 / GPT-5 Codex / GPT-4.1)、Gemini(2.5 Pro / 2.5 Flash)。每当官方发布新模型,我们通常在 24 小时内上线。完整模型列表见',
|
||||||
|
a10Link: '文档',
|
||||||
|
a10Part2: '。',
|
||||||
|
},
|
||||||
|
finalCta: {
|
||||||
|
kicker: '// ready to start',
|
||||||
|
title: '5 分钟,拿到你第一个 sk-puro-* key',
|
||||||
|
subtitle: '绑定你的第一个订阅即可开始。',
|
||||||
|
ctaPrimary: '免费注册 →',
|
||||||
|
ctaDocs: '查看文档',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
docs: {
|
docs: {
|
||||||
nav: {
|
nav: {
|
||||||
products: '产品',
|
products: '产品',
|
||||||
|
|||||||
@@ -129,6 +129,12 @@ const routes: RouteRecordRaw[] = [
|
|||||||
title: 'PURO AI · 文档'
|
title: 'PURO AI · 文档'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/pricing',
|
||||||
|
name: 'pricing',
|
||||||
|
component: () => import('@/views/pricing/PricingView.vue'),
|
||||||
|
meta: { requiresAuth: false, title: 'Pricing · PURO AI' }
|
||||||
|
},
|
||||||
|
|
||||||
// ==================== User Routes ====================
|
// ==================== User Routes ====================
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
<div class="nav-links">
|
<div class="nav-links">
|
||||||
<a href="#features">{{ $t('landing.nav.products') }}</a>
|
<a href="#features">{{ $t('landing.nav.products') }}</a>
|
||||||
<a href="/docs">{{ $t('landing.nav.docs') }}</a>
|
<router-link to="/pricing">{{ $t('landing.nav.pricing') }}</router-link>
|
||||||
|
<router-link to="/docs">{{ $t('landing.nav.docs') }}</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-cta">
|
<div class="nav-cta">
|
||||||
<PuroLocaleSwitcher />
|
<PuroLocaleSwitcher />
|
||||||
|
|||||||
513
frontend/src/views/pricing/PricingView.vue
Normal file
513
frontend/src/views/pricing/PricingView.vue
Normal file
@@ -0,0 +1,513 @@
|
|||||||
|
<template>
|
||||||
|
<div class="puro-page">
|
||||||
|
<div class="bg-glow"></div>
|
||||||
|
<div class="grain"></div>
|
||||||
|
|
||||||
|
<!-- NAV -->
|
||||||
|
<nav class="nav">
|
||||||
|
<div class="container nav-inner">
|
||||||
|
<router-link to="/" class="brand">
|
||||||
|
<svg class="hex" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M12 2L21 7V17L12 22L3 17V7L12 2Z" stroke="currentColor" stroke-width="1.8" fill="rgba(34, 211, 238, 0.08)"/>
|
||||||
|
<path d="M12 7L17 9.5V14.5L12 17L7 14.5V9.5L12 7Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
PURO AI
|
||||||
|
</router-link>
|
||||||
|
<div class="nav-links">
|
||||||
|
<router-link to="/">{{ $t('pricing.nav.products') }}</router-link>
|
||||||
|
<a href="#" class="active">{{ $t('pricing.nav.pricing') }}</a>
|
||||||
|
<router-link to="/docs">{{ $t('pricing.nav.docs') }}</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="nav-cta">
|
||||||
|
<PuroLocaleSwitcher />
|
||||||
|
<router-link to="/login" class="btn btn-ghost">{{ $t('pricing.nav.login') }}</router-link>
|
||||||
|
<router-link to="/register" class="btn btn-primary">{{ $t('pricing.nav.signup') }}</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- HERO -->
|
||||||
|
<section class="hero">
|
||||||
|
<div class="section-kicker" style="margin-bottom:14px;">{{ $t('pricing.hero.kicker') }}</div>
|
||||||
|
<div class="preview-pill">{{ $t('pricing.hero.previewPill') }}</div>
|
||||||
|
<h1>{{ $t('pricing.hero.title1') }}<span class="accent">{{ $t('pricing.hero.titleAccent') }}</span>{{ $t('pricing.hero.title2') }}</h1>
|
||||||
|
<p class="sub">
|
||||||
|
<i18n-t keypath="pricing.hero.sub" tag="span">
|
||||||
|
<template #discount><b class="text-cyan">{{ $t('pricing.hero.subDiscount') }}</b></template>
|
||||||
|
</i18n-t>
|
||||||
|
</p>
|
||||||
|
<div class="underline">
|
||||||
|
<span class="dot"></span>
|
||||||
|
{{ $t('pricing.hero.underline') }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- PRICING GRID -->
|
||||||
|
<div class="pricing-wrap">
|
||||||
|
<div class="pricing-grid">
|
||||||
|
|
||||||
|
<!-- STARTER -->
|
||||||
|
<div class="tier">
|
||||||
|
<span class="flag muted">{{ $t('pricing.tiers.starter.flag') }}</span>
|
||||||
|
<div class="tier-name">{{ $t('pricing.tiers.starter.tierLabel') }}</div>
|
||||||
|
<div class="tier-headline">{{ $t('pricing.tiers.starter.headline') }}</div>
|
||||||
|
<div class="price-row"><span class="price"><span class="curr">$</span>{{ $t('pricing.tiers.starter.creditAmount') }}</span></div>
|
||||||
|
<div class="credit-line">
|
||||||
|
<i18n-t keypath="pricing.tiers.starter.credit" tag="span">
|
||||||
|
<template #creditAmount>{{ $t('pricing.tiers.starter.creditAmount') }}</template>
|
||||||
|
<template #creditBonus><b>{{ $t('pricing.tiers.starter.creditBonus') }}</b> <span class="bonus">+21%</span></template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
<span class="discount-tag">{{ $t('pricing.tiers.starter.discountTag') }}</span>
|
||||||
|
<hr/>
|
||||||
|
<div class="feats">
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.starter.features.allModels') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span><b>1</b> {{ $t('pricing.tiers.starter.features.oneKey') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.starter.features.rpm60') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.starter.features.log7') }}</div>
|
||||||
|
<div class="feat muted"><span class="tick">✗</span>{{ $t('pricing.tiers.starter.features.noBYOS') }}</div>
|
||||||
|
<div class="feat muted"><span class="tick">✗</span>{{ $t('pricing.tiers.starter.features.noTeam') }}</div>
|
||||||
|
</div>
|
||||||
|
<router-link to="/register" class="btn btn-ghost btn-lg tier-cta">{{ $t('pricing.tiers.starter.cta') }}</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PRO -->
|
||||||
|
<div class="tier popular">
|
||||||
|
<span class="flag">{{ $t('pricing.tiers.pro.flag') }}</span>
|
||||||
|
<div class="tier-name">{{ $t('pricing.tiers.pro.tierLabel') }}</div>
|
||||||
|
<div class="tier-headline">{{ $t('pricing.tiers.pro.headline') }}</div>
|
||||||
|
<div class="price-row"><span class="price"><span class="curr">$</span>{{ $t('pricing.tiers.pro.creditAmount') }}</span></div>
|
||||||
|
<div class="credit-line">
|
||||||
|
<i18n-t keypath="pricing.tiers.pro.credit" tag="span">
|
||||||
|
<template #creditAmount>{{ $t('pricing.tiers.pro.creditAmount') }}</template>
|
||||||
|
<template #creditBonus><b>{{ $t('pricing.tiers.pro.creditBonus') }}</b> <span class="bonus">+50%</span></template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
<span class="discount-tag">{{ $t('pricing.tiers.pro.discountTag') }}</span>
|
||||||
|
<hr/>
|
||||||
|
<div class="feats">
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.pro.features.allModels') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span><b>3</b> {{ $t('pricing.tiers.pro.features.threeKeys') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.pro.features.rpm120') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.pro.features.log30') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.pro.features.byos') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.pro.features.failover') }}</div>
|
||||||
|
</div>
|
||||||
|
<router-link to="/register" class="btn btn-primary btn-lg tier-cta">{{ $t('pricing.tiers.pro.cta') }}</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SCALE -->
|
||||||
|
<div class="tier">
|
||||||
|
<span class="flag amber">{{ $t('pricing.tiers.scale.flag') }}</span>
|
||||||
|
<div class="tier-name">{{ $t('pricing.tiers.scale.tierLabel') }}</div>
|
||||||
|
<div class="tier-headline">{{ $t('pricing.tiers.scale.headline') }}</div>
|
||||||
|
<div class="price-row"><span class="price"><span class="curr">$</span>{{ $t('pricing.tiers.scale.creditAmount') }}</span></div>
|
||||||
|
<div class="credit-line">
|
||||||
|
<i18n-t keypath="pricing.tiers.scale.credit" tag="span">
|
||||||
|
<template #creditAmount>{{ $t('pricing.tiers.scale.creditAmount') }}</template>
|
||||||
|
<template #creditBonus><b>{{ $t('pricing.tiers.scale.creditBonus') }}</b> <span class="bonus">+100%</span></template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
<span class="discount-tag">{{ $t('pricing.tiers.scale.discountTag') }}</span>
|
||||||
|
<hr/>
|
||||||
|
<div class="feats">
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.scale.features.proAll') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span><b>10</b> {{ $t('pricing.tiers.scale.features.tenKeys') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.scale.features.rpm300') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.scale.features.log90') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.scale.features.priority') }}<span class="soon-chip">{{ $t('pricing.soonChip') }}</span></div>
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.scale.features.community') }}</div>
|
||||||
|
</div>
|
||||||
|
<router-link to="/register" class="btn btn-ghost btn-lg tier-cta">{{ $t('pricing.tiers.scale.cta') }}</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CUSTOM -->
|
||||||
|
<div class="tier">
|
||||||
|
<span class="flag muted">{{ $t('pricing.tiers.custom.flag') }}</span>
|
||||||
|
<div class="tier-name">{{ $t('pricing.tiers.custom.tierLabel') }}</div>
|
||||||
|
<div class="tier-headline">{{ $t('pricing.tiers.custom.headline') }}</div>
|
||||||
|
<div class="price-row"><span class="price"><span class="curr">$</span>{{ customAmt }}</span></div>
|
||||||
|
<div class="credit-line">{{ $t('pricing.tiers.custom.creditPrefix') }} <b>${{ customCredit }}</b> <span class="bonus">{{ $t('pricing.tiers.custom.bonusPrefix') }}{{ customBonus }}%</span></div>
|
||||||
|
<input type="range" min="10" max="500" value="50" step="10" v-model.number="customAmt" style="-webkit-appearance:none; width:100%; height:4px; background:var(--border); border-radius:2px; margin-bottom:12px;">
|
||||||
|
<span class="discount-tag">{{ $t('pricing.tiers.custom.discountTag') }}</span>
|
||||||
|
<hr/>
|
||||||
|
<div class="feats">
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.custom.features.neverExpire') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.custom.features.proAll') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.custom.features.tiered') }}</div>
|
||||||
|
<div class="feat"><span class="tick">✓</span>{{ $t('pricing.tiers.custom.features.payment') }}</div>
|
||||||
|
<div class="feat muted"><span class="tick">—</span>{{ $t('pricing.tiers.custom.features.preview') }}</div>
|
||||||
|
</div>
|
||||||
|
<router-link to="/register" class="btn btn-ghost btn-lg tier-cta">{{ $t('pricing.tiers.custom.cta') }}</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CUSTOM ROW -->
|
||||||
|
<div class="custom-row">
|
||||||
|
<div class="custom-card">
|
||||||
|
<div class="icon purple">
|
||||||
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M2 20h20"/>
|
||||||
|
<path d="M4 20V10l8-6 8 6v10"/>
|
||||||
|
<path d="M9 20v-7h6v7"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div style="flex:1;">
|
||||||
|
<h3>{{ $t('pricing.custom.enterprise.title') }}</h3>
|
||||||
|
<p>{{ $t('pricing.custom.enterprise.desc') }}</p>
|
||||||
|
</div>
|
||||||
|
<a href="mailto:contact@puro.im" class="btn btn-ghost">{{ $t('pricing.custom.enterprise.cta') }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="custom-card">
|
||||||
|
<div class="icon">
|
||||||
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="3" y="3" width="7" height="7"/>
|
||||||
|
<rect x="14" y="3" width="7" height="7"/>
|
||||||
|
<rect x="14" y="14" width="7" height="7"/>
|
||||||
|
<rect x="3" y="14" width="7" height="7"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div style="flex:1;">
|
||||||
|
<h3>{{ $t('pricing.custom.binding.title') }}</h3>
|
||||||
|
<p>
|
||||||
|
<i18n-t keypath="pricing.custom.binding.desc" tag="span">
|
||||||
|
<template #price><code class="pill">{{ $t('pricing.custom.binding.price') }}</code></template>
|
||||||
|
</i18n-t>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<router-link to="/register" class="btn btn-ghost">{{ $t('pricing.custom.binding.cta') }}</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CALCULATOR -->
|
||||||
|
<section class="calc-section">
|
||||||
|
<div class="calc-preview-pill">{{ $t('pricing.calc.previewPill') }}</div>
|
||||||
|
<PricingCalculator />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- WORKS EVERYWHERE -->
|
||||||
|
<section class="works">
|
||||||
|
<div class="section-head">
|
||||||
|
<div class="kicker">{{ $t('pricing.works.kicker') }}</div>
|
||||||
|
<h2>{{ $t('pricing.works.title') }}</h2>
|
||||||
|
<p>
|
||||||
|
<i18n-t keypath="pricing.works.sub" tag="span">
|
||||||
|
<template #baseUrl><code class="pill">{{ $t('pricing.works.baseUrl') }}</code></template>
|
||||||
|
</i18n-t>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="tools-grid">
|
||||||
|
<div class="tool">
|
||||||
|
<div class="logo"><svg width="22" height="22" viewBox="0 0 24 24" fill="#d97757"><path d="M4.5 19L12 4l7.5 15H16l-4-8.5L8 19H4.5z"/></svg></div>
|
||||||
|
<div class="name">{{ $t('pricing.works.tools.claudeCode') }}</div>
|
||||||
|
<div class="tag">{{ $t('pricing.works.tags.claudeCode') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="tool">
|
||||||
|
<div class="logo"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 18L18 6M8 6h10v10"/></svg></div>
|
||||||
|
<div class="name">{{ $t('pricing.works.tools.cursor') }}</div>
|
||||||
|
<div class="tag">{{ $t('pricing.works.tags.cursor') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="tool">
|
||||||
|
<div class="logo"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg></div>
|
||||||
|
<div class="name">{{ $t('pricing.works.tools.cline') }}</div>
|
||||||
|
<div class="tag">{{ $t('pricing.works.tags.cline') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="tool">
|
||||||
|
<div class="logo"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="8"/><path d="m8 12 2 2 6-6"/></svg></div>
|
||||||
|
<div class="name">{{ $t('pricing.works.tools.rooCode') }}</div>
|
||||||
|
<div class="tag">{{ $t('pricing.works.tags.rooCode') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="tool">
|
||||||
|
<div class="logo"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M3 12h4l3-8 4 16 3-8h4"/></svg></div>
|
||||||
|
<div class="name">{{ $t('pricing.works.tools.continueTag') }}</div>
|
||||||
|
<div class="tag">{{ $t('pricing.works.tags.continueTag') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="tool">
|
||||||
|
<div class="logo"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M12 3v18M3 12h18"/></svg></div>
|
||||||
|
<div class="name">{{ $t('pricing.works.tools.openaiSdk') }}</div>
|
||||||
|
<div class="tag">{{ $t('pricing.works.tags.openaiSdk') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="tool">
|
||||||
|
<div class="logo"><svg width="22" height="22" viewBox="0 0 24 24" fill="#d97757"><path d="M4.5 19L12 4l7.5 15H16l-4-8.5L8 19H4.5z"/></svg></div>
|
||||||
|
<div class="name">{{ $t('pricing.works.tools.anthropicSdk') }}</div>
|
||||||
|
<div class="tag">{{ $t('pricing.works.tags.anthropicSdk') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="tool">
|
||||||
|
<div class="logo"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 10h18M8 5v14"/></svg></div>
|
||||||
|
<div class="name">{{ $t('pricing.works.tools.openWebui') }}</div>
|
||||||
|
<div class="tag">{{ $t('pricing.works.tags.openWebui') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="tool">
|
||||||
|
<div class="logo"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 2L2 7v10l10 5 10-5V7z"/><path d="M12 22V12M2 7l10 5 10-5"/></svg></div>
|
||||||
|
<div class="name">{{ $t('pricing.works.tools.langchain') }}</div>
|
||||||
|
<div class="tag">{{ $t('pricing.works.tags.langchain') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="tool">
|
||||||
|
<div class="logo"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="6" cy="6" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="12" cy="18" r="3"/><path d="M6 9v3l6 3M18 9v3l-6 3"/></svg></div>
|
||||||
|
<div class="name">{{ $t('pricing.works.tools.llamaIndex') }}</div>
|
||||||
|
<div class="tag">{{ $t('pricing.works.tags.llamaIndex') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="tool">
|
||||||
|
<div class="logo"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 9h6v6H9z"/></svg></div>
|
||||||
|
<div class="name">{{ $t('pricing.works.tools.zed') }}</div>
|
||||||
|
<div class="tag">{{ $t('pricing.works.tags.zed') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="tool">
|
||||||
|
<div class="logo"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 4h6v6H4zM14 4h6v6h-6zM4 14h6v6H4zM14 14h6v6h-6z"/></svg></div>
|
||||||
|
<div class="name">{{ $t('pricing.works.tools.more') }}</div>
|
||||||
|
<div class="tag">{{ $t('pricing.works.tags.more') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- FAQ -->
|
||||||
|
<section class="faq-section">
|
||||||
|
<div class="section-head">
|
||||||
|
<div class="kicker">{{ $t('pricing.faq.kicker') }}</div>
|
||||||
|
<h2>{{ $t('pricing.faq.title') }}</h2>
|
||||||
|
<p>
|
||||||
|
<i18n-t keypath="pricing.faq.noAnswer" tag="span">
|
||||||
|
<template #contact><a href="mailto:contact@puro.im" style="color:var(--cyan)">{{ $t('pricing.faq.contact') }}</a></template>
|
||||||
|
</i18n-t>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="faq" open>
|
||||||
|
<summary><span class="num">01</span>{{ $t('pricing.faq.q1') }}</summary>
|
||||||
|
<div class="answer">{{ $t('pricing.faq.a1') }}</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="faq">
|
||||||
|
<summary><span class="num">02</span>{{ $t('pricing.faq.q2') }}</summary>
|
||||||
|
<div class="answer">
|
||||||
|
{{ $t('pricing.faq.a2') }}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="faq">
|
||||||
|
<summary><span class="num">03</span>{{ $t('pricing.faq.q3') }}</summary>
|
||||||
|
<div class="answer">
|
||||||
|
<b>{{ $t('pricing.faq.a3Part1') }}</b>
|
||||||
|
{{ $t('pricing.faq.a3Part2') }} <a href="#">{{ $t('pricing.faq.a3Link') }}</a>{{ $t('pricing.faq.a3Part3') }}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="faq">
|
||||||
|
<summary><span class="num">04</span>{{ $t('pricing.faq.q4') }}</summary>
|
||||||
|
<div class="answer">{{ $t('pricing.faq.a4') }}</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="faq">
|
||||||
|
<summary><span class="num">05</span>{{ $t('pricing.faq.q5') }}</summary>
|
||||||
|
<div class="answer">
|
||||||
|
<ul>
|
||||||
|
<li><b>{{ $t('pricing.faq.a5StarterLabel') }}</b> {{ $t('pricing.faq.a5Starter') }}</li>
|
||||||
|
<li><b>{{ $t('pricing.faq.a5ProLabel') }}</b> {{ $t('pricing.faq.a5Pro') }}</li>
|
||||||
|
<li><b>{{ $t('pricing.faq.a5EnterpriseLabel') }}</b> {{ $t('pricing.faq.a5Enterprise') }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="faq">
|
||||||
|
<summary><span class="num">06</span>{{ $t('pricing.faq.q6') }}</summary>
|
||||||
|
<div class="answer">
|
||||||
|
{{ $t('pricing.faq.a6') }}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="faq">
|
||||||
|
<summary><span class="num">07</span>{{ $t('pricing.faq.q7') }}</summary>
|
||||||
|
<div class="answer">{{ $t('pricing.faq.a7') }}</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="faq">
|
||||||
|
<summary><span class="num">08</span>{{ $t('pricing.faq.q8') }}</summary>
|
||||||
|
<div class="answer">
|
||||||
|
<b>{{ $t('pricing.faq.a8Part1') }}</b> {{ $t('pricing.faq.a8Part2') }}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="faq">
|
||||||
|
<summary><span class="num">09</span>{{ $t('pricing.faq.q9') }}</summary>
|
||||||
|
<div class="answer">
|
||||||
|
{{ $t('pricing.faq.a9') }} <a href="mailto:contact@puro.im">{{ $t('pricing.faq.a9Link') }}</a>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="faq">
|
||||||
|
<summary><span class="num">10</span>{{ $t('pricing.faq.q10') }}</summary>
|
||||||
|
<div class="answer">
|
||||||
|
{{ $t('pricing.faq.a10') }} 24 {{ $t('pricing.faq.a10Part2') }} <a href="/docs">{{ $t('pricing.faq.a10Link') }}</a>.
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- FINAL CTA -->
|
||||||
|
<section class="final-cta">
|
||||||
|
<div class="final-cta-inner">
|
||||||
|
<div class="section-kicker" style="margin-bottom:12px;">{{ $t('pricing.finalCta.kicker') }}</div>
|
||||||
|
<h2>{{ $t('pricing.finalCta.title') }}</h2>
|
||||||
|
<p>{{ $t('pricing.finalCta.subtitle') }}</p>
|
||||||
|
<div style="display:inline-flex; gap:12px;">
|
||||||
|
<router-link to="/register" class="btn btn-primary btn-lg">{{ $t('pricing.finalCta.ctaPrimary') }}</router-link>
|
||||||
|
<router-link to="/docs" class="btn btn-ghost btn-lg">{{ $t('pricing.finalCta.ctaDocs') }}</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import PuroLocaleSwitcher from '@/components/puro/PuroLocaleSwitcher.vue'
|
||||||
|
import PricingCalculator from '@/components/puro/PricingCalculator.vue'
|
||||||
|
|
||||||
|
const customAmt = ref(50)
|
||||||
|
|
||||||
|
const customBonus = computed(() => {
|
||||||
|
const v = customAmt.value
|
||||||
|
if (v >= 500) return 120
|
||||||
|
if (v >= 200) return 110
|
||||||
|
if (v >= 99) return 100
|
||||||
|
if (v >= 50) return 70
|
||||||
|
if (v >= 30) return 50
|
||||||
|
if (v >= 20) return 35
|
||||||
|
return 21
|
||||||
|
})
|
||||||
|
const customCredit = computed(() => Math.round(customAmt.value * (1 + customBonus.value / 100)))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.hero { max-width: 1180px; margin: 0 auto; padding: 80px 32px 40px; text-align: center; }
|
||||||
|
.hero h1 { font-size: 54px; font-weight: 800; letter-spacing: -0.03em; margin-bottom: 18px; }
|
||||||
|
.hero h1 .accent { color: var(--cyan); }
|
||||||
|
.hero .sub { color: var(--text-2); font-size: 17px; max-width: 620px; margin: 0 auto 14px; line-height: 1.6; }
|
||||||
|
.hero .underline { display: inline-flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: 12px; color: var(--text-3); padding: 6px 14px; background: rgba(2, 6, 23, 0.5); border: 1px solid var(--border); border-radius: 100px; }
|
||||||
|
.hero .underline .dot { width: 6px; height: 6px; background: var(--green); border-radius: 50%; box-shadow: 0 0 0 3px rgba(52,211,153,0.15); }
|
||||||
|
|
||||||
|
.pricing-wrap { max-width: 1180px; margin: 0 auto; padding: 20px 32px 40px; }
|
||||||
|
.pricing-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }
|
||||||
|
.tier { position: relative; border: 1px solid var(--border); border-radius: var(--r-xl); background: rgba(15, 23, 42, 0.5); padding: 28px 24px; display: flex; flex-direction: column; transition: all .2s; }
|
||||||
|
.tier:hover { border-color: var(--border-2); transform: translateY(-3px); }
|
||||||
|
.tier.popular { border-color: rgba(34, 211, 238, 0.4); background: radial-gradient(500px 300px at 50% 0%, rgba(34,211,238,0.08), transparent 60%), rgba(15, 23, 42, 0.7); box-shadow: 0 0 0 1px rgba(34,211,238,0.15), 0 20px 40px -20px rgba(34,211,238,0.2); transform: translateY(-6px); }
|
||||||
|
.tier.popular:hover { transform: translateY(-9px); }
|
||||||
|
.tier .flag { position: absolute; top: -11px; left: 50%; transform: translateX(-50%); font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.14em; padding: 4px 12px; border-radius: 100px; background: var(--cyan); color: #042f2e; font-weight: 700; white-space: nowrap; }
|
||||||
|
.tier .flag.amber { background: var(--amber); color: #422006; }
|
||||||
|
.tier .flag.muted { background: rgba(100, 116, 139, 0.2); color: var(--text-2); border: 1px solid var(--border); }
|
||||||
|
|
||||||
|
.tier-name { font-size: 13px; font-family: var(--font-mono); letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-3); margin-bottom: 8px; }
|
||||||
|
.tier-headline { font-size: 16px; font-weight: 600; color: var(--text-0); margin-bottom: 22px; line-height: 1.35; min-height: 44px; }
|
||||||
|
|
||||||
|
.price-row { display: flex; align-items: baseline; gap: 4px; margin-bottom: 4px; }
|
||||||
|
.price { font-family: var(--font-mono); font-size: 42px; font-weight: 800; letter-spacing: -0.03em; color: var(--text-0); }
|
||||||
|
.tier.popular .price { color: var(--cyan); }
|
||||||
|
.price .curr { font-size: 18px; font-weight: 600; color: var(--text-3); margin-right: 2px; vertical-align: super; }
|
||||||
|
.credit-line { font-family: var(--font-mono); font-size: 12px; color: var(--cyan); margin-bottom: 14px; }
|
||||||
|
.credit-line .arrow { margin: 0 6px; color: var(--text-3); }
|
||||||
|
.credit-line .bonus { padding: 2px 8px; background: rgba(34,211,238,0.08); border: 1px solid rgba(34,211,238,0.25); border-radius: 4px; font-weight: 600; margin-left: 6px; }
|
||||||
|
.discount-tag { display: inline-block; font-family: var(--font-mono); font-size: 11px; color: var(--amber); background: rgba(251,191,36,0.08); border: 1px solid rgba(251,191,36,0.25); border-radius: 4px; padding: 3px 8px; margin-bottom: 18px; }
|
||||||
|
|
||||||
|
.tier hr { border: none; border-top: 1px dashed var(--border); margin: 4px 0 18px; }
|
||||||
|
|
||||||
|
.feat { display: flex; gap: 10px; align-items: flex-start; font-size: 13px; color: var(--text-1); padding: 4px 0; line-height: 1.55; }
|
||||||
|
.feat .tick { color: var(--cyan); flex-shrink: 0; margin-top: 2px; }
|
||||||
|
.feat.muted { color: var(--text-3); }
|
||||||
|
.feat.muted .tick { color: var(--text-3); }
|
||||||
|
.feat b { color: var(--text-0); font-weight: 600; }
|
||||||
|
.feats { display: flex; flex-direction: column; gap: 2px; margin-bottom: 24px; flex: 1; }
|
||||||
|
.tier-cta { width: 100%; justify-content: center; }
|
||||||
|
|
||||||
|
.custom-row { margin-top: 12px; display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
|
||||||
|
.custom-card { padding: 24px; border: 1px solid var(--border); border-radius: var(--r-xl); background: linear-gradient(135deg, rgba(168,85,247,0.05), transparent 50%), rgba(15, 23, 42, 0.4); display: flex; align-items: center; gap: 22px; }
|
||||||
|
.custom-card .icon { width: 48px; height: 48px; border-radius: 10px; background: rgba(34,211,238,0.1); color: var(--cyan); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||||
|
.custom-card .icon.purple { background: rgba(168,85,247,0.1); color: var(--purple); }
|
||||||
|
.custom-card h3 { font-size: 16px; font-weight: 600; margin-bottom: 4px; }
|
||||||
|
.custom-card p { font-size: 13px; color: var(--text-2); line-height: 1.5; }
|
||||||
|
.custom-card .btn { margin-left: auto; flex-shrink: 0; }
|
||||||
|
|
||||||
|
.works { max-width: 1180px; margin: 0 auto; padding: 80px 32px 40px; }
|
||||||
|
.section-head { text-align: center; margin-bottom: 32px; }
|
||||||
|
.section-head .kicker { font-family: var(--font-mono); font-size: 12px; color: var(--cyan); letter-spacing: 0.14em; margin-bottom: 10px; }
|
||||||
|
.section-head h2 { font-size: 32px; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 8px; }
|
||||||
|
.section-head p { color: var(--text-2); font-size: 15px; max-width: 560px; margin: 0 auto; line-height: 1.55; }
|
||||||
|
.tools-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; }
|
||||||
|
.tool { padding: 18px 14px; border: 1px solid var(--border); border-radius: var(--r-md); background: rgba(15, 23, 42, 0.4); text-align: center; transition: all .15s; }
|
||||||
|
.tool:hover { border-color: var(--border-2); background: rgba(15, 23, 42, 0.7); }
|
||||||
|
.tool .logo { width: 28px; height: 28px; margin: 0 auto 10px; display: flex; align-items: center; justify-content: center; color: var(--text-1); }
|
||||||
|
.tool .name { font-size: 12px; font-weight: 500; color: var(--text-1); }
|
||||||
|
.tool .tag { font-size: 10px; color: var(--text-3); margin-top: 2px; font-family: var(--font-mono); }
|
||||||
|
|
||||||
|
.calc-section { max-width: 1180px; margin: 0 auto; padding: 40px 32px; }
|
||||||
|
.faq-section { max-width: 880px; margin: 0 auto; padding: 60px 32px 100px; }
|
||||||
|
.faq { border: 1px solid var(--border); border-radius: var(--r-md); background: rgba(15, 23, 42, 0.4); margin-bottom: 8px; overflow: hidden; transition: all .15s; }
|
||||||
|
.faq:hover { border-color: var(--border-2); }
|
||||||
|
.faq summary { padding: 18px 22px; cursor: pointer; list-style: none; display: flex; align-items: center; gap: 14px; font-size: 15px; font-weight: 500; color: var(--text-0); position: relative; }
|
||||||
|
.faq summary::-webkit-details-marker { display: none; }
|
||||||
|
.faq summary::after { content: "+"; margin-left: auto; font-family: var(--font-mono); font-size: 18px; color: var(--text-3); transition: transform .2s; }
|
||||||
|
.faq[open] summary::after { content: "−"; color: var(--cyan); }
|
||||||
|
.faq summary .num { font-family: var(--font-mono); font-size: 11px; color: var(--cyan); letter-spacing: 0.1em; min-width: 26px; }
|
||||||
|
.faq .answer { padding: 0 22px 20px 62px; color: var(--text-2); font-size: 14px; line-height: 1.7; }
|
||||||
|
.faq .answer code { font-family: var(--font-mono); background: rgba(2, 6, 23, 0.6); padding: 1px 6px; border-radius: 3px; color: var(--cyan); font-size: 12.5px; }
|
||||||
|
.faq .answer a { color: var(--cyan); }
|
||||||
|
.faq .answer ul { padding-left: 20px; margin-top: 8px; }
|
||||||
|
|
||||||
|
.final-cta { max-width: 1180px; margin: 40px auto 80px; padding: 0 32px; }
|
||||||
|
.final-cta-inner { padding: 48px; border: 1px solid var(--border); border-radius: var(--r-xl); background: radial-gradient(800px 400px at 50% 0%, rgba(34,211,238,0.08), transparent 60%), rgba(15, 23, 42, 0.6); text-align: center; }
|
||||||
|
.final-cta-inner h2 { font-size: 32px; font-weight: 800; letter-spacing: -0.02em; margin-bottom: 10px; }
|
||||||
|
.final-cta-inner p { color: var(--text-2); font-size: 15px; margin-bottom: 26px; }
|
||||||
|
|
||||||
|
.preview-pill {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--amber, #fbbf24);
|
||||||
|
background: rgba(251, 191, 36, 0.08);
|
||||||
|
border: 1px solid rgba(251, 191, 36, 0.25);
|
||||||
|
border-radius: 100px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
.calc-preview-pill {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--amber, #fbbf24);
|
||||||
|
background: rgba(251, 191, 36, 0.08);
|
||||||
|
border: 1px solid rgba(251, 191, 36, 0.25);
|
||||||
|
border-radius: 100px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
.soon-chip {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 9px;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: var(--amber, #fbbf24);
|
||||||
|
background: rgba(251, 191, 36, 0.1);
|
||||||
|
border: 1px solid rgba(251, 191, 36, 0.3);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 1px 5px;
|
||||||
|
margin-left: 6px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pill inline code */
|
||||||
|
.pill { font-family: var(--font-mono); background: rgba(2, 6, 23, 0.6); padding: 1px 6px; border-radius: 3px; color: var(--cyan); font-size: 12.5px; }
|
||||||
|
|
||||||
|
/* nav brand */
|
||||||
|
.brand { display: flex; align-items: center; gap: 8px; text-decoration: none; color: var(--text-0); font-weight: 700; font-size: 15px; }
|
||||||
|
.hex { width: 24px; height: 24px; color: var(--cyan); }
|
||||||
|
.nav-links .active { color: var(--cyan); }
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.pricing-grid { grid-template-columns: 1fr 1fr; }
|
||||||
|
.custom-row { grid-template-columns: 1fr; }
|
||||||
|
.tools-grid { grid-template-columns: repeat(3, 1fr); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user