{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "pricing-calculator",
  "title": "Pricing Calculator",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/pricing-calculator/PricingCalculator.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, ref, type HTMLAttributes } from 'vue'\nimport { ArrowRight, Check, HardDrive, Headphones, KeyRound, ShieldCheck, Users, Zap } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Separator } from '@/components/ui/separator'\nimport { Slider } from '@/components/ui/slider'\nimport { Switch } from '@/components/ui/switch'\n\ninterface Props {\n  class?: HTMLAttributes['class']\n}\n\nconst props = defineProps<Props>()\n\ninterface RequestTier {\n  label: string\n  requests: number\n  cost: number\n}\n\ninterface StorageTier {\n  label: string\n  gb: number\n  cost: number\n}\n\nconst REQUEST_TIERS: RequestTier[] = [\n  { label: '10K requests', requests: 10_000, cost: 0 },\n  { label: '50K requests', requests: 50_000, cost: 25 },\n  { label: '100K requests', requests: 100_000, cost: 60 },\n  { label: '250K requests', requests: 250_000, cost: 120 },\n  { label: '500K requests', requests: 500_000, cost: 220 },\n  { label: '1M requests', requests: 1_000_000, cost: 400 },\n  { label: '2.5M requests', requests: 2_500_000, cost: 750 },\n  { label: '5M requests', requests: 5_000_000, cost: 1200 },\n]\n\nconst STORAGE_TIERS: StorageTier[] = [\n  { label: '100 GB', gb: 100, cost: 10 },\n  { label: '250 GB', gb: 250, cost: 25 },\n  { label: '500 GB', gb: 500, cost: 45 },\n  { label: '1 TB', gb: 1_000, cost: 80 },\n  { label: '2 TB', gb: 2_000, cost: 150 },\n  { label: '5 TB', gb: 5_000, cost: 320 },\n  { label: '10 TB', gb: 10_000, cost: 580 },\n]\n\nconst SEAT_PRICE = 15\nconst SUPPORT_MANAGER_PRICE = 200\nconst CUSTOM_SLA_PRICE = 500\nconst SSO_PRICE = 100\n\nconst SEAT_MARKS = {\n  1: '1',\n  25: '25',\n  50: '50',\n  75: '75',\n  100: '100',\n}\n\nconst REQUEST_MARKS = {\n  0: '10K',\n  2: '100K',\n  4: '500K',\n  7: '5M',\n}\n\nconst STORAGE_MARKS = {\n  0: '100 GB',\n  2: '500 GB',\n  4: '2 TB',\n  6: '10 TB',\n}\n\nconst isAnnual = ref(true)\nconst seats = ref(12)\nconst requestIndex = ref(2)\nconst storageIndex = ref(2)\nconst addonSupport = ref(false)\nconst addonSla = ref(false)\nconst addonSso = ref(true)\n\nconst currentRequestTier = computed(() => REQUEST_TIERS[requestIndex.value] ?? REQUEST_TIERS[0])\nconst currentStorageTier = computed(() => STORAGE_TIERS[storageIndex.value] ?? STORAGE_TIERS[0])\n\nconst seatTier = computed(() => {\n  if (seats.value <= 5) return { label: 'Starter Team', variant: 'secondary' as const }\n  if (seats.value <= 25) return { label: 'Growth Team', variant: 'outline' as const }\n  return { label: 'Scale Team', variant: 'default' as const }\n})\n\nconst recommendedPlan = computed(() => {\n  if (addonSla.value || requestIndex.value >= 6 || storageIndex.value >= 5 || seats.value >= 30) {\n    return { name: 'Enterprise', badge: 'Enterprise Plan', baseFee: 199, variant: 'default' as const }\n  }\n  if (seats.value >= 8 || requestIndex.value >= 3 || storageIndex.value >= 3 || addonSupport.value) {\n    return { name: 'Growth', badge: 'Growth Plan', baseFee: 79, variant: 'default' as const }\n  }\n  return { name: 'Starter', badge: 'Starter Plan', baseFee: 29, variant: 'secondary' as const }\n})\n\nconst baseCost = computed(() => recommendedPlan.value.baseFee)\nconst seatsCost = computed(() => seats.value * SEAT_PRICE)\nconst requestsCost = computed(() => currentRequestTier.value.cost)\nconst storageCost = computed(() => currentStorageTier.value.cost)\nconst addonsCost = computed(() => {\n  let cost = 0\n  if (addonSupport.value) cost += SUPPORT_MANAGER_PRICE\n  if (addonSla.value) cost += CUSTOM_SLA_PRICE\n  if (addonSso.value) cost += SSO_PRICE\n  return cost\n})\n\nconst activeAddonsCount = computed(() => {\n  let count = 0\n  if (addonSupport.value) count++\n  if (addonSla.value) count++\n  if (addonSso.value) count++\n  return count\n})\n\nconst subtotalMonthly = computed(\n  () => baseCost.value + seatsCost.value + requestsCost.value + storageCost.value + addonsCost.value,\n)\n\nconst discountMultiplier = computed(() => (isAnnual.value ? 0.8 : 1.0))\nconst totalMonthly = computed(() => Math.round(subtotalMonthly.value * discountMultiplier.value))\nconst totalAnnual = computed(() => totalMonthly.value * 12)\nconst annualSavings = computed(() => Math.round(subtotalMonthly.value * 0.2 * 12))\n</script>\n\n<template>\n  <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', props.class)\">\n    <div class=\"flex flex-col items-center gap-4 text-center\">\n      <Badge variant=\"outline\" class=\"gap-1.5 px-3 py-1 text-xs font-medium tracking-wider uppercase\">\n        <ShieldCheck class=\"text-primary size-3.5\" />\n        Pricing Calculator\n      </Badge>\n      <div class=\"space-y-2\">\n        <h2 class=\"text-foreground text-2xl font-bold tracking-tight sm:text-3xl lg:text-4xl\">\n          Interactive Pricing Calculator\n        </h2>\n        <p class=\"text-muted-foreground mx-auto max-w-2xl text-sm sm:text-base\">\n          Calculate your exact monthly or annual investment based on usage.\n        </p>\n      </div>\n\n      <div class=\"border-border bg-card mt-2 inline-flex items-center gap-3 rounded-full border px-4 py-2 shadow-xs\">\n        <span\n          :class=\"\n            cn(\n              'text-xs font-medium transition-colors',\n              !isAnnual ? 'text-foreground font-semibold' : 'text-muted-foreground',\n            )\n          \"\n        >\n          Monthly\n        </span>\n        <Switch :model-value=\"isAnnual\" @update:model-value=\"(val) => (isAnnual = val)\" />\n        <span\n          :class=\"\n            cn(\n              'text-xs font-medium transition-colors',\n              isAnnual ? 'text-foreground font-semibold' : 'text-muted-foreground',\n            )\n          \"\n        >\n          Pay Annually\n        </span>\n        <Badge variant=\"secondary\" class=\"bg-primary/10 text-primary border-primary/20 text-xs font-semibold\">\n          Save 20%\n        </Badge>\n      </div>\n    </div>\n\n    <div class=\"grid grid-cols-1 gap-8 lg:grid-cols-12 lg:items-start\">\n      <div class=\"space-y-6 lg:col-span-7\">\n        <Card>\n          <CardHeader>\n            <CardTitle>Capacity &amp; Scale</CardTitle>\n            <CardDescription>Configure team seats, API request throughput, and dedicated storage.</CardDescription>\n          </CardHeader>\n          <CardContent class=\"space-y-8\">\n            <div class=\"space-y-4\">\n              <div class=\"flex items-center justify-between\">\n                <div class=\"flex items-center gap-2.5\">\n                  <div\n                    class=\"bg-primary/10 text-primary border-primary/20 flex size-8 shrink-0 items-center justify-center rounded-md border\"\n                  >\n                    <Users class=\"size-4\" />\n                  </div>\n                  <div>\n                    <span class=\"text-foreground text-sm font-medium\">Team Seats</span>\n                    <Badge :variant=\"seatTier.variant\" class=\"ml-2 text-xs font-normal\">\n                      {{ seatTier.label }}\n                    </Badge>\n                  </div>\n                </div>\n                <div class=\"text-right\">\n                  <span class=\"text-foreground text-base font-semibold tabular-nums\">{{ seats }}</span>\n                  <span class=\"text-muted-foreground text-xs\"> seats (${{ seats * SEAT_PRICE }}/mo)</span>\n                </div>\n              </div>\n              <div class=\"pb-6\">\n                <Slider\n                  :model-value=\"seats\"\n                  :min=\"1\"\n                  :max=\"100\"\n                  :step=\"1\"\n                  :marks=\"SEAT_MARKS\"\n                  :tooltip=\"(val) => `${val} seats`\"\n                  @update:model-value=\"(val) => (seats = typeof val === 'number' ? val : val[0])\"\n                />\n              </div>\n              <p class=\"text-muted-foreground text-xs\">\n                Full workspace access, fine-grained permission controls, and audit log tracking for each member.\n              </p>\n            </div>\n\n            <Separator />\n\n            <div class=\"space-y-4\">\n              <div class=\"flex items-center justify-between\">\n                <div class=\"flex items-center gap-2.5\">\n                  <div\n                    class=\"bg-primary/10 text-primary border-primary/20 flex size-8 shrink-0 items-center justify-center rounded-md border\"\n                  >\n                    <Zap class=\"size-4\" />\n                  </div>\n                  <div>\n                    <span class=\"text-foreground text-sm font-medium\">Monthly API Requests</span>\n                  </div>\n                </div>\n                <div class=\"text-right\">\n                  <span class=\"text-foreground text-base font-semibold tabular-nums\">\n                    {{ currentRequestTier.label }}\n                  </span>\n                  <span class=\"text-muted-foreground text-xs\">\n                    ({{ currentRequestTier.cost === 0 ? 'Included' : `+$${currentRequestTier.cost}/mo` }})\n                  </span>\n                </div>\n              </div>\n              <div class=\"pb-6\">\n                <Slider\n                  :model-value=\"requestIndex\"\n                  :min=\"0\"\n                  :max=\"REQUEST_TIERS.length - 1\"\n                  :step=\"1\"\n                  :marks=\"REQUEST_MARKS\"\n                  :tooltip=\"(idx) => REQUEST_TIERS[idx]?.label ?? ''\"\n                  @update:model-value=\"(val) => (requestIndex = typeof val === 'number' ? val : val[0])\"\n                />\n              </div>\n              <p class=\"text-muted-foreground text-xs\">\n                Globally distributed edge endpoints, automatic rate limiting, and sub-50ms p99 latency SLA.\n              </p>\n            </div>\n\n            <Separator />\n\n            <div class=\"space-y-4\">\n              <div class=\"flex items-center justify-between\">\n                <div class=\"flex items-center gap-2.5\">\n                  <div\n                    class=\"bg-primary/10 text-primary border-primary/20 flex size-8 shrink-0 items-center justify-center rounded-md border\"\n                  >\n                    <HardDrive class=\"size-4\" />\n                  </div>\n                  <div>\n                    <span class=\"text-foreground text-sm font-medium\">Dedicated Cloud Storage</span>\n                  </div>\n                </div>\n                <div class=\"text-right\">\n                  <span class=\"text-foreground text-base font-semibold tabular-nums\">\n                    {{ currentStorageTier.label }}\n                  </span>\n                  <span class=\"text-muted-foreground text-xs\"> (+${{ currentStorageTier.cost }}/mo)</span>\n                </div>\n              </div>\n              <div class=\"pb-6\">\n                <Slider\n                  :model-value=\"storageIndex\"\n                  :min=\"0\"\n                  :max=\"STORAGE_TIERS.length - 1\"\n                  :step=\"1\"\n                  :marks=\"STORAGE_MARKS\"\n                  :tooltip=\"(idx) => STORAGE_TIERS[idx]?.label ?? ''\"\n                  @update:model-value=\"(val) => (storageIndex = typeof val === 'number' ? val : val[0])\"\n                />\n              </div>\n              <p class=\"text-muted-foreground text-xs\">\n                Encrypted at rest (AES-256) with multi-region automated replication and daily disaster backups.\n              </p>\n            </div>\n          </CardContent>\n        </Card>\n\n        <Card>\n          <CardHeader>\n            <CardTitle>Enterprise Add-ons</CardTitle>\n            <CardDescription\n              >Enhance your infrastructure with enterprise compliance, reliability, and support.</CardDescription\n            >\n          </CardHeader>\n          <CardContent class=\"space-y-3\">\n            <div\n              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\"\n            >\n              <div class=\"flex items-start gap-3\">\n                <div\n                  class=\"bg-primary/10 text-primary border-primary/20 mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border\"\n                >\n                  <Headphones class=\"size-4\" />\n                </div>\n                <div class=\"space-y-0.5\">\n                  <div class=\"flex items-center gap-2\">\n                    <span class=\"text-foreground text-sm font-medium\">Dedicated Support Manager</span>\n                    <Badge variant=\"outline\" class=\"text-xs font-normal\">+$200/mo</Badge>\n                  </div>\n                  <p class=\"text-muted-foreground text-xs\">\n                    Direct Slack channel, named technical account manager &amp; 1-hour response SLA.\n                  </p>\n                </div>\n              </div>\n              <div class=\"flex sm:justify-end\">\n                <Switch :model-value=\"addonSupport\" @update:model-value=\"(val) => (addonSupport = val)\" />\n              </div>\n            </div>\n\n            <div\n              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\"\n            >\n              <div class=\"flex items-start gap-3\">\n                <div\n                  class=\"bg-primary/10 text-primary border-primary/20 mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border\"\n                >\n                  <ShieldCheck class=\"size-4\" />\n                </div>\n                <div class=\"space-y-0.5\">\n                  <div class=\"flex items-center gap-2\">\n                    <span class=\"text-foreground text-sm font-medium\">Custom SLA Guarantee</span>\n                    <Badge variant=\"outline\" class=\"text-xs font-normal\">+$500/mo</Badge>\n                  </div>\n                  <p class=\"text-muted-foreground text-xs\">\n                    99.99% uptime guarantee with financial commitments and priority disaster recovery.\n                  </p>\n                </div>\n              </div>\n              <div class=\"flex sm:justify-end\">\n                <Switch :model-value=\"addonSla\" @update:model-value=\"(val) => (addonSla = val)\" />\n              </div>\n            </div>\n\n            <div\n              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\"\n            >\n              <div class=\"flex items-start gap-3\">\n                <div\n                  class=\"bg-primary/10 text-primary border-primary/20 mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border\"\n                >\n                  <KeyRound class=\"size-4\" />\n                </div>\n                <div class=\"space-y-0.5\">\n                  <div class=\"flex items-center gap-2\">\n                    <span class=\"text-foreground text-sm font-medium\">Single Sign-On (SSO / SAML)</span>\n                    <Badge variant=\"outline\" class=\"text-xs font-normal\">+$100/mo</Badge>\n                  </div>\n                  <p class=\"text-muted-foreground text-xs\">\n                    Okta, Azure AD, Google Workspace, and SAML 2.0 enterprise identity integration.\n                  </p>\n                </div>\n              </div>\n              <div class=\"flex sm:justify-end\">\n                <Switch :model-value=\"addonSso\" @update:model-value=\"(val) => (addonSso = val)\" />\n              </div>\n            </div>\n          </CardContent>\n        </Card>\n      </div>\n\n      <div class=\"lg:sticky lg:top-8 lg:col-span-5\">\n        <Card class=\"border-border bg-card shadow-xs\">\n          <CardHeader class=\"pb-4\">\n            <div class=\"flex items-center justify-between\">\n              <span class=\"text-muted-foreground text-xs font-semibold tracking-wider uppercase\">\n                Estimated Investment\n              </span>\n              <Badge :variant=\"recommendedPlan.variant\" class=\"gap-1 shadow-xs\">\n                <ShieldCheck v-if=\"recommendedPlan.name === 'Enterprise'\" class=\"size-3\" />\n                {{ recommendedPlan.badge }}\n              </Badge>\n            </div>\n            <div class=\"mt-4\">\n              <div class=\"flex items-baseline gap-1.5\">\n                <span class=\"text-foreground text-4xl font-bold tracking-tight tabular-nums sm:text-5xl\">\n                  ${{ totalMonthly }}\n                </span>\n                <span class=\"text-muted-foreground text-sm font-normal\"> / month</span>\n              </div>\n              <p v-if=\"isAnnual\" class=\"text-muted-foreground mt-2 text-xs\">\n                Billed annually (${{ totalAnnual.toLocaleString('en-US') }}/yr) ·\n                <span class=\"text-success font-semibold\">Save ${{ annualSavings.toLocaleString('en-US') }}/yr</span>\n              </p>\n              <p v-else class=\"text-muted-foreground mt-2 text-xs\">\n                Billed monthly · Switch to annual to save 20% (${{ annualSavings.toLocaleString('en-US') }}/yr)\n              </p>\n            </div>\n          </CardHeader>\n          <CardContent class=\"space-y-4\">\n            <Separator />\n            <div class=\"space-y-2.5\">\n              <div class=\"flex items-center justify-between text-sm\">\n                <span class=\"text-muted-foreground\">Base platform ({{ recommendedPlan.name }})</span>\n                <span class=\"font-medium tabular-nums\">${{ isAnnual ? Math.round(baseCost * 0.8) : baseCost }}/mo</span>\n              </div>\n              <div class=\"flex items-center justify-between text-sm\">\n                <span class=\"text-muted-foreground\">Team seats ({{ seats }} × ${{ SEAT_PRICE }})</span>\n                <span class=\"font-medium tabular-nums\"\n                  >${{ isAnnual ? Math.round(seatsCost * 0.8) : seatsCost }}/mo</span\n                >\n              </div>\n              <div class=\"flex items-center justify-between text-sm\">\n                <span class=\"text-muted-foreground\">API throughput ({{ currentRequestTier.label }})</span>\n                <span class=\"font-medium tabular-nums\">\n                  {{\n                    requestsCost === 0 ? 'Included' : `$${isAnnual ? Math.round(requestsCost * 0.8) : requestsCost}/mo`\n                  }}\n                </span>\n              </div>\n              <div class=\"flex items-center justify-between text-sm\">\n                <span class=\"text-muted-foreground\">Cloud storage ({{ currentStorageTier.label }})</span>\n                <span class=\"font-medium tabular-nums\">\n                  ${{ isAnnual ? Math.round(storageCost * 0.8) : storageCost }}/mo\n                </span>\n              </div>\n              <div class=\"flex items-center justify-between text-sm\">\n                <span class=\"text-muted-foreground\">Add-ons ({{ activeAddonsCount }} active)</span>\n                <span class=\"font-medium tabular-nums\">\n                  {{ addonsCost === 0 ? '$0/mo' : `$${isAnnual ? Math.round(addonsCost * 0.8) : addonsCost}/mo` }}\n                </span>\n              </div>\n              <div\n                v-if=\"isAnnual\"\n                class=\"bg-success/10 text-success flex items-center justify-between rounded-md px-2.5 py-1.5 text-xs font-medium\"\n              >\n                <span>Annual discount applied</span>\n                <span class=\"font-semibold tabular-nums\">20% off</span>\n              </div>\n            </div>\n            <Separator />\n            <ul class=\"text-muted-foreground space-y-2 text-xs\">\n              <li class=\"flex items-center gap-2\">\n                <Check class=\"text-primary size-3.5 shrink-0\" />\n                <span>14-day fully featured free trial</span>\n              </li>\n              <li class=\"flex items-center gap-2\">\n                <Check class=\"text-primary size-3.5 shrink-0\" />\n                <span>No credit card required upfront</span>\n              </li>\n              <li class=\"flex items-center gap-2\">\n                <Check class=\"text-primary size-3.5 shrink-0\" />\n                <span>Zero-downtime migration assistance</span>\n              </li>\n            </ul>\n          </CardContent>\n          <CardFooter class=\"flex flex-col gap-2.5 pt-2\">\n            <Button class=\"w-full gap-2 font-semibold shadow-xs\" size=\"lg\">\n              Start 14-Day Free Trial\n              <ArrowRight class=\"size-4\" />\n            </Button>\n            <Button variant=\"outline\" class=\"w-full\" size=\"default\"> Request Custom Quote </Button>\n          </CardFooter>\n        </Card>\n      </div>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/PricingCalculator.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/separator.json",
    "https://uipkge.dev/r/vue/slider.json",
    "https://uipkge.dev/r/vue/switch.json"
  ],
  "description": "Interactive SaaS and API pricing calculator with dynamic seat, request throughput, and cloud storage sliders, annual billing toggle with 20% discount, enterprise add-on switches, automated tier recommendation, and sticky live estimated cost breakdown.",
  "categories": [
    "marketing",
    "billing"
  ],
  "deprecated": true,
  "replacedBy": "pricing"
}