Pricing
blockpricingPricing collection with eleven interchangeable variants: seat matrix, interactive calculator, enterprise SLA card, feature add-on builder, feature tier slider, grouped matrix, per-plan cards, sticky-header matrix, single plan, tier comparison matrix, and usage calculator slider.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/pricing.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/pricing.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/pricing.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/pricing.jsonManifest:
https://uipkge.dev/r/svelte/pricing.jsonInstalls to:src/lib/components/blocks/pricing/Variants
Loading interactive previews…
Loading interactive previews…
npm dependencies
Files installed (11)
src/lib/components/blocks/pricing/Pricing01.svelte22.4 kB
<script lang="ts"> import { ArrowRight, Check, ChevronDown, ChevronUp, ShieldCheck, Users, Zap } from '@lucide/svelte' import { Badge } from '$lib/components/ui/badge' import { Button } from '$lib/components/ui/button' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '$lib/components/ui/card' import { Separator } from '$lib/components/ui/separator' import { Slider } from '$lib/components/ui/slider' import { Switch } from '$lib/components/ui/switch' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table' import { ToggleGroup, ToggleGroupItem } from '$lib/components/ui/toggle-group' import { cn } from '$lib/utils' interface Pricing01Props { class?: string } let { class: className }: Pricing01Props = $props() type Currency = 'USD' | 'EUR' | 'GBP' // ToggleGroup binds `string | string[]` (house convention, cf. the // toggle-group demo); the guards below keep these at the literal unions. let billingCycle = $state<string | string[]>('yearly') let currency = $state<string | string[]>('USD') let teamSeats = $state([12]) let addDedicatedSla = $state(false) let addAuditVault = $state(false) let showComparisonTable = $state(false) let prevBillingCycle: string | string[] = 'yearly' let prevCurrency: string | string[] = 'USD' // Single-select re-click yields '' — keep the previous selection, mirroring // the twin's `v && (...)` guard. Runs synchronously after the bind update, // so the render pass only ever sees a consistent value. function onBillingCycleChange(v: string | string[]) { if (v === '') billingCycle = prevBillingCycle else { billingCycle = v prevBillingCycle = v } } function onCurrencyChange(v: string | string[]) { if (v === '') currency = prevCurrency else { currency = v prevCurrency = v } } const currencySymbols: Record<Currency, string> = { USD: '$', EUR: '€', GBP: '£', } const currencyRates: Record<Currency, number> = { USD: 1.0, EUR: 0.92, GBP: 0.79, } const rates = $derived(currencyRates[currency as Currency]) const sym = $derived(currencySymbols[currency as Currency]) // Base tier pricing (per seat / month in USD) const starterBase = $derived(billingCycle === 'yearly' ? 12 : 15) const teamBase = $derived(billingCycle === 'yearly' ? 28 : 35) const enterpriseBase = $derived(billingCycle === 'yearly' ? 68 : 85) // Addons calculation const slaCost = $derived(addDedicatedSla ? 99 : 0) const vaultCost = $derived(addAuditVault ? 49 : 0) // Computed calculated totals const currentSeats = $derived(teamSeats[0] ?? 1) const starterMonthlyTotal = $derived( Math.round((starterBase * currentSeats + (addAuditVault ? 29 : 0)) * rates), ) const teamMonthlyTotal = $derived(Math.round((teamBase * currentSeats + slaCost + vaultCost) * rates)) const enterpriseMonthlyTotal = $derived( Math.round((enterpriseBase * currentSeats + slaCost + vaultCost) * rates), ) const annualSavingsTeam = $derived( Math.round((35 * currentSeats * 12 - 28 * currentSeats * 12) * rates), ) const comparisonMatrix = [ { category: 'Platform & Compute Core', features: [ { name: 'Active Team Members', starter: 'Up to 10', team: 'Unlimited', enterprise: 'Unlimited + Org Units' }, { name: 'Monthly API Requests', starter: '100,000 / mo', team: '2,500,000 / mo', enterprise: 'Custom Unlimited' }, { name: 'Workflow Automations', starter: '10 active', team: '100 active', enterprise: 'Unlimited real-time' }, { name: 'Data Retention History', starter: '30 days', team: '365 days', enterprise: '7 years immutable' }, ], }, { category: 'Security & Enterprise Governance', features: [ { name: 'SOC 2 Type II & ISO 27001', starter: 'Standard', team: 'Included', enterprise: 'Included + Auditor Portal', }, { name: 'SAML SSO & SCIM Provisioning', starter: '—', team: 'Google / Okta', enterprise: 'Custom IdP + SCIM v2' }, { name: 'Role-Based Access (RBAC)', starter: '3 predefined roles', team: 'Granular permissions', enterprise: 'Custom policy engine', }, { name: 'Immutable Audit Trail Logs', starter: '—', team: '90 days exportable', enterprise: 'Real-time SIEM streaming', }, ], }, { category: 'Support & Success SLAs', features: [ { name: 'Support Channel', starter: 'Community & Email', team: 'Priority Email + Chat', enterprise: 'Dedicated Private Slack', }, { name: 'First-Response SLA', starter: '24 business hours', team: '4 business hours', enterprise: '< 15 mins (24/7/365)', }, { name: 'Uptime SLA Guarantee', starter: '99.9%', team: '99.95%', enterprise: '99.99% financially backed' }, { name: 'Dedicated Solutions Architect', starter: '—', team: '—', enterprise: 'Assigned Principal Engineer' }, ], }, ] </script> <section data-slot="pricing-01" class={cn('bg-background w-full py-16 sm:py-24', className)}> <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> <!-- Section Header --> <div class="mx-auto max-w-3xl space-y-4 text-center"> <div class="border-primary/20 bg-primary/5 text-primary inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium shadow-2xs" > <ShieldCheck class="size-3.5" /> <span>Predictable Enterprise Pricing</span> </div> <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl"> Scale effortlessly without seat tax surprises </h2> <p class="text-muted-foreground text-base sm:text-lg"> Zero setup fees, transparent volume discounts, and instant self-serve provisioning. Switch or cancel plans anytime. </p> <!-- Billing Cycle & Currency Switcher Toolbar --> <div class="mt-8 flex flex-wrap items-center justify-center gap-4 pt-2"> <!-- Monthly vs Yearly --> <div class="border-border bg-card inline-flex rounded-lg border p-1 shadow-2xs"> <ToggleGroup type="single" bind:value={billingCycle} onValueChange={onBillingCycleChange}> <ToggleGroupItem value="monthly" class="px-3.5 py-1.5 text-xs font-medium"> Monthly </ToggleGroupItem> <ToggleGroupItem value="yearly" class="flex items-center gap-1.5 px-3.5 py-1.5 text-xs font-medium"> <span>Annual</span> <Badge variant="secondary" class="bg-primary/10 text-primary border-primary/20 px-1.5 py-0 text-xs"> Save 20% </Badge> </ToggleGroupItem> </ToggleGroup> </div> <!-- Currency Selector --> <div class="border-border bg-card inline-flex rounded-lg border p-1 shadow-2xs"> <ToggleGroup type="single" bind:value={currency} onValueChange={onCurrencyChange}> <ToggleGroupItem value="USD" class="px-2.5 py-1 font-mono text-xs font-medium">USD ($)</ToggleGroupItem> <ToggleGroupItem value="EUR" class="px-2.5 py-1 font-mono text-xs font-medium">EUR (€)</ToggleGroupItem> <ToggleGroupItem value="GBP" class="px-2.5 py-1 font-mono text-xs font-medium">GBP (£)</ToggleGroupItem> </ToggleGroup> </div> </div> <!-- Interactive Team Seat Simulator Bar --> <div class="border-border bg-card/60 mx-auto mt-6 max-w-xl rounded-xl border p-4 shadow-2xs backdrop-blur-xs"> <div class="flex flex-wrap items-center justify-between gap-2 text-xs"> <div class="text-foreground flex items-center gap-2 font-medium"> <Users class="text-primary size-4" /> <span>Simulate Team Size:</span> </div> <div class="flex items-center gap-2"> <span class="bg-muted text-foreground rounded px-2 py-0.5 font-mono text-xs font-semibold"> {currentSeats} {currentSeats === 1 ? 'seat' : 'seats'} </span> {#if billingCycle === 'yearly' && annualSavingsTeam > 0} <span class="text-success text-xs font-medium"> (Saves ~{sym}{annualSavingsTeam.toLocaleString()}/yr on Team plan) </span> {/if} </div> </div> <div class="mt-3"> <Slider bind:value={teamSeats} min={1} max={50} step={1} class="cursor-pointer" /> </div> <div class="text-muted-foreground mt-2 flex justify-between font-mono text-xs"> <span>1 seat</span> <span>25 seats</span> <span>50+ seats</span> </div> </div> </div> <!-- Plan Cards Grid --> <div class="mt-12 grid grid-cols-1 gap-8 lg:grid-cols-3 lg:items-stretch"> <!-- Tier 1: Starter --> <Card class="border-border bg-card hover:border-primary/40 relative flex flex-col justify-between shadow-xs transition-colors duration-200 hover:shadow-md" > <div> <CardHeader class="pb-4"> <div class="flex items-center justify-between"> <Badge variant="outline" class="font-mono text-xs">Bootstrap & Indie</Badge> </div> <CardTitle class="text-foreground text-xl font-bold tracking-tight">Starter Studio</CardTitle> <CardDescription class="text-muted-foreground text-xs"> For agile squads and early-stage product teams validating core workflows. </CardDescription> <div class="mt-6 space-y-1"> <div class="flex items-baseline gap-1.5"> <span class="text-foreground text-4xl font-bold tracking-tight"> {sym}{Math.round(starterBase * rates)} </span> <span class="text-muted-foreground text-xs font-medium">/ seat / month</span> </div> <div class="text-muted-foreground font-mono text-xs"> Est. {sym}{starterMonthlyTotal.toLocaleString()}/mo for {currentSeats} {currentSeats === 1 ? 'seat' : 'seats'} </div> </div> </CardHeader> <CardContent class="space-y-4 pt-2"> <Separator /> <div class="space-y-2.5"> <p class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">Included features</p> <ul class="text-foreground space-y-2.5 text-xs"> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span>Up to 10 team seats & unlimited guests</span> </li> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span>100,000 monthly API event calls</span> </li> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span>30-day continuous audit & revision history</span> </li> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span>Standard Community & Email support (24h)</span> </li> <li class="text-muted-foreground flex items-start gap-2"> <Check class="text-muted-foreground/50 mt-0.5 size-4 shrink-0" /> <span>Community Discord access & quickstarts</span> </li> </ul> </div> </CardContent> </div> <CardFooter class="border-border mt-auto border-t pt-4"> <Button variant="outline" class="h-10 w-full gap-1.5 text-xs font-semibold"> <span>Start Free 14-Day Trial</span> <ArrowRight class="size-3.5" /> </Button> </CardFooter> </Card> <!-- Tier 2: Team Pro (Highlighted Flagship) --> <Card class="border-primary bg-card ring-primary/20 relative flex flex-col justify-between shadow-lg ring-2 transition-colors duration-200 hover:shadow-xl lg:-translate-y-2" > <div class="absolute -top-3.5 left-1/2 -translate-x-1/2"> <Badge class="bg-primary text-primary-foreground gap-1.5 px-3 py-0.5 text-xs font-semibold shadow-sm"> <Zap class="size-3 fill-current" /> <span>Most Popular Choice</span> </Badge> </div> <div> <CardHeader class="pt-7 pb-4"> <div class="flex items-center justify-between"> <Badge variant="secondary" class="bg-primary/10 text-primary border-primary/20 font-mono text-xs"> Scale & High-Growth </Badge> </div> <CardTitle class="text-foreground text-2xl font-bold tracking-tight">Team Scale</CardTitle> <CardDescription class="text-muted-foreground text-xs"> For fast-scaling engineering and product organizations requiring deep automation. </CardDescription> <div class="mt-6 space-y-1"> <div class="flex items-baseline gap-1.5"> <span class="text-foreground text-4xl font-bold tracking-tight"> {sym}{Math.round(teamBase * rates)} </span> <span class="text-muted-foreground text-xs font-medium">/ seat / month</span> </div> <div class="text-primary font-mono text-xs font-medium"> Est. {sym}{teamMonthlyTotal.toLocaleString()}/mo for {currentSeats} {currentSeats === 1 ? 'seat' : 'seats'} </div> </div> </CardHeader> <CardContent class="space-y-4 pt-2"> <Separator /> <div class="space-y-2.5"> <p class="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Everything in Starter, plus </p> <ul class="text-foreground space-y-2.5 text-xs"> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span class="font-medium">Unlimited team seats & custom workspace roles</span> </li> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span>2.5M monthly API calls & priority queue</span> </li> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span>Google & Okta SAML SSO integration</span> </li> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span>365-day immutable compliance logs</span> </li> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span>Priority ticketing with 4h guaranteed SLA</span> </li> </ul> </div> <!-- Addons Selector for Team --> <div class="border-border bg-muted/30 space-y-2.5 rounded-lg border p-3"> <p class="text-foreground text-xs font-semibold">Optional Power Add-ons</p> <div class="flex items-center justify-between text-xs"> <label for="svelte-sla-addon" class="text-muted-foreground cursor-pointer"> 24/7 Dedicated SLA (+{sym}{Math.round(99 * rates)}/mo) </label> <Switch id="svelte-sla-addon" bind:checked={addDedicatedSla} /> </div> <div class="flex items-center justify-between text-xs"> <label for="svelte-vault-addon" class="text-muted-foreground cursor-pointer"> SOC 2 Vault Streaming (+{sym}{Math.round(49 * rates)}/mo) </label> <Switch id="svelte-vault-addon" bind:checked={addAuditVault} /> </div> </div> </CardContent> </div> <CardFooter class="border-border mt-auto border-t pt-4"> <Button class="h-10 w-full gap-1.5 text-xs font-semibold shadow-sm"> <span>Deploy Team Workspace</span> <ArrowRight class="size-3.5" /> </Button> </CardFooter> </Card> <!-- Tier 3: Enterprise Platform --> <Card class="border-border bg-card hover:border-primary/40 relative flex flex-col justify-between shadow-xs transition-colors duration-200 hover:shadow-md" > <div> <CardHeader class="pb-4"> <div class="flex items-center justify-between"> <Badge variant="outline" class="font-mono text-xs">Enterprise & Security</Badge> </div> <CardTitle class="text-foreground text-xl font-bold tracking-tight">Enterprise Suite</CardTitle> <CardDescription class="text-muted-foreground text-xs"> Dedicated infrastructure, custom security controls, and bespoke compliance SLAs. </CardDescription> <div class="mt-6 space-y-1"> <div class="flex items-baseline gap-1.5"> <span class="text-foreground text-4xl font-bold tracking-tight"> {sym}{Math.round(enterpriseBase * rates)} </span> <span class="text-muted-foreground text-xs font-medium">/ seat / month</span> </div> <div class="text-muted-foreground font-mono text-xs"> Custom volume licensing available for 100+ seats </div> </div> </CardHeader> <CardContent class="space-y-4 pt-2"> <Separator /> <div class="space-y-2.5"> <p class="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Everything in Team, plus </p> <ul class="text-foreground space-y-2.5 text-xs"> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span>Custom SCIM v2 user provisioning & directory sync</span> </li> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span>Dedicated VPC peering & custom data residency</span> </li> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span>99.99% uptime SLA with financial penalty backing</span> </li> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span>Assigned Principal Solutions Engineer & private Slack</span> </li> <li class="flex items-start gap-2"> <Check class="text-success mt-0.5 size-4 shrink-0" /> <span>Custom Master Service Agreement (MSA) & DPA</span> </li> </ul> </div> </CardContent> </div> <CardFooter class="border-border mt-auto border-t pt-4"> <Button variant="outline" class="h-10 w-full gap-1.5 text-xs font-semibold"> <ShieldCheck class="text-primary size-3.5" /> <span>Contact Solutions Team</span> </Button> </CardFooter> </Card> </div> <!-- Collapsible Feature Matrix Trigger --> <div class="mt-12 text-center"> <Button variant="ghost" size="sm" class="text-foreground hover:bg-muted gap-2 text-xs font-semibold" onclick={() => (showComparisonTable = !showComparisonTable)} > <span>{showComparisonTable ? 'Hide Detailed Feature Comparison' : 'Compare All Features & Enterprise Limits'}</span> {#if showComparisonTable} <ChevronUp class="size-4" /> {:else} <ChevronDown class="size-4" /> {/if} </Button> </div> <!-- Feature Comparison Matrix Table --> {#if showComparisonTable} <div class="border-border bg-card mt-8 overflow-hidden rounded-xl border shadow-xs"> <div class="border-border bg-muted/20 flex flex-wrap items-center justify-between gap-4 border-b p-4 sm:p-6"> <div> <h3 class="text-foreground text-base font-bold">Detailed Specification & Limits</h3> <p class="text-muted-foreground text-xs">Comprehensive side-by-side breakdown across every tier.</p> </div> <Badge variant="outline" class="font-mono text-xs">All plans include SSL & automated backups</Badge> </div> <div class="overflow-x-auto"> <Table> <TableHeader> <TableRow class="bg-muted/40"> <TableHead class="text-foreground w-[34%] text-xs font-semibold">Capabilities</TableHead> <TableHead class="text-foreground w-[22%] text-xs font-semibold">Starter Studio</TableHead> <TableHead class="text-primary w-[22%] text-xs font-bold font-semibold">Team Scale</TableHead> <TableHead class="text-foreground w-[22%] text-xs font-semibold">Enterprise Suite</TableHead> </TableRow> </TableHeader> <TableBody> {#each comparisonMatrix as group, idx (idx)} <TableRow class="bg-muted/60 text-muted-foreground text-xs font-semibold"> <TableCell colspan={4} class="py-2.5 font-mono text-xs tracking-wider uppercase"> {group.category} </TableCell> </TableRow> {#each group.features as feat, fIdx (fIdx)} <TableRow class="text-xs"> <TableCell class="text-foreground py-3 font-medium"> {feat.name} </TableCell> <TableCell class="text-muted-foreground py-3">{feat.starter}</TableCell> <TableCell class="text-foreground bg-primary/5 py-3 font-medium">{feat.team}</TableCell> <TableCell class="text-foreground py-3">{feat.enterprise}</TableCell> </TableRow> {/each} {/each} </TableBody> </Table> </div> </div> {/if} </div> </section>src/lib/components/blocks/pricing/PricingCalculator.svelte18.6 kB
<script lang="ts"> import { ArrowRight, Check, HardDrive, Headphones, KeyRound, ShieldCheck, Users, Zap } from '@lucide/svelte' import { cn } from '$lib/utils' import { Badge } from '$lib/components/ui/badge' import { Button } from '$lib/components/ui/button' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '$lib/components/ui/card' import { Separator } from '$lib/components/ui/separator' import { Slider } from '$lib/components/ui/slider' import { Switch } from '$lib/components/ui/switch' interface PricingCalculatorProps { class?: string } let { class: className }: PricingCalculatorProps = $props() interface RequestTier { label: string requests: number cost: number } interface StorageTier { label: string gb: number cost: number } const REQUEST_TIERS: RequestTier[] = [ { label: '10K requests', requests: 10_000, cost: 0 }, { label: '50K requests', requests: 50_000, cost: 25 }, { label: '100K requests', requests: 100_000, cost: 60 }, { label: '250K requests', requests: 250_000, cost: 120 }, { label: '500K requests', requests: 500_000, cost: 220 }, { label: '1M requests', requests: 1_000_000, cost: 400 }, { label: '2.5M requests', requests: 2_500_000, cost: 750 }, { label: '5M requests', requests: 5_000_000, cost: 1200 }, ] const STORAGE_TIERS: StorageTier[] = [ { label: '100 GB', gb: 100, cost: 10 }, { label: '250 GB', gb: 250, cost: 25 }, { label: '500 GB', gb: 500, cost: 45 }, { label: '1 TB', gb: 1_000, cost: 80 }, { label: '2 TB', gb: 2_000, cost: 150 }, { label: '5 TB', gb: 5_000, cost: 320 }, { label: '10 TB', gb: 10_000, cost: 580 }, ] const SEAT_PRICE = 15 const SUPPORT_MANAGER_PRICE = 200 const CUSTOM_SLA_PRICE = 500 const SSO_PRICE = 100 const SEAT_MARKS = { 1: '1', 25: '25', 50: '50', 75: '75', 100: '100', } const REQUEST_MARKS = { 0: '10K', 2: '100K', 4: '500K', 7: '5M', } const STORAGE_MARKS = { 0: '100 GB', 2: '500 GB', 4: '2 TB', 6: '10 TB', } let isAnnual = $state(true) let seats = $state(12) let requestIndex = $state(2) let storageIndex = $state(2) let addonSupport = $state(false) let addonSla = $state(false) let addonSso = $state(true) const currentRequestTier = $derived(REQUEST_TIERS[requestIndex] ?? REQUEST_TIERS[0]) const currentStorageTier = $derived(STORAGE_TIERS[storageIndex] ?? STORAGE_TIERS[0]) const seatTier = $derived.by(() => { if (seats <= 5) return { label: 'Starter Team', variant: 'secondary' as const } if (seats <= 25) return { label: 'Growth Team', variant: 'outline' as const } return { label: 'Scale Team', variant: 'default' as const } }) const recommendedPlan = $derived.by(() => { if (addonSla || requestIndex >= 6 || storageIndex >= 5 || seats >= 30) { return { name: 'Enterprise', badge: 'Enterprise Plan', baseFee: 199, variant: 'default' as const } } if (seats >= 8 || requestIndex >= 3 || storageIndex >= 3 || addonSupport) { return { name: 'Growth', badge: 'Growth Plan', baseFee: 79, variant: 'default' as const } } return { name: 'Starter', badge: 'Starter Plan', baseFee: 29, variant: 'secondary' as const } }) const baseCost = $derived(recommendedPlan.baseFee) const seatsCost = $derived(seats * SEAT_PRICE) const requestsCost = $derived(currentRequestTier.cost) const storageCost = $derived(currentStorageTier.cost) const addonsCost = $derived( (addonSupport ? SUPPORT_MANAGER_PRICE : 0) + (addonSla ? CUSTOM_SLA_PRICE : 0) + (addonSso ? SSO_PRICE : 0), ) const activeAddonsCount = $derived( (addonSupport ? 1 : 0) + (addonSla ? 1 : 0) + (addonSso ? 1 : 0), ) const subtotalMonthly = $derived(baseCost + seatsCost + requestsCost + storageCost + addonsCost) const discountMultiplier = $derived(isAnnual ? 0.8 : 1.0) const totalMonthly = $derived(Math.round(subtotalMonthly * discountMultiplier)) const totalAnnual = $derived(totalMonthly * 12) const annualSavings = $derived(Math.round(subtotalMonthly * 0.2 * 12)) </script> <div data-slot="pricing-calculator" class={cn('mx-auto w-full max-w-6xl space-y-10 p-4 sm:p-6 lg:p-8', className)}> <div class="flex flex-col items-center gap-4 text-center"> <Badge variant="outline" class="gap-1.5 px-3 py-1 text-xs font-medium tracking-wider uppercase"> <ShieldCheck class="text-primary size-3.5" /> Pricing Calculator </Badge> <div class="space-y-2"> <h2 class="text-foreground text-2xl font-bold tracking-tight sm:text-3xl lg:text-4xl"> Interactive Pricing Calculator </h2> <p class="text-muted-foreground mx-auto max-w-2xl text-sm sm:text-base"> Calculate your exact monthly or annual investment based on usage. </p> </div> <div class="border-border bg-card mt-2 inline-flex items-center gap-3 rounded-full border px-4 py-2 shadow-xs"> <span class={cn( 'text-xs font-medium transition-colors', !isAnnual ? 'text-foreground font-semibold' : 'text-muted-foreground', )} > Monthly </span> <Switch bind:checked={isAnnual} /> <span class={cn( 'text-xs font-medium transition-colors', isAnnual ? 'text-foreground font-semibold' : 'text-muted-foreground', )} > Pay Annually </span> <Badge variant="secondary" class="bg-primary/10 text-primary border-primary/20 text-xs font-semibold"> Save 20% </Badge> </div> </div> <div class="grid grid-cols-1 gap-8 lg:grid-cols-12 lg:items-start"> <div class="space-y-6 lg:col-span-7"> <Card> <CardHeader> <CardTitle>Capacity & Scale</CardTitle> <CardDescription>Configure team seats, API request throughput, and dedicated storage.</CardDescription> </CardHeader> <CardContent class="space-y-8"> <div class="space-y-4"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2.5"> <div class="bg-primary/10 text-primary border-primary/20 flex size-8 shrink-0 items-center justify-center rounded-md border" > <Users class="size-4" /> </div> <div> <span class="text-foreground text-sm font-medium">Team Seats</span> <Badge variant={seatTier.variant} class="ml-2 text-xs font-normal"> {seatTier.label} </Badge> </div> </div> <div class="text-right"> <span class="text-foreground text-base font-semibold tabular-nums">{seats}</span> <span class="text-muted-foreground text-xs"> seats (${seats * SEAT_PRICE}/mo)</span> </div> </div> <div class="pb-6"> <Slider bind:value={seats} min={1} max={100} step={1} marks={SEAT_MARKS} tooltip={(val) => `${val} seats`} /> </div> <p class="text-muted-foreground text-xs"> Full workspace access, fine-grained permission controls, and audit log tracking for each member. </p> </div> <Separator /> <div class="space-y-4"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2.5"> <div class="bg-primary/10 text-primary border-primary/20 flex size-8 shrink-0 items-center justify-center rounded-md border" > <Zap class="size-4" /> </div> <div> <span class="text-foreground text-sm font-medium">Monthly API Requests</span> </div> </div> <div class="text-right"> <span class="text-foreground text-base font-semibold tabular-nums"> {currentRequestTier.label} </span> <span class="text-muted-foreground text-xs"> ({currentRequestTier.cost === 0 ? 'Included' : `+$${currentRequestTier.cost}/mo`}) </span> </div> </div> <div class="pb-6"> <Slider bind:value={requestIndex} min={0} max={REQUEST_TIERS.length - 1} step={1} marks={REQUEST_MARKS} tooltip={(idx) => REQUEST_TIERS[idx]?.label ?? ''} /> </div> <p class="text-muted-foreground text-xs"> Globally distributed edge endpoints, automatic rate limiting, and sub-50ms p99 latency SLA. </p> </div> <Separator /> <div class="space-y-4"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2.5"> <div class="bg-primary/10 text-primary border-primary/20 flex size-8 shrink-0 items-center justify-center rounded-md border" > <HardDrive class="size-4" /> </div> <div> <span class="text-foreground text-sm font-medium">Dedicated Cloud Storage</span> </div> </div> <div class="text-right"> <span class="text-foreground text-base font-semibold tabular-nums"> {currentStorageTier.label} </span> <span class="text-muted-foreground text-xs"> (+${currentStorageTier.cost}/mo)</span> </div> </div> <div class="pb-6"> <Slider bind:value={storageIndex} min={0} max={STORAGE_TIERS.length - 1} step={1} marks={STORAGE_MARKS} tooltip={(idx) => STORAGE_TIERS[idx]?.label ?? ''} /> </div> <p class="text-muted-foreground text-xs"> Encrypted at rest (AES-256) with multi-region automated replication and daily disaster backups. </p> </div> </CardContent> </Card> <Card> <CardHeader> <CardTitle>Enterprise Add-ons</CardTitle> <CardDescription> Enhance your infrastructure with enterprise compliance, reliability, and support. </CardDescription> </CardHeader> <CardContent class="space-y-3"> <div class="border-border bg-card hover:border-primary/30 flex flex-col justify-between gap-4 rounded-lg border p-4 transition-colors sm:flex-row sm:items-center" > <div class="flex items-start gap-3"> <div class="bg-primary/10 text-primary border-primary/20 mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border" > <Headphones class="size-4" /> </div> <div class="space-y-0.5"> <div class="flex items-center gap-2"> <span class="text-foreground text-sm font-medium">Dedicated Support Manager</span> <Badge variant="outline" class="text-xs font-normal">+$200/mo</Badge> </div> <p class="text-muted-foreground text-xs"> Direct Slack channel, named technical account manager & 1-hour response SLA. </p> </div> </div> <div class="flex sm:justify-end"> <Switch bind:checked={addonSupport} /> </div> </div> <div class="border-border bg-card hover:border-primary/30 flex flex-col justify-between gap-4 rounded-lg border p-4 transition-colors sm:flex-row sm:items-center" > <div class="flex items-start gap-3"> <div class="bg-primary/10 text-primary border-primary/20 mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border" > <ShieldCheck class="size-4" /> </div> <div class="space-y-0.5"> <div class="flex items-center gap-2"> <span class="text-foreground text-sm font-medium">Custom SLA Guarantee</span> <Badge variant="outline" class="text-xs font-normal">+$500/mo</Badge> </div> <p class="text-muted-foreground text-xs"> 99.99% uptime guarantee with financial commitments and priority disaster recovery. </p> </div> </div> <div class="flex sm:justify-end"> <Switch bind:checked={addonSla} /> </div> </div> <div class="border-border bg-card hover:border-primary/30 flex flex-col justify-between gap-4 rounded-lg border p-4 transition-colors sm:flex-row sm:items-center" > <div class="flex items-start gap-3"> <div class="bg-primary/10 text-primary border-primary/20 mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border" > <KeyRound class="size-4" /> </div> <div class="space-y-0.5"> <div class="flex items-center gap-2"> <span class="text-foreground text-sm font-medium">Single Sign-On (SSO / SAML)</span> <Badge variant="outline" class="text-xs font-normal">+$100/mo</Badge> </div> <p class="text-muted-foreground text-xs"> Okta, Azure AD, Google Workspace, and SAML 2.0 enterprise identity integration. </p> </div> </div> <div class="flex sm:justify-end"> <Switch bind:checked={addonSso} /> </div> </div> </CardContent> </Card> </div> <div class="lg:sticky lg:top-8 lg:col-span-5"> <Card class="border-border bg-card shadow-xs"> <CardHeader class="pb-4"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Estimated Investment </span> <Badge variant={recommendedPlan.variant} class="gap-1 shadow-xs"> {#if recommendedPlan.name === 'Enterprise'} <ShieldCheck class="size-3" /> {/if} {recommendedPlan.badge} </Badge> </div> <div class="mt-4"> <div class="flex items-baseline gap-1.5"> <span class="text-foreground text-4xl font-bold tracking-tight tabular-nums sm:text-5xl"> ${totalMonthly} </span> <span class="text-muted-foreground text-sm font-normal"> / month</span> </div> {#if isAnnual} <p class="text-muted-foreground mt-2 text-xs"> Billed annually (${totalAnnual.toLocaleString('en-US')}/yr) · <span class="text-success font-semibold">Save ${annualSavings.toLocaleString('en-US')}/yr</span> </p> {:else} <p class="text-muted-foreground mt-2 text-xs"> Billed monthly · Switch to annual to save 20% (${annualSavings.toLocaleString('en-US')}/yr) </p> {/if} </div> </CardHeader> <CardContent class="space-y-4"> <Separator /> <div class="space-y-2.5"> <div class="flex items-center justify-between text-sm"> <span class="text-muted-foreground">Base platform ({recommendedPlan.name})</span> <span class="font-medium tabular-nums">${isAnnual ? Math.round(baseCost * 0.8) : baseCost}/mo</span> </div> <div class="flex items-center justify-between text-sm"> <span class="text-muted-foreground">Team seats ({seats} × ${SEAT_PRICE})</span> <span class="font-medium tabular-nums"> ${isAnnual ? Math.round(seatsCost * 0.8) : seatsCost}/mo </span> </div> <div class="flex items-center justify-between text-sm"> <span class="text-muted-foreground">API throughput ({currentRequestTier.label})</span> <span class="font-medium tabular-nums"> {requestsCost === 0 ? 'Included' : `$${isAnnual ? Math.round(requestsCost * 0.8) : requestsCost}/mo`} </span> </div> <div class="flex items-center justify-between text-sm"> <span class="text-muted-foreground">Cloud storage ({currentStorageTier.label})</span> <span class="font-medium tabular-nums"> ${isAnnual ? Math.round(storageCost * 0.8) : storageCost}/mo </span> </div> <div class="flex items-center justify-between text-sm"> <span class="text-muted-foreground">Add-ons ({activeAddonsCount} active)</span> <span class="font-medium tabular-nums"> {addonsCost === 0 ? '$0/mo' : `$${isAnnual ? Math.round(addonsCost * 0.8) : addonsCost}/mo`} </span> </div> {#if isAnnual} <div class="bg-success/10 text-success flex items-center justify-between rounded-md px-2.5 py-1.5 text-xs font-medium" > <span>Annual discount applied</span> <span class="font-semibold tabular-nums">20% off</span> </div> {/if} </div> <Separator /> <ul class="text-muted-foreground space-y-2 text-xs"> <li class="flex items-center gap-2"> <Check class="text-primary size-3.5 shrink-0" /> <span>14-day fully featured free trial</span> </li> <li class="flex items-center gap-2"> <Check class="text-primary size-3.5 shrink-0" /> <span>No credit card required upfront</span> </li> <li class="flex items-center gap-2"> <Check class="text-primary size-3.5 shrink-0" /> <span>Zero-downtime migration assistance</span> </li> </ul> </CardContent> <CardFooter class="flex flex-col gap-2.5 pt-2"> <Button class="w-full gap-2 font-semibold shadow-xs" size="lg"> Start 14-Day Free Trial <ArrowRight class="size-4" /> </Button> <Button variant="outline" class="w-full" size="default"> Request Custom Quote </Button> </CardFooter> </Card> </div> </div> </div>src/lib/components/blocks/pricing/PricingEnterpriseSlaCard.svelte8.3 kB
<script lang="ts"> import { ArrowRight, Building2, Calendar, CircleCheckBig, Lock, Server } from '@lucide/svelte' import { Badge } from '$lib/components/ui/badge' import { Button } from '$lib/components/ui/button' import { cn } from '$lib/utils' export interface SlaGuarantee { title: string metric: string description: string icon: string } export interface ComplianceCert { id: string name: string status: string badgeVariant?: 'default' | 'outline' | 'secondary' } interface PricingEnterpriseSlaCardProps { title?: string description?: string guarantees?: SlaGuarantee[] certifications?: ComplianceCert[] class?: string } const DEFAULT_GUARANTEES: SlaGuarantee[] = [ { title: 'High-Availability SLA', metric: '99.999%', description: 'Financial penalty-backed monthly uptime commitment across multi-region clusters.', icon: 'ShieldCheck', }, { title: 'P1 Incident Response', metric: '< 15 mins', description: 'Direct paging to designated Staff Infrastructure Commanders 24/7/365.', icon: 'Headphones', }, { title: 'Global Edge TTFB', metric: '< 12ms', description: 'Sub-15ms Time-To-First-Byte guaranteed via Anycast network mesh.', icon: 'Zap', }, { title: 'Custom Legal & DPA', metric: 'Bespoke', description: 'Dedicated legal counsel review, redline allowances, and customized BAAs.', icon: 'Scale', }, ] const DEFAULT_CERTS: ComplianceCert[] = [ { id: 'soc2', name: 'SOC 2 Type II Certified', status: 'Continuous Audit' }, { id: 'hipaa', name: 'HIPAA Compliant BAA', status: 'Available' }, { id: 'iso27001', name: 'ISO/IEC 27001:2022', status: 'Certified' }, { id: 'gdpr', name: 'GDPR & CCPA Verified', status: 'Compliant' }, { id: 'fedramp', name: 'FedRAMP In-Process', status: 'High Baseline' }, ] let { title = 'Mission-critical infrastructure with contractual legal guarantees.', description = 'Tailored enterprise licensing, custom security reviews, isolated VPC deployments, and white-glove migration engineering.', guarantees, certifications, class: className, }: PricingEnterpriseSlaCardProps = $props() const activeGuarantees = $derived(guarantees ?? DEFAULT_GUARANTEES) const activeCertifications = $derived(certifications ?? DEFAULT_CERTS) let isMeetingRequested = $state(false) function requestMeeting() { isMeetingRequested = true setTimeout(() => { isMeetingRequested = false }, 3000) } </script> <section data-slot="pricing-enterprise-sla-card" class={cn('bg-background relative overflow-hidden py-16 sm:py-24', className)}> <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> <!-- Section Header --> <div class="mx-auto max-w-3xl space-y-4 text-center"> <a href="#enterprise-contract" class="group border-border/80 bg-secondary/60 hover:bg-secondary text-foreground inline-flex items-center gap-2 rounded-full border px-3.5 py-1 text-xs font-medium shadow-2xs transition-colors" > <Building2 class="text-primary size-3.5" /> <span>Enterprise Custom Contracting</span> <ArrowRight class="text-muted-foreground size-3 transition-transform group-hover:translate-x-0.5" /> </a> <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl"> {title} </h2> <p class="text-muted-foreground text-base sm:text-lg"> {description} </p> </div> <!-- Main Enterprise Showcase Container --> <div class="border-border bg-card mt-12 overflow-hidden rounded-2xl border shadow-sm"> <div class="divide-border grid grid-cols-1 divide-y lg:grid-cols-12 lg:divide-x lg:divide-y-0"> <!-- Left: SLA Guarantees & Contractual Commitments (7 Cols) --> <div class="space-y-8 p-8 lg:col-span-7"> <div class="space-y-1"> <div class="text-primary text-xs font-bold tracking-wider uppercase">Service Level Agreement</div> <h3 class="text-foreground text-xl font-bold">Penalty-Backed Contractual Metrics</h3> <p class="text-muted-foreground text-xs"> Every commitment is codified into your master service agreement with direct financial remedies. </p> </div> <!-- Guarantees 2x2 Grid --> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2"> {#each activeGuarantees as item, idx (idx)} <div class="border-border bg-muted/20 space-y-2 rounded-xl border p-4"> <div class="flex items-center justify-between"> <span class="text-foreground text-xs font-bold">{item.title}</span> <span class="text-primary font-mono text-xs font-bold">{item.metric}</span> </div> <p class="text-muted-foreground text-xs leading-relaxed"> {item.description} </p> </div> {/each} </div> <!-- Compliance & Governance Strip --> <div class="space-y-3 pt-2"> <div class="text-muted-foreground flex items-center gap-2 text-xs font-bold tracking-wider uppercase"> <Lock class="text-primary size-3.5" /> <span>Security & Regulatory Attestations</span> </div> <div class="flex flex-wrap gap-2"> {#each activeCertifications as cert (cert.id)} <Badge variant="outline" class="border-border bg-background text-foreground gap-1.5 px-3 py-1 text-xs font-medium" > <CircleCheckBig class="text-success size-3" /> <span>{cert.name}</span> <span class="text-muted-foreground font-mono text-xs">({cert.status})</span> </Badge> {/each} </div> </div> </div> <!-- Right: Direct Enterprise Solution Consultation Card (5 Cols) --> <div class="bg-muted/30 flex flex-col justify-between space-y-6 p-8 lg:col-span-5"> <div class="space-y-4"> <div class="border-border flex items-center justify-between border-b pb-3"> <div class="flex items-center gap-2"> <Server class="text-primary size-4" /> <span class="text-foreground text-sm font-semibold">Custom Private Deployment</span> </div> <Badge variant="outline" class="border-success/30 bg-success/10 text-success font-mono text-xs"> Tailored </Badge> </div> <div class="space-y-2.5"> <div class="text-foreground text-xs font-bold">Included with Custom Tier:</div> <ul class="text-muted-foreground space-y-2 text-xs"> <li class="flex items-center gap-2"> <CircleCheckBig class="text-success size-3.5 shrink-0" /> <span>Dedicated AWS / GCP VPC peering or self-hosted air-gap</span> </li> <li class="flex items-center gap-2"> <CircleCheckBig class="text-success size-3.5 shrink-0" /> <span>Custom SAML 2.0 / Okta / Azure AD SCIM provisioning</span> </li> <li class="flex items-center gap-2"> <CircleCheckBig class="text-success size-3.5 shrink-0" /> <span>Dedicated Solution Architect & design system migration team</span> </li> <li class="flex items-center gap-2"> <CircleCheckBig class="text-success size-3.5 shrink-0" /> <span>Invoiced payment via ACH, Wire Transfer, or AWS Marketplace</span> </li> </ul> </div> </div> <!-- Action Block --> <div class="border-border space-y-3 border-t pt-4"> <Button class="w-full gap-2 shadow-xs" size="lg" onclick={requestMeeting}> <Calendar class="size-4" /> <span>{isMeetingRequested ? 'Direct Routing to Architect...' : 'Book Enterprise Technical Review'}</span> </Button> <div class="text-muted-foreground text-center text-xs"> Average executive response time: <strong>under 20 minutes</strong> </div> </div> </div> </div> </div> </div> </section>src/lib/components/blocks/pricing/PricingFeatureAddonBuilder.svelte14 kB
<script lang="ts"> import { ArrowRight, Calculator, Check, CreditCard } from '@lucide/svelte' import { Badge } from '$lib/components/ui/badge' import { Button } from '$lib/components/ui/button' import { Card, CardContent } from '$lib/components/ui/card' import { cn } from '$lib/utils' export interface PricingPlan { id: string name: string monthlyPrice: number description: string includedSeats: number } export interface PricingAddon { id: string name: string monthlyPrice: number description: string category: string } interface PricingFeatureAddonBuilderProps { title?: string description?: string plans?: PricingPlan[] addons?: PricingAddon[] class?: string } const DEFAULT_PLANS: PricingPlan[] = [ { id: 'starter', name: 'Starter Tier', monthlyPrice: 29, description: 'For indie developers and early-stage prototypes needing unbundled speed.', includedSeats: 2, }, { id: 'growth', name: 'Growth Core', monthlyPrice: 99, description: 'For scaling product engineering teams building high-conversion platforms.', includedSeats: 5, }, { id: 'scale', name: 'Scale Enterprise', monthlyPrice: 299, description: 'For mission-critical production clusters requiring sub-10ms global edge delivery.', includedSeats: 15, }, ] const DEFAULT_ADDONS: PricingAddon[] = [ { id: 'dedicated-ip', name: 'Dedicated Static Edge IP', monthlyPrice: 49, description: 'Static IPv4/IPv6 address allocations with zero-reputation penalty.', category: 'Network', }, { id: 'audit-logs', name: 'Immutable SOC2 Audit Logs', monthlyPrice: 79, description: 'Cryptographically signed telemetry logs with 365-day cold storage retention.', category: 'Security', }, { id: 'multi-region', name: 'Multi-Region Active-Active Mesh', monthlyPrice: 129, description: 'Synchronized cross-continental database replicas and automatic DNS failover.', category: 'Reliability', }, { id: 'priority-sla', name: '1-Hour Enterprise Response SLA', monthlyPrice: 199, description: 'Direct Slack / Discord hotline with senior design engineering staff.', category: 'Support', }, ] let { title = 'Build your custom plan with transparent, zero-surprise pricing.', description = 'Select your base tier, adjust seat allocations, and toggle modular enterprise add-ons with real-time invoice calculations.', plans, addons, class: className, }: PricingFeatureAddonBuilderProps = $props() const activePlans = $derived(plans ?? DEFAULT_PLANS) const activeAddons = $derived(addons ?? DEFAULT_ADDONS) let selectedPlanId = $state('growth') let selectedAddonIds = $state<string[]>(['dedicated-ip', 'audit-logs']) let seatCount = $state(8) let isAnnual = $state(true) const selectedPlan = $derived(activePlans.find((p) => p.id === selectedPlanId) ?? activePlans[0]) const extraSeats = $derived(Math.max(0, seatCount - selectedPlan.includedSeats)) const extraSeatCost = $derived(extraSeats * 15) const totalAddonsCost = $derived( activeAddons.filter((a) => selectedAddonIds.includes(a.id)).reduce((sum, a) => sum + a.monthlyPrice, 0), ) const monthlySubtotal = $derived(selectedPlan.monthlyPrice + extraSeatCost + totalAddonsCost) const finalMonthlyRate = $derived(isAnnual ? Math.round(monthlySubtotal * 0.8) : monthlySubtotal) const annualSavings = $derived((monthlySubtotal - Math.round(monthlySubtotal * 0.8)) * 12) const selectedAddons = $derived(activeAddons.filter((a) => selectedAddonIds.includes(a.id))) function toggleAddon(addonId: string) { if (selectedAddonIds.includes(addonId)) { selectedAddonIds = selectedAddonIds.filter((id) => id !== addonId) } else { selectedAddonIds.push(addonId) } } </script> <section data-slot="pricing-feature-addon-builder" class={cn('bg-background relative overflow-hidden py-16 sm:py-24', className)} > <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> <!-- Section Header --> <div class="mx-auto max-w-3xl space-y-4 text-center"> <a href="#pricing-calculator" class="group border-border/80 bg-secondary/60 hover:bg-secondary text-foreground inline-flex items-center gap-2 rounded-full border px-3.5 py-1 text-xs font-medium shadow-2xs transition-colors" > <Calculator class="text-primary size-3.5" /> <span>Real-time Add-on Cost Synthesizer</span> <ArrowRight class="text-muted-foreground size-3 transition-transform group-hover:translate-x-0.5" /> </a> <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl"> {title} </h2> <p class="text-muted-foreground text-base sm:text-lg"> {description} </p> <!-- Billing Cadence Toggle --> <div class="flex items-center justify-center gap-3 pt-2"> <span class="text-xs font-medium" class:text-foreground={!isAnnual} class:font-semibold={!isAnnual} class:text-muted-foreground={isAnnual}> Monthly Billing </span> <button type="button" aria-label="Toggle annual billing" class="bg-muted focus-visible:ring-ring relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus-visible:ring-2 focus-visible:outline-none" class:bg-primary={isAnnual} class:bg-muted={!isAnnual} onclick={() => (isAnnual = !isAnnual)} > <span class="bg-background pointer-events-none inline-block size-5 transform rounded-full shadow-lg ring-0 transition duration-200 ease-in-out" class:translate-x-5={isAnnual} class:translate-x-0={!isAnnual} ></span> </button> <span class="flex items-center gap-1.5 text-xs font-medium" class:text-foreground={isAnnual} class:font-semibold={isAnnual} class:text-muted-foreground={!isAnnual} > <span>Annual Billing</span> <Badge variant="outline" class="border-success/30 bg-success/10 text-success text-xs"> Save 20% </Badge> </span> </div> </div> <!-- Add-on Builder Workbench --> <div class="mt-12 grid grid-cols-1 gap-6 lg:grid-cols-12"> <!-- Left: Plan Selection & Addon Toggles (7 Cols) --> <div class="space-y-6 lg:col-span-7"> <!-- Step 1: Base Tier Cards --> <div class="space-y-3"> <div class="text-muted-foreground text-xs font-bold tracking-wider uppercase"> Step 1: Choose Base Core Tier </div> <div class="grid grid-cols-1 gap-3 sm:grid-cols-3"> {#each activePlans as plan (plan.id)} <button type="button" class={cn( 'flex flex-col justify-between rounded-xl border p-4 text-left transition-colors', selectedPlanId === plan.id ? 'border-primary bg-primary/5 ring-primary/20 shadow-xs ring-1' : 'border-border bg-card hover:bg-muted/40 text-muted-foreground hover:text-foreground', )} onclick={() => (selectedPlanId = plan.id)} > <div> <div class="text-foreground text-xs font-bold">{plan.name}</div> <div class="text-foreground mt-1 font-mono text-lg font-bold"> ${plan.monthlyPrice}<span class="text-muted-foreground text-xs font-normal">/mo</span> </div> </div> <div class="text-muted-foreground mt-2 text-xs">Includes {plan.includedSeats} engineer seats</div> </button> {/each} </div> </div> <!-- Step 2: Seat Allocation Slider --> <Card class="border-border bg-card/60 shadow-2xs"> <CardContent class="space-y-2.5 p-4"> <div class="flex items-center justify-between text-xs"> <span class="text-foreground font-bold">Engineer Team Seats</span> <span class="text-foreground font-mono text-sm font-bold" >{seatCount} seats (${extraSeatCost}/mo extra)</span > </div> <input value={seatCount} oninput={(e) => (seatCount = e.currentTarget.valueAsNumber)} type="range" min="2" max="50" step="1" class="accent-primary w-full cursor-pointer" /> <div class="text-muted-foreground flex justify-between font-mono text-xs"> <span>2 seats</span> <span>50 seats</span> </div> </CardContent> </Card> <!-- Step 3: Enterprise Add-ons Checklist --> <div class="space-y-3"> <div class="text-muted-foreground text-xs font-bold tracking-wider uppercase"> Step 3: Select Modular Capabilities </div> <div class="grid grid-cols-1 gap-2.5"> {#each activeAddons as addon (addon.id)} {@const selected = selectedAddonIds.includes(addon.id)} <button type="button" class={cn( 'flex items-center justify-between rounded-xl border p-3.5 text-left transition-colors', selected ? 'border-primary/60 bg-primary/5 shadow-2xs' : 'border-border bg-card hover:bg-muted/30 text-muted-foreground hover:text-foreground', )} onclick={() => toggleAddon(addon.id)} > <div class="flex min-w-0 items-center gap-3"> <div class={cn( 'flex size-5 shrink-0 items-center justify-center rounded border transition-colors', selected ? 'border-primary bg-primary text-primary-foreground' : 'border-border bg-background', )} > {#if selected} <Check class="size-3.5" /> {/if} </div> <div class="min-w-0 space-y-0.5"> <div class="flex items-center gap-2"> <span class="text-foreground text-xs font-bold">{addon.name}</span> <Badge variant="outline" class="border-border text-muted-foreground text-xs"> {addon.category} </Badge> </div> <div class="text-muted-foreground truncate text-xs">{addon.description}</div> </div> </div> <div class="text-foreground shrink-0 pl-2 font-mono text-xs font-bold"> +${addon.monthlyPrice}<span class="text-muted-foreground text-xs font-normal">/mo</span> </div> </button> {/each} </div> </div> </div> <!-- Right: Real-time Invoice Estimate Card (5 Cols) --> <div class="lg:col-span-5"> <Card class="border-border bg-card/90 sticky top-8 shadow-md backdrop-blur-xs"> <CardContent class="space-y-6 p-6"> <div class="border-border flex items-center justify-between border-b pb-3"> <div class="flex items-center gap-2"> <CreditCard class="text-primary size-4" /> <span class="text-foreground text-sm font-semibold">Estimated Monthly Invoice</span> </div> <Badge variant="outline" class="border-border text-primary font-mono text-xs"> {isAnnual ? 'Annualized' : 'Monthly'} </Badge> </div> <!-- Price Breakdown List --> <div class="space-y-3 text-xs"> <div class="text-muted-foreground flex justify-between"> <span>{selectedPlan.name}</span> <span class="text-foreground font-mono">${selectedPlan.monthlyPrice}.00</span> </div> {#if extraSeats > 0} <div class="text-muted-foreground flex justify-between"> <span>Extra Seats ({extraSeats} × $15)</span> <span class="text-foreground font-mono">${extraSeatCost}.00</span> </div> {/if} {#each selectedAddons as addon (addon.id)} <div class="text-muted-foreground flex justify-between"> <span class="truncate pr-2">{addon.name}</span> <span class="text-foreground font-mono">${addon.monthlyPrice}.00</span> </div> {/each} {#if isAnnual} <div class="border-border/60 text-success flex justify-between border-t pt-2 font-medium"> <span>Annual Billing Discount (20%)</span> <span class="font-mono">−${monthlySubtotal - finalMonthlyRate}.00</span> </div> {/if} </div> <!-- Total Sum Band --> <div class="border-border bg-muted/40 space-y-1 rounded-lg border p-4"> <div class="text-muted-foreground text-xs font-medium">Net Monthly Investment</div> <div class="flex items-baseline gap-1.5"> <span class="text-foreground font-mono text-3xl font-bold tracking-tight"> ${finalMonthlyRate} </span> <span class="text-muted-foreground font-mono text-xs">/ month</span> </div> {#if isAnnual} <div class="text-success text-xs font-medium"> Billed annually (${finalMonthlyRate * 12}/yr • Save ${annualSavings}/yr) </div> {/if} </div> <Button class="w-full gap-2 shadow-xs" size="lg"> <span>Start 14-Day Free Evaluation</span> <ArrowRight class="size-4" /> </Button> </CardContent> </Card> </div> </div> </div> </section>src/lib/components/blocks/pricing/PricingFeatureTierSlider.svelte8.4 kB
<script lang="ts"> import { ArrowRight, CircleCheckBig, Sliders } from '@lucide/svelte' import { Badge } from '$lib/components/ui/badge' import { Button } from '$lib/components/ui/button' import { Card, CardContent } from '$lib/components/ui/card' import { cn } from '$lib/utils' interface PricingFeatureTierSliderProps { title?: string description?: string class?: string } let { title = 'Predictable usage pricing with dynamic linear scale.', description = 'Slide to your estimated monthly active users or edge invocations to calculate your exact monthly investment.', class: className, }: PricingFeatureTierSliderProps = $props() // Slider value from 0 to 100 let sliderIndex = $state(2) let isAnnual = $state(true) const TIERS = [ { label: '10K MAU', mauCount: '10,000', basePriceMonthly: 19, tierName: 'Starter Hobbyist', features: [ '10,000 Monthly Users', '2 Dedicated Edge Locations', 'Community Discord SLA', 'Standard 1-Day Log Retention', ], }, { label: '50K MAU', mauCount: '50,000', basePriceMonthly: 49, tierName: 'Pro Creator', features: [ '50,000 Monthly Users', '12 Global Anycast Edges', 'Next-Business-Day Support SLA', '7-Day Immutable Log Retention', ], }, { label: '250K MAU', mauCount: '250,000', basePriceMonthly: 149, tierName: 'Growth Scale', features: [ '250,000 Monthly Users', 'All 36 Global Edge Regions', '4-Hour Priority Engineering SLA', '30-Day SOC2 Audit Logs', ], }, { label: '1M MAU', mauCount: '1,000,000', basePriceMonthly: 399, tierName: 'Enterprise Core', features: [ '1,000,000 Monthly Users', 'Dedicated VPC & Multi-Region Mesh', '15-Minute Critical Incident SLA', '365-Day Cold Storage Logs', ], }, { label: '5M+ MAU', mauCount: '5,000,000+', basePriceMonthly: 899, tierName: 'Hyperscale Cluster', features: [ '5,000,000+ Monthly Users', 'Custom Bare-Metal Cloud Partitions', 'Dedicated Solutions Architect', 'Custom Security Review & BAA', ], }, ] const currentTier = $derived(TIERS[sliderIndex]) const effectivePrice = $derived(isAnnual ? Math.round(currentTier.basePriceMonthly * 0.8) : currentTier.basePriceMonthly) const annualSavings = $derived((currentTier.basePriceMonthly - effectivePrice) * 12) </script> <section data-slot="pricing-feature-tier-slider" class={cn('bg-background relative overflow-hidden py-16 sm:py-24', className)} > <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> <!-- Section Header --> <div class="mx-auto max-w-3xl space-y-4 text-center"> <a href="#scale-pricing" class="group border-border/80 bg-secondary/60 hover:bg-secondary text-foreground inline-flex items-center gap-2 rounded-full border px-3.5 py-1 text-xs font-medium shadow-2xs transition-colors" > <Sliders class="text-primary size-3.5" /> <span>Continuous Scale Synthesizer</span> <ArrowRight class="text-muted-foreground size-3 transition-transform group-hover:translate-x-0.5" /> </a> <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl"> {title} </h2> <p class="text-muted-foreground text-base sm:text-lg"> {description} </p> <!-- Cadence Switcher --> <div class="flex items-center justify-center gap-3 pt-2"> <span class={cn('text-xs font-medium', !isAnnual ? 'text-foreground font-semibold' : 'text-muted-foreground')}> Monthly </span> <button type="button" aria-label="Toggle annual billing" class={cn( 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out', isAnnual ? 'bg-primary' : 'bg-muted', )} onclick={() => (isAnnual = !isAnnual)} > <span class={cn( 'bg-background pointer-events-none inline-block size-5 transform rounded-full shadow-lg ring-0 transition duration-200 ease-in-out', isAnnual ? 'translate-x-5' : 'translate-x-0', )} ></span> </button> <span class={cn( 'flex items-center gap-1.5 text-xs font-medium', isAnnual ? 'text-foreground font-semibold' : 'text-muted-foreground', )} > <span>Annual Billing</span> <Badge variant="outline" class="border-success/30 bg-success/10 text-success text-xs"> Save 20% </Badge> </span> </div> </div> <!-- Pricing Slider Main Card --> <div class="mx-auto mt-12 max-w-4xl"> <Card class="border-border bg-card overflow-hidden shadow-sm"> <CardContent class="space-y-8 p-8"> <!-- Scale Indicator Bar --> <div class="border-border flex flex-wrap items-center justify-between gap-4 border-b pb-6"> <div class="space-y-1"> <span class="text-muted-foreground text-xs font-bold tracking-wider uppercase">Calculated Tier Plan</span> <h3 class="text-foreground text-2xl font-bold">{currentTier.tierName}</h3> <div class="text-muted-foreground text-xs"> Engineered for {currentTier.mauCount} active user sessions </div> </div> <!-- Price Box --> <div class="text-right"> <div class="flex items-baseline justify-end gap-1"> <span class="text-foreground font-mono text-4xl font-bold">${effectivePrice}</span> <span class="text-muted-foreground font-mono text-xs">/ month</span> </div> {#if isAnnual} <div class="text-success font-mono text-xs font-medium"> Save ${annualSavings}/yr on annual </div> {/if} </div> </div> <!-- Interactive Stepped Range Slider --> <div class="space-y-3"> <div class="text-foreground flex items-center justify-between text-xs font-bold"> <span>Monthly Traffic Volume</span> <span class="text-primary font-mono font-bold">{currentTier.mauCount} Users</span> </div> <input value={sliderIndex} oninput={(e) => (sliderIndex = e.currentTarget.valueAsNumber)} type="range" min="0" max="4" step="1" class="accent-primary h-2 w-full cursor-pointer" /> <!-- Slider Step Labels --> <div class="text-muted-foreground flex justify-between font-mono text-xs"> {#each TIERS as t, idx (idx)} <!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions: labels mirror the keyboard-operable range input above --> <span class={cn( 'hover:text-foreground cursor-pointer transition-colors', sliderIndex === idx ? 'text-primary font-bold' : '', )} onclick={() => (sliderIndex = idx)} > {t.label} </span> {/each} </div> </div> <!-- Features Checklist Grid for Current Tier --> <div class="border-border space-y-3 border-t pt-4"> <span class="text-muted-foreground text-xs font-bold tracking-wider uppercase">Guaranteed Tier Deliverables</span> <div class="grid grid-cols-1 gap-2.5 sm:grid-cols-2"> {#each currentTier.features as feat, fIdx (fIdx)} <div class="text-foreground flex items-center gap-2 text-xs"> <CircleCheckBig class="text-success size-4 shrink-0" /> <span>{feat}</span> </div> {/each} </div> </div> <!-- Action Button --> <div class="pt-2"> <Button size="lg" class="w-full gap-2 shadow-xs"> <span>Deploy with {currentTier.tierName}</span> <ArrowRight class="size-4" /> </Button> </div> </CardContent> </Card> </div> </div> </section>src/lib/components/blocks/pricing/PricingMatrixGrouped.svelte5 kB
<script lang="ts"> import { Check, ChevronDown, Minus } from '@lucide/svelte' import { cn } from '$lib/utils' import { Badge } from '$lib/components/ui/badge' import { Button } from '$lib/components/ui/button' import { Card, CardContent } from '$lib/components/ui/card' import { Separator } from '$lib/components/ui/separator' import { Table, TableBody, TableCell, TableRow } from '$lib/components/ui/table' const plans = ['Team', 'Business', 'Enterprise'] const groups = [ { id: 'modelling', label: 'Modelling', summary: 'Definitions, versioning, and review', rows: [ { feature: 'Certified definitions', values: ['50', '500', 'Unlimited'] }, { feature: 'Version history', values: ['30 days', 'Unlimited', 'Unlimited'] }, { feature: 'Required review', values: [false, true, true] }, { feature: 'Period locking', values: [false, true, true] }, ], }, { id: 'access', label: 'Access & identity', summary: 'Who sees which rows', rows: [ { feature: 'SSO (SAML)', values: [true, true, true] }, { feature: 'SCIM provisioning', values: [false, true, true] }, { feature: 'Row-level scope', values: [false, true, true] }, { feature: 'Customer-managed keys', values: [false, false, true] }, ], }, { id: 'ops', label: 'Operations', summary: 'Cost, alerting, and support', rows: [ { feature: 'Query budgets', values: [false, true, true] }, { feature: 'Drift alerting', values: [false, true, true] }, { feature: 'Audit log export', values: [false, '90 days', 'Unlimited'] }, { feature: 'Named support engineer', values: [false, false, true] }, ], }, ] // Opens on the first group only: a fully expanded matrix is the thing readers // bounce off, and every group is one click from open. let open = $state<string[]>([groups[0].id]) function toggle(id: string) { open = open.includes(id) ? open.filter((entry) => entry !== id) : [...open, id] } </script> <section data-slot="pricing-matrix-grouped" class="bg-background"> <div class="mx-auto max-w-4xl px-6 py-20 lg:py-28"> <Badge variant="secondary">Compare plans</Badge> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Grouped, so it stays readable</h2> <Card class="mt-8"> <CardContent class="p-0"> <div class="text-muted-foreground grid grid-cols-[1fr_repeat(3,5rem)] gap-2 px-4 py-3 text-xs font-medium sm:grid-cols-[1fr_repeat(3,7rem)]" > <span>Capability</span> {#each plans as plan (plan)} <span class="text-center">{plan}</span> {/each} </div> <Separator /> {#each groups as group (group.id)} {@const isOpen = open.includes(group.id)} <div> <button type="button" class="hover:bg-muted focus-visible:ring-ring flex w-full items-center gap-3 px-4 py-3 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none" aria-expanded={isOpen} aria-controls={`group-${group.id}`} onclick={() => toggle(group.id)} > <ChevronDown class={cn('text-muted-foreground size-4 shrink-0 transition-transform', !isOpen && '-rotate-90')} aria-hidden="true" /> <span class="min-w-0"> <span class="block text-sm font-medium">{group.label}</span> <span class="text-muted-foreground block text-xs">{group.summary}</span> </span> <span class="text-muted-foreground ml-auto shrink-0 font-mono text-xs">{group.rows.length}</span> </button> <div id={`group-${group.id}`} class:hidden={!isOpen}> <Table> <TableBody> {#each group.rows as row (row.feature)} <TableRow> <TableCell class="pl-11 text-sm">{row.feature}</TableCell> {#each row.values as value, index (index)} <TableCell class="w-20 text-center sm:w-28"> {#if value === true} <Check class="text-success mx-auto size-4" aria-hidden="true" /> <span class="sr-only">Included</span> {:else if value === false} <Minus class="text-muted-foreground/50 mx-auto size-4" aria-hidden="true" /> <span class="sr-only">Not included</span> {:else} <span class="font-mono text-xs">{value}</span> {/if} </TableCell> {/each} </TableRow> {/each} </TableBody> </Table> </div> <Separator /> </div> {/each} </CardContent> </Card> <Button class="mt-6">Start on Business</Button> </div> </section>src/lib/components/blocks/pricing/PricingMatrixPlanCards.svelte3.5 kB
<script lang="ts"> import { Check, Minus } from '@lucide/svelte' import { cn } from '$lib/utils' import { Badge } from '$lib/components/ui/badge' import { Button } from '$lib/components/ui/button' import { Card, CardContent } from '$lib/components/ui/card' import { Separator } from '$lib/components/ui/separator' // One capability list, three plans referencing it by index — a capability can // never appear on one card and silently vanish from another. const capabilities = [ 'Certified metric definitions', 'Connected warehouses', 'Query budget per team', 'Row-level access control', 'SCIM provisioning', 'Audit log export', 'Embedded dashboards', 'Data residency choice', 'Customer-managed keys', 'Named support engineer', ] const plans = [ { name: 'Team', price: '$0', cadence: 'while in beta', included: [0, 1], note: 'For one team proving it out.' }, { name: 'Business', price: '$1,400', cadence: 'per month', included: [0, 1, 2, 3, 4, 5, 6], note: 'For finance and analytics running together.', featured: true, }, { name: 'Enterprise', price: 'Custom', cadence: 'annual', included: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], note: 'For regulated estates and multi-region.', }, ] </script> <section data-slot="pricing-matrix-plan-cards" class="bg-background"> <div class="mx-auto max-w-6xl px-6 py-20 lg:py-28"> <div class="max-w-2xl"> <Badge variant="secondary">Plans</Badge> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">What each plan actually includes</h2> <p class="text-muted-foreground mt-3 text-lg"> The same list on every card, with what is missing shown rather than omitted. </p> </div> <div class="mt-10 grid gap-4 lg:grid-cols-3"> {#each plans as plan (plan.name)} <Card class={plan.featured ? 'border-primary' : undefined}> <CardContent class="flex h-full flex-col p-6"> <div class="flex items-center justify-between gap-3"> <p class="text-sm font-semibold">{plan.name}</p> {#if plan.featured} <Badge variant="secondary">Most chosen</Badge> {/if} </div> <p class="font-display mt-4 text-3xl font-bold tracking-tight">{plan.price}</p> <p class="text-muted-foreground mt-1 text-xs">{plan.cadence}</p> <p class="text-muted-foreground mt-3 text-sm leading-relaxed">{plan.note}</p> <Separator class="my-5" /> <ul class="space-y-2.5"> {#each capabilities as capability, index (capability)} {@const included = plan.included.includes(index)} <li class={cn('flex items-start gap-2.5 text-sm', !included && 'text-muted-foreground/60')}> {#if included} <Check class="text-success mt-0.5 size-4 shrink-0" aria-hidden="true" /> {:else} <Minus class="text-muted-foreground/40 mt-0.5 size-4 shrink-0" aria-hidden="true" /> {/if} <span>{capability}</span> </li> {/each} </ul> <Button variant={plan.featured ? 'default' : 'outline'} class="mt-auto pt-0 [&]:mt-8"> {plan.name === 'Enterprise' ? 'Talk to us' : `Start on ${plan.name}`} </Button> </CardContent> </Card> {/each} </div> </div> </section>src/lib/components/blocks/pricing/PricingMatrixStickyHeader.svelte3.6 kB
<script lang="ts"> import { Check, Minus } from '@lucide/svelte' import { Badge } from '$lib/components/ui/badge' import { Button } from '$lib/components/ui/button' import { Card, CardContent } from '$lib/components/ui/card' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table' const plans = ['Team', 'Business', 'Enterprise'] // Cells are true / false / a string, so a plan can state a limit rather than // only claiming a feature exists. const rows = [ { feature: 'Certified metric definitions', values: ['50', '500', 'Unlimited'] }, { feature: 'Connected warehouses', values: ['1', '3', 'Unlimited'] }, { feature: 'Query budget per team', values: [false, true, true] }, { feature: 'Row-level access control', values: [false, true, true] }, { feature: 'SCIM provisioning', values: [false, true, true] }, { feature: 'Audit log export', values: [false, '90 days', 'Unlimited'] }, { feature: 'Period locking', values: [false, true, true] }, { feature: 'Embedded dashboards', values: [false, true, true] }, { feature: 'Data residency choice', values: [false, false, true] }, { feature: 'Customer-managed keys', values: [false, false, true] }, { feature: 'Private link', values: [false, false, true] }, { feature: 'Named support engineer', values: [false, false, true] }, ] </script> <section data-slot="pricing-matrix-sticky-header" class="bg-background"> <div class="mx-auto max-w-4xl px-6 py-20 lg:py-28"> <Badge variant="secondary">Compare plans</Badge> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Every line, no asterisks</h2> <p class="text-muted-foreground mt-3 text-lg"> Limits differ between plans. Where a plan caps something, the cap is written in the cell. </p> <Card class="mt-8"> <CardContent class="p-0"> <!-- The header pins with CSS alone. Thirty rows down, a checkmark is still attributable to a column without scrolling back up. --> <div class="max-h-[28rem] overflow-auto"> <Table> <TableHeader class="bg-card sticky top-0 z-10"> <TableRow> <TableHead class="bg-card">Capability</TableHead> {#each plans as plan (plan)} <TableHead class="bg-card text-center">{plan}</TableHead> {/each} </TableRow> </TableHeader> <TableBody> {#each rows as row (row.feature)} <TableRow> <TableCell class="font-medium">{row.feature}</TableCell> {#each row.values as value, index (index)} <TableCell class="text-center"> {#if value === true} <Check class="text-success mx-auto size-4" aria-hidden="true" /> <span class="sr-only">Included</span> {:else if value === false} <Minus class="text-muted-foreground/50 mx-auto size-4" aria-hidden="true" /> <span class="sr-only">Not included</span> {:else} <span class="font-mono text-xs">{value}</span> {/if} </TableCell> {/each} </TableRow> {/each} </TableBody> </Table> </div> </CardContent> </Card> <div class="mt-6 flex flex-wrap gap-3"> <Button>Start on Team</Button> <Button variant="outline">Talk about Enterprise</Button> </div> </div> </section>src/lib/components/blocks/pricing/PricingSinglePlan.svelte3.3 kB
<script lang="ts"> import { ArrowRight, Check, X } from '@lucide/svelte' import { Badge } from '$lib/components/ui/badge' import { Button } from '$lib/components/ui/button' import { Card, CardContent } from '$lib/components/ui/card' import { Separator } from '$lib/components/ui/separator' const included = [ 'Unlimited certified metric definitions', 'Unlimited viewers and editors', 'Row-level access from your identity provider', 'Query budgets enforced per team', 'Audit log export, 7-year retention', 'SOC 2 report under NDA', ] // Naming exclusions is what makes a single plan credible. A list of only // inclusions invites the reader to assume the gap is hidden somewhere. const excluded = [ 'On-premise deployment behind your firewall', 'Custom SLAs below 99.9%', 'Professional services beyond the five-week rollout', ] </script> <section data-slot="pricing-single-plan" class="bg-background"> <div class="mx-auto max-w-3xl px-6 py-20 lg:py-28"> <div class="text-center"> <Badge variant="secondary">Pricing</Badge> <h2 class="mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-4xl">One plan, one number</h2> <p class="text-muted-foreground mt-3 text-lg"> No seat maths and no feature gates. The only thing that scales is query volume. </p> </div> <Card class="mt-10"> <CardContent class="p-8"> <div class="flex flex-wrap items-baseline gap-3"> <span class="font-display text-4xl font-bold tracking-tight">£1,400</span> <span class="text-muted-foreground">per month, billed annually</span> </div> <p class="text-muted-foreground mt-2 text-sm"> Includes 2 million queries a month. Beyond that it is £0.0004 per query, capped by your own budgets. </p> <Button size="lg" class="mt-6 w-full sm:w-auto"> Start the 30-day trial <ArrowRight class="ml-2 size-4" aria-hidden="true" /> </Button> <Separator class="my-8" /> <p class="text-muted-foreground font-mono text-xs tracking-[0.14em] uppercase">Included</p> <ul class="mt-3 grid gap-2.5 sm:grid-cols-2"> {#each included as item (item)} <li class="flex items-start gap-2.5 text-sm"> <Check class="text-success mt-0.5 size-4 shrink-0" aria-hidden="true" /> <span>{item}</span> </li> {/each} </ul> <Separator class="my-6" /> <p class="text-muted-foreground font-mono text-xs tracking-[0.14em] uppercase">Not included</p> <ul class="mt-3 space-y-2.5"> {#each excluded as item (item)} <li class="flex items-start gap-2.5 text-sm"> <X class="text-muted-foreground/50 mt-0.5 size-4 shrink-0" aria-hidden="true" /> <span class="text-muted-foreground">{item}</span> </li> {/each} </ul> <Separator class="my-6" /> <p class="text-muted-foreground text-sm leading-relaxed"> Past roughly 20 million queries a month, or if any of the exclusions above are hard requirements, a conversation will get you a better answer than this page can. </p> <Button variant="outline" class="mt-4">Talk to us instead</Button> </CardContent> </Card> </div> </section>src/lib/components/blocks/pricing/PricingTierComparisonMatrix.svelte11.5 kB
<script lang="ts"> import { ArrowRight, Check, Minus, ShieldCheck } from '@lucide/svelte' import { Badge } from '$lib/components/ui/badge' import { Button } from '$lib/components/ui/button' import { Card } from '$lib/components/ui/card' import { cn } from '$lib/utils' type BillingCycle = 'monthly' | 'annual' let billing = $state<BillingCycle>('annual') interface Plan { id: string name: string description: string monthlyPrice: number annualPrice: number highlight: boolean badge?: string ctaText: string ctaVariant: 'default' | 'outline' | 'secondary' } const plans: Plan[] = [ { id: 'community', name: 'Community OSS', description: 'For indie hackers and developers building open source products.', monthlyPrice: 0, annualPrice: 0, highlight: false, ctaText: 'Start Building Free', ctaVariant: 'outline', }, { id: 'pro', name: 'Pro Team', description: 'For growing startup engineering teams that require high velocity.', monthlyPrice: 29, annualPrice: 24, highlight: true, badge: 'Most Popular', ctaText: 'Claim Pro License', ctaVariant: 'default', }, { id: 'enterprise', name: 'Enterprise Scale', description: 'Dedicated registry syncing, SSO, SOC2 compliance audits, and SLA.', monthlyPrice: 119, annualPrice: 99, highlight: false, ctaText: 'Talk to Sales', ctaVariant: 'outline', }, ] interface FeatureComparisonRow { category: string features: { name: string tooltip: string community: boolean | string pro: boolean | string enterprise: boolean | string }[] } const comparisonData: FeatureComparisonRow[] = [ { category: 'Registry & Core Primitives', features: [ { name: 'Full AST Component Source Access', tooltip: 'Raw SFC and TSX source files copied directly into your repository.', community: true, pro: true, enterprise: true, }, { name: 'Dual-Framework Parity (Vue + React)', tooltip: 'Identical DOM semantics and CVA tokens across both ecosystems.', community: true, pro: true, enterprise: true, }, { name: 'Curated Marketing & SaaS Blocks', tooltip: 'Access to 450+ production-grade unbundled layout blocks.', community: '100+ Blocks', pro: 'All 450+ Blocks', enterprise: 'All Blocks + Custom', }, { name: 'Tailwind CSS v4 OKLCH Token System', tooltip: 'Hardware-calibrated color spaces and spring curves.', community: true, pro: true, enterprise: true, }, ], }, { category: 'Enterprise & Security Compliance', features: [ { name: 'Private Registry Mirroring', tooltip: 'Host your organization’s customized internal registry behind a firewall.', community: false, pro: '1 Private Repo', enterprise: 'Unlimited Private Hubs', }, { name: 'SOC2 & ISO 27001 Audit Packs', tooltip: 'Pre-certified security documentation and architecture proofs.', community: false, pro: false, enterprise: true, }, { name: 'Guaranteed 99.99% Registry CDN SLA', tooltip: 'Global multi-region edge distribution uptime guarantee.', community: false, pro: '99.9% SLA', enterprise: '99.99% High Availability', }, { name: 'Dedicated Design Engineering Support', tooltip: 'Direct Slack / Discord channel with core design system maintainers.', community: false, pro: 'Priority Email', enterprise: 'Dedicated Slack Channel', }, ], }, ] </script> <section data-slot="pricing-tier-comparison-matrix" class="bg-background relative overflow-hidden px-4 py-16 sm:px-6 sm:py-24 lg:px-8" > <div class="mx-auto max-w-7xl space-y-16"> <!-- Header --> <div class="mx-auto max-w-3xl space-y-4 text-center"> <Badge variant="secondary" class="gap-1.5 px-3 py-1 font-mono text-xs shadow-xs"> <ShieldCheck class="text-primary size-3.5" /> Predictable Pricing </Badge> <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl"> Zero seat taxes. Own your source code forever. </h2> <p class="text-muted-foreground text-base"> Choose the tier that fits your engineering team's delivery scale and compliance needs. </p> <!-- Billing Toggle --> <div class="flex items-center justify-center gap-3 pt-4"> <span class="font-mono text-xs" class:text-foreground={billing === 'monthly'} class:font-bold={billing === 'monthly'} class:text-muted-foreground={billing !== 'monthly'} > Monthly </span> <div class="border-border bg-card relative flex items-center rounded-full border p-1"> <button type="button" class={cn( 'relative z-10 rounded-full px-3 py-1 font-mono text-xs transition-colors', billing === 'monthly' ? 'bg-primary text-primary-foreground font-semibold shadow-xs' : 'text-muted-foreground', )} onclick={() => (billing = 'monthly')} > Monthly </button> <button type="button" class={cn( 'relative z-10 flex items-center gap-1.5 rounded-full px-3 py-1 font-mono text-xs transition-colors', billing === 'annual' ? 'bg-primary text-primary-foreground font-semibold shadow-xs' : 'text-muted-foreground', )} onclick={() => (billing = 'annual')} > <span>Annual</span> <span class="bg-success rounded-full px-1.5 py-0.5 text-xs font-bold text-white">Save 20%</span> </button> </div> </div> </div> <!-- Pricing Plan Cards Grid (3 Columns) --> <div class="grid grid-cols-1 items-stretch gap-6 md:grid-cols-3"> {#each plans as plan (plan.id)} <Card class={cn( 'border-border bg-card relative flex flex-col justify-between space-y-6 rounded-2xl p-6 text-left shadow-xl transition-colors sm:p-8', plan.highlight ? 'border-primary/80 ring-primary/20 scale-[1.02] shadow-sm ring-2' : 'hover:border-border/80', )} > <!-- Top Badge --> {#if plan.badge} <div class="absolute -top-3 left-1/2 -translate-x-1/2"> <Badge class="px-3 py-0.5 font-mono text-xs tracking-wider uppercase shadow-md"> {plan.badge} </Badge> </div> {/if} <div class="space-y-4"> <div> <h3 class="text-foreground font-mono text-xl font-bold">{plan.name}</h3> <p class="text-muted-foreground mt-1 min-h-[36px] text-xs">{plan.description}</p> </div> <!-- Price display --> <div class="flex items-baseline gap-1.5 font-mono"> <span class="text-foreground text-4xl font-bold"> ${billing === 'annual' ? plan.annualPrice : plan.monthlyPrice} </span> <span class="text-muted-foreground text-xs">/ month</span> </div> <p class="text-muted-foreground font-mono text-xs"> {billing === 'annual' && plan.annualPrice > 0 ? 'Billed annually ($' + plan.annualPrice * 12 + '/yr)' : 'Billed monthly'} </p> </div> <Button variant={plan.ctaVariant} class="h-10 w-full gap-1.5 font-mono text-xs shadow-xs"> <span>{plan.ctaText}</span> <ArrowRight class="size-3.5" /> </Button> </Card> {/each} </div> <!-- Deep Feature Comparison Matrix Table --> <div class="space-y-6"> <div class="text-center"> <h3 class="text-foreground font-mono text-xl font-bold">Detailed Feature Comparison</h3> <p class="text-muted-foreground mt-1 text-xs">Full granular matrix of capabilities and entitlements.</p> </div> <Card class="border-border bg-card overflow-hidden rounded-2xl text-left shadow-sm"> <div class="overflow-x-auto"> <table class="w-full border-collapse text-left text-xs"> <thead> <tr class="border-border bg-muted/40 text-muted-foreground border-b font-mono"> <th class="w-1/2 p-4 font-semibold">Capability</th> <th class="p-4 text-center font-semibold">Community</th> <th class="text-primary p-4 text-center font-bold font-semibold">Pro Team</th> <th class="p-4 text-center font-semibold">Enterprise</th> </tr> </thead> <tbody> {#each comparisonData as section, sIdx (sIdx)} <tr class="bg-muted/20 border-border/80 border-b"> <td colspan="4" class="text-muted-foreground p-3 px-4 font-mono text-xs font-bold tracking-wider uppercase" > {section.category} </td> </tr> {#each section.features as row, rIdx (rIdx)} <tr class="border-border/60 hover:bg-muted/10 border-b transition-colors"> <td class="p-4"> <div class="text-foreground font-medium">{row.name}</div> <div class="text-muted-foreground mt-0.5 text-xs">{row.tooltip}</div> </td> <td class="p-4 text-center font-mono"> {#if typeof row.community === 'boolean'} {#if row.community} <Check class="text-success mx-auto size-4" /> {:else} <Minus class="text-muted-foreground/40 mx-auto size-4" /> {/if} {:else} <span class="text-muted-foreground">{row.community}</span> {/if} </td> <td class="p-4 text-center font-mono font-semibold"> {#if typeof row.pro === 'boolean'} {#if row.pro} <Check class="text-success mx-auto size-4" /> {:else} <Minus class="text-muted-foreground/40 mx-auto size-4" /> {/if} {:else} <span class="text-primary">{row.pro}</span> {/if} </td> <td class="p-4 text-center font-mono"> {#if typeof row.enterprise === 'boolean'} {#if row.enterprise} <Check class="text-success mx-auto size-4" /> {:else} <Minus class="text-muted-foreground/40 mx-auto size-4" /> {/if} {:else} <span class="text-foreground font-semibold">{row.enterprise}</span> {/if} </td> </tr> {/each} {/each} </tbody> </table> </div> </Card> </div> </div> </section>src/lib/components/blocks/pricing/PricingUsageCalculatorSlider.svelte8.3 kB
<script lang="ts"> import { ArrowRight, Calculator, Globe, Server, Users } from '@lucide/svelte' import { Badge } from '$lib/components/ui/badge' import { Button } from '$lib/components/ui/button' import { Card } from '$lib/components/ui/card' // Interactive Sliders let mau = $state(100000) // 10k to 5M let qps = $state(500) // 100 to 20k let edgeReplicas = $state(3) // 1 to 12 function formatNumber(num: number): string { if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M' if (num >= 1000) return (num / 1000).toFixed(0) + 'k' return num.toString() } // Preset configurations function applyPreset(preset: 'seed' | 'growth' | 'scale') { if (preset === 'seed') { mau = 25000 qps = 150 edgeReplicas = 2 } else if (preset === 'growth') { mau = 350000 qps = 2500 edgeReplicas = 5 } else { mau = 2500000 qps = 12000 edgeReplicas = 10 } } // Calculations const traditionalCost = $derived(Math.round(350 + (mau / 1000) * 1.8 + qps * 0.25)) const uipkgeCost = $derived( // Flat zero licensing + modest raw edge static bandwidth Math.round(24 + (mau / 100000) * 4), ) const monthlySavings = $derived(Math.max(0, traditionalCost - uipkgeCost)) const annualSavings = $derived(monthlySavings * 12) </script> <section data-slot="pricing-usage-calculator-slider" class="bg-background relative overflow-hidden px-4 py-16 sm:px-6 sm:py-24 lg:px-8" > <div class="mx-auto max-w-6xl space-y-12"> <!-- Section Header --> <div class="mx-auto max-w-3xl space-y-4 text-center"> <Badge variant="secondary" class="gap-1.5 px-3 py-1 font-mono text-xs shadow-xs"> <Calculator class="text-primary size-3.5" /> Interactive Infrastructure ROI Calculator </Badge> <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl"> Calculate your annual savings with unbundled architecture. </h2> <p class="text-muted-foreground text-base"> See how eliminating proprietary seat licenses and runtime SaaS wrappers cuts your front-end TCO. </p> <!-- Presets Bar --> <div class="flex flex-wrap items-center justify-center gap-2 pt-2"> <Button size="sm" variant="outline" class="font-mono text-xs" onclick={() => applyPreset('seed')}> Seed Startup (25k MAU) </Button> <Button size="sm" variant="outline" class="font-mono text-xs" onclick={() => applyPreset('growth')}> Growth Scale (350k MAU) </Button> <Button size="sm" variant="outline" class="font-mono text-xs" onclick={() => applyPreset('scale')}> Hypergrowth (2.5M MAU) </Button> </div> </div> <!-- 2-Column Split: Sliders Workbench Left (7 Cols), Savings Scorecard Right (5 Cols) --> <div class="grid grid-cols-1 items-stretch gap-8 lg:grid-cols-12"> <!-- Sliders Controls (7 Cols) --> <Card class="border-border bg-card/95 flex flex-col justify-between space-y-6 rounded-2xl p-6 text-left shadow-xl sm:p-8 lg:col-span-7" > <div class="space-y-6"> <div class="border-border flex items-center justify-between border-b pb-4"> <h3 class="text-foreground font-mono text-sm font-bold">Traffic & Telemetry Inputs</h3> <span class="text-muted-foreground font-mono text-xs">Dynamic Projection</span> </div> <!-- Slider 1: MAU --> <div class="space-y-2"> <div class="flex items-center justify-between font-mono text-xs"> <span class="text-muted-foreground flex items-center gap-1.5"> <Users class="text-primary size-3.5" /> Monthly Active Users (MAU) </span> <span class="text-foreground text-sm font-bold">{formatNumber(mau)} users</span> </div> <input value={mau} oninput={(e) => (mau = e.currentTarget.valueAsNumber)} type="range" min="10000" max="5000000" step="25000" class="accent-primary bg-border h-2 w-full cursor-pointer rounded-lg" /> </div> <!-- Slider 2: QPS --> <div class="space-y-2"> <div class="flex items-center justify-between font-mono text-xs"> <span class="text-muted-foreground flex items-center gap-1.5"> <Server class="text-primary size-3.5" /> Peak Query Throughput </span> <span class="text-foreground text-sm font-bold">{formatNumber(qps)} QPS</span> </div> <input value={qps} oninput={(e) => (qps = e.currentTarget.valueAsNumber)} type="range" min="100" max="20000" step="100" class="accent-primary bg-border h-2 w-full cursor-pointer rounded-lg" /> </div> <!-- Slider 3: Global Replicas --> <div class="space-y-2"> <div class="flex items-center justify-between font-mono text-xs"> <span class="text-muted-foreground flex items-center gap-1.5"> <Globe class="text-primary size-3.5" /> Global Edge POP Replicas </span> <span class="text-foreground text-sm font-bold">{edgeReplicas} Edge Regions</span> </div> <input value={edgeReplicas} oninput={(e) => (edgeReplicas = e.currentTarget.valueAsNumber)} type="range" min="1" max="12" step="1" class="accent-primary bg-border h-2 w-full cursor-pointer rounded-lg" /> </div> </div> <div class="border-border text-muted-foreground flex items-center justify-between border-t pt-4 font-mono text-xs" > <span>Formula: Direct AST + Raw Static Hosting</span> <span class="text-success font-semibold">✓ Zero Seat Surcharges</span> </div> </Card> <!-- ROI Cost & Savings Projection Card (5 Cols) --> <Card class="border-border bg-card/95 flex flex-col justify-between space-y-6 rounded-2xl p-6 text-left shadow-sm sm:p-8 lg:col-span-5" > <div class="space-y-6"> <Badge variant="outline" class="border-success/20 bg-success/10 text-success font-mono text-xs"> Projected Annual Net Savings </Badge> <!-- Big Stat Display --> <div class="space-y-1"> <div class="text-success font-mono text-4xl font-bold tracking-tight sm:text-5xl"> ${annualSavings.toLocaleString()} </div> <p class="text-muted-foreground font-mono text-xs"> Saved every year (${monthlySavings.toLocaleString()}/month) </p> </div> <!-- Comparative Cost Bars --> <div class="space-y-3 pt-2"> <div class="space-y-1"> <div class="flex items-center justify-between font-mono text-xs"> <span class="text-destructive font-medium">Traditional Monolith Stack:</span> <span class="text-foreground font-bold">${traditionalCost.toLocaleString()}/mo</span> </div> <div class="bg-destructive/20 h-2 overflow-hidden rounded-full"> <div class="bg-destructive h-full w-full"></div> </div> </div> <div class="space-y-1"> <div class="flex items-center justify-between font-mono text-xs"> <span class="text-success font-semibold">UIPKGE Unbundled Registry:</span> <span class="text-foreground font-bold">${uipkgeCost.toLocaleString()}/mo</span> </div> <div class="bg-success/20 h-2 overflow-hidden rounded-full"> <div class="bg-success h-full transition-[width] duration-300" style={`width: ${Math.max(5, (uipkgeCost / traditionalCost) * 100)}%;`} ></div> </div> </div> </div> </div> <Button class="mt-4 h-10 w-full gap-1.5 font-mono text-xs shadow-md"> <span>Lock In Pro Savings</span> <ArrowRight class="size-3.5" /> </Button> </Card> </div> </div> </section>
Raw manifest:https://uipkge.dev/r/svelte/pricing.json