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:
@@ -14,7 +14,8 @@
|
||||
</router-link>
|
||||
<div class="nav-links">
|
||||
<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 class="nav-cta">
|
||||
<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