diff --git a/apps/web/src/lib/components/pricing/PlanCalculator.svelte b/apps/web/src/lib/components/pricing/PlanCalculator.svelte index abfdb1d..e955cf3 100644 --- a/apps/web/src/lib/components/pricing/PlanCalculator.svelte +++ b/apps/web/src/lib/components/pricing/PlanCalculator.svelte @@ -46,8 +46,8 @@ {i18n.t("pricing.calc.lead")} - -
+ +
{#if result.enterprise} -
+

{i18n.t("pricing.calc.enterpriseTitle")}

{i18n.t("pricing.calc.enterpriseBody")}

-
+ {/if} - -

{i18n.t("pricing.calc.disclaimer")}

diff --git a/apps/web/src/lib/components/pricing/PlanCard.svelte b/apps/web/src/lib/components/pricing/PlanCard.svelte index 53d6e15..8f7cafb 100644 --- a/apps/web/src/lib/components/pricing/PlanCard.svelte +++ b/apps/web/src/lib/components/pricing/PlanCard.svelte @@ -1,5 +1,6 @@ -
+
{#if plan.popular} @@ -150,9 +157,9 @@
- + {#each displayedFeatures as feature, index (index)} -
+
+
{/if} - + import { onMount } from "svelte"; + import { slide } from "svelte/transition"; import { CreditCard, Check, Calendar } from "@lucide/svelte"; import { Badge, @@ -44,6 +45,7 @@ let isYearly = $state(false); let openFaq = $state(null); + let openPlanId = $state(null); let plans = $state(PRICING_PLANS); onMount(() => { @@ -118,15 +120,22 @@
-
+
{#each plans as plan (plan.id)} - + (openPlanId = openPlanId === plan.id ? null : plan.id)} + /> {/each}
-
- -
+ {#if hideIntro} +
+ +
+ {/if}

{i18n.t("pricing.section.guestPublicBefore")} @@ -169,7 +178,7 @@ {i18n.t("pricing.section.faqTitle")}

{#each PRICING_FAQ as item, index (index)}
@@ -185,7 +194,10 @@ {#if openFaq === index} -
+
{item.answerKey ? i18n.t(item.answerKey) : item.answer}
{/if} @@ -194,7 +206,7 @@
-
+

{i18n.t("pricing.section.readyTitle")} diff --git a/apps/web/src/lib/components/pricing/pricing-data.ts b/apps/web/src/lib/components/pricing/pricing-data.ts index c5bce63..c79d76f 100644 --- a/apps/web/src/lib/components/pricing/pricing-data.ts +++ b/apps/web/src/lib/components/pricing/pricing-data.ts @@ -1,10 +1,10 @@ export interface PricingFeature { - /** English fallback / source label (also used when nameKey is absent). */ - name: string; - /** Optional i18n message key for marketing UI. */ - nameKey?: string; - included: boolean; - description?: string; + /** English fallback / source label (also used when nameKey is absent). */ + name: string; + /** Optional i18n message key for marketing UI. */ + nameKey?: string; + included: boolean; + description?: string; } /** @@ -12,42 +12,42 @@ export interface PricingFeature { * Used by /plans to overlay `resolved_features` onto marketing bullets. */ export const PRICING_FEATURE_CAPABILITY_BY_NAME_KEY: Record = { - "pricing.feature.aiTitles": "capability.ai_processing", - "pricing.feature.eprel": "capability.eprel", - "pricing.feature.apiAccess": "capability.api_access", - "pricing.feature.readApi": "capability.api_access", - "pricing.feature.fullApi": "capability.api_access", - "pricing.feature.fullApiWebhooks": "capability.api_access", - "pricing.feature.byok": "capability.byok", - "pricing.feature.byokAddon": "capability.byok", - "pricing.feature.byokIncluded": "capability.byok", - "pricing.feature.unlimitedAiOwnKey": "capability.byok" + "pricing.feature.aiTitles": "capability.ai_processing", + "pricing.feature.eprel": "capability.eprel", + "pricing.feature.apiAccess": "capability.api_access", + "pricing.feature.readApi": "capability.api_access", + "pricing.feature.fullApi": "capability.api_access", + "pricing.feature.fullApiWebhooks": "capability.api_access", + "pricing.feature.byok": "capability.byok", + "pricing.feature.byokAddon": "capability.byok", + "pricing.feature.byokIncluded": "capability.byok", + "pricing.feature.unlimitedAiOwnKey": "capability.byok", }; /** English-name fallbacks when nameKey is absent (or stripped by meter overlay). */ const PRICING_FEATURE_CAPABILITY_BY_NAME: Record = { - "AI titles & descriptions": "capability.ai_processing", - "AI titles and descriptions": "capability.ai_processing", - "EU energy labels (EPREL)": "capability.eprel", - "API access": "capability.api_access", - "Read API access": "capability.api_access", - "Full API access": "capability.api_access", - "Full API": "capability.api_access", - "Bring your own AI key": "capability.byok", - "Bring-your-own-key add-on": "capability.byok", - "Bring your own AI key included": "capability.byok", - "Unlimited AI credits / own key": "capability.byok" + "AI titles & descriptions": "capability.ai_processing", + "AI titles and descriptions": "capability.ai_processing", + "EU energy labels (EPREL)": "capability.eprel", + "API access": "capability.api_access", + "Read API access": "capability.api_access", + "Full API access": "capability.api_access", + "Full API": "capability.api_access", + "Bring your own AI key": "capability.byok", + "Bring-your-own-key add-on": "capability.byok", + "Bring your own AI key included": "capability.byok", + "Unlimited AI credits / own key": "capability.byok", }; /** Resolve the backend capability key for a marketing pricing feature bullet. */ export function resolvePricingFeatureCapabilityKey( - feature: Pick + feature: Pick, ): string | undefined { - if (feature.nameKey) { - const byKey = PRICING_FEATURE_CAPABILITY_BY_NAME_KEY[feature.nameKey]; - if (byKey) return byKey; - } - return PRICING_FEATURE_CAPABILITY_BY_NAME[feature.name]; + if (feature.nameKey) { + const byKey = PRICING_FEATURE_CAPABILITY_BY_NAME_KEY[feature.nameKey]; + if (byKey) return byKey; + } + return PRICING_FEATURE_CAPABILITY_BY_NAME[feature.name]; } /** @@ -55,48 +55,51 @@ export function resolvePricingFeatureCapabilityKey( * Prefer original (pre-meter) features for capability lookup so cleared nameKeys still map. */ export function applyResolvedFeaturesToPricingFeatures( - features: PricingFeature[], - originals: PricingFeature[], - resolved: Record | null | undefined + features: PricingFeature[], + originals: PricingFeature[], + resolved: Record | null | undefined, ): PricingFeature[] { - if (!resolved || typeof resolved !== "object") return features; - return features.map((f, index) => { - const original = originals[index] ?? f; - const capabilityKey = - resolvePricingFeatureCapabilityKey(original) ?? - resolvePricingFeatureCapabilityKey(f); - if (!capabilityKey || !Object.prototype.hasOwnProperty.call(resolved, capabilityKey)) { - return f; - } - return { ...f, included: resolved[capabilityKey] === true }; - }); + if (!resolved || typeof resolved !== "object") return features; + return features.map((f, index) => { + const original = originals[index] ?? f; + const capabilityKey = + resolvePricingFeatureCapabilityKey(original) ?? + resolvePricingFeatureCapabilityKey(f); + if ( + !capabilityKey || + !Object.prototype.hasOwnProperty.call(resolved, capabilityKey) + ) { + return f; + } + return { ...f, included: resolved[capabilityKey] === true }; + }); } export interface PricingPlan { - id: number; - name: string; - description: string; - /** Optional i18n message key for marketing description. */ - descriptionKey?: string; - pricePerMonth: number; - maxProducts: number; - /** Feed source limit (marketing / calculator). Unlimited when customPrice. */ - maxFeeds: number; - /** Monthly AI credits — matches apps/api billing.EnsureDefaultPlans. */ - monthlyCredits: number; - extraCreditCost: number; - features: PricingFeature[]; - popular?: boolean; - customPrice?: boolean; - ctaText: string; - ctaHref: string; + id: number; + name: string; + description: string; + /** Optional i18n message key for marketing description. */ + descriptionKey?: string; + pricePerMonth: number; + maxProducts: number; + /** Feed source limit (marketing / calculator). Unlimited when customPrice. */ + maxFeeds: number; + /** Monthly AI credits — matches apps/api billing.EnsureDefaultPlans. */ + monthlyCredits: number; + extraCreditCost: number; + features: PricingFeature[]; + popular?: boolean; + customPrice?: boolean; + ctaText: string; + ctaHref: string; } export interface PricingFaqItem { - question: string; - answer: string; - questionKey?: string; - answerKey?: string; + question: string; + answer: string; + questionKey?: string; + answerKey?: string; } /** @@ -108,345 +111,594 @@ export interface PricingFaqItem { * List prices are marketing placeholders; self-serve Checkout uses Stripe Price IDs from env/admin. */ export const PRICING_PLANS: PricingPlan[] = [ - { - id: 1, - name: "Free", - description: "Map a sample feed and clean product data — no card required", - descriptionKey: "pricing.plan.free.description", - pricePerMonth: 0, - maxProducts: 50, - maxFeeds: 1, - monthlyCredits: 0, - extraCreditCost: 0, - features: [ - { name: "Up to 50 SKUs", nameKey: "pricing.feature.upTo50Skus", included: true }, - { name: "1 feed source", nameKey: "pricing.feature.oneFeedSource", included: true }, - { - name: "Clean data, parse specs, and fill fields", - nameKey: "pricing.feature.cleanData", - included: true - }, - { name: "EU energy labels (EPREL)", nameKey: "pricing.feature.eprel", included: true }, - { name: "0 AI credits / month", nameKey: "pricing.feature.zeroCredits", included: true }, - { name: "1 manual export feed", nameKey: "pricing.feature.oneManualExport", included: true }, - { - name: "WooCommerce connection test only", - nameKey: "pricing.feature.wooTestOnly", - included: true - }, - { name: "Up to 2 seats", nameKey: "pricing.feature.upTo2Seats", included: true }, - { name: "AI titles & descriptions", nameKey: "pricing.feature.aiTitles", included: false }, - { name: "Live store sync", nameKey: "pricing.feature.liveStoreSync", included: false }, - { name: "API access", nameKey: "pricing.feature.apiAccess", included: false }, - { name: "Bring your own AI key", nameKey: "pricing.feature.byok", included: false } - ], - ctaText: "Get started", - ctaHref: "/register" - }, - { - id: 2, - name: "Starter", - description: "Up to 100 SKUs, entry AI (~100 credits), Woo sync — packs for more AI", - descriptionKey: "pricing.plan.starter.description", - pricePerMonth: 49, - maxProducts: 100, - maxFeeds: 2, - monthlyCredits: 100, - extraCreditCost: 0.009, - features: [ - { name: "Up to 100 SKUs", nameKey: "pricing.feature.upTo100Skus", included: true }, - { name: "2 feed sources", nameKey: "pricing.feature.twoFeedSources", included: true }, - { - name: "100 AI credits / month (~50% catalog cover)", - nameKey: "pricing.feature.credits100starter", - included: true - }, - { name: "AI titles & descriptions", nameKey: "pricing.feature.aiTitles", included: true }, - { name: "EU energy labels (EPREL)", nameKey: "pricing.feature.eprel", included: true }, - { name: "2 export feeds", nameKey: "pricing.feature.twoExports", included: true }, - { name: "Full WooCommerce sync", nameKey: "pricing.feature.fullWooSync", included: true }, - { name: "Read API access", nameKey: "pricing.feature.readApi", included: true }, - { name: "Email support", nameKey: "pricing.feature.emailSupport", included: true }, - { name: "Buy extra AI credit packs", nameKey: "pricing.feature.creditPacks", included: true }, - { name: "Bring your own AI key", nameKey: "pricing.feature.byok", included: false } - ], - ctaText: "Get started", - ctaHref: "/register" - }, - { - id: 3, - name: "Plus", - description: "Up to 400 SKUs, Woo + Shopify, AI (~400 credits)", - descriptionKey: "pricing.plan.plus.description", - pricePerMonth: 199, - maxProducts: 400, - maxFeeds: 3, - monthlyCredits: 400, - extraCreditCost: 0.009, - features: [ - { name: "Up to 400 SKUs", nameKey: "pricing.feature.upTo400Skus", included: true }, - { name: "3 feed sources", nameKey: "pricing.feature.threeFeedSources", included: true }, - { - name: "400 AI credits / month (~50% catalog cover)", - nameKey: "pricing.feature.credits400", - included: true - }, - { name: "AI titles & descriptions", nameKey: "pricing.feature.aiTitles", included: true }, - { name: "EU energy labels (EPREL)", nameKey: "pricing.feature.eprel", included: true }, - { name: "4 export feeds", nameKey: "pricing.feature.fourExports", included: true }, - { - name: "Full WooCommerce + Shopify sync", - nameKey: "pricing.feature.wooShopifySync", - included: true - }, - { name: "Read API access", nameKey: "pricing.feature.readApi", included: true }, - { name: "Email support", nameKey: "pricing.feature.emailSupport", included: true }, - { name: "Buy extra AI credit packs", nameKey: "pricing.feature.creditPacks", included: true }, - { name: "Bring your own AI key", nameKey: "pricing.feature.byok", included: false } - ], - ctaText: "Get started", - ctaHref: "/register" - }, - { - id: 4, - name: "Growth", - description: "Up to 1,200 SKUs, stores, BYOK, AI (~1,200 credits)", - descriptionKey: "pricing.plan.growth.description", - pricePerMonth: 299, - maxProducts: 1200, - maxFeeds: 5, - monthlyCredits: 1200, - extraCreditCost: 0.009, - popular: true, - features: [ - { name: "Up to 1,200 SKUs", nameKey: "pricing.feature.upTo1200Skus", included: true }, - { name: "5 feed sources", nameKey: "pricing.feature.fiveFeedSources", included: true }, - { - name: "1,200 AI credits / month (~50% catalog cover)", - nameKey: "pricing.feature.credits1200", - included: true - }, - { name: "AI titles & descriptions", nameKey: "pricing.feature.aiTitles", included: true }, - { name: "EU energy labels (EPREL)", nameKey: "pricing.feature.eprel", included: true }, - { name: "8 export feeds", nameKey: "pricing.feature.eightExports", included: true }, - { name: "Full formulas and variables", nameKey: "pricing.feature.fullFormulas", included: true }, - { name: "Full API access", nameKey: "pricing.feature.fullApi", included: true }, - { name: "Buy extra AI credit packs", nameKey: "pricing.feature.creditPacks", included: true }, - { name: "Bring-your-own-key add-on", nameKey: "pricing.feature.byokAddon", included: true }, - { name: "Email support (24h)", nameKey: "pricing.feature.emailSupport24h", included: true } - ], - ctaText: "Get started", - ctaHref: "/register" - }, - { - id: 5, - name: "Business", - description: "Up to 4,000 SKUs, BYOK, AI (~4,000 credits)", - descriptionKey: "pricing.plan.business.description", - pricePerMonth: 499, - maxProducts: 4000, - maxFeeds: 8, - monthlyCredits: 4000, - extraCreditCost: 0.009, - features: [ - { name: "Up to 4,000 SKUs", nameKey: "pricing.feature.upTo4kSkus", included: true }, - { name: "8 feed sources", nameKey: "pricing.feature.eightFeedSources", included: true }, - { - name: "4,000 AI credits / month (~50% catalog cover)", - nameKey: "pricing.feature.credits4000", - included: true - }, - { name: "AI titles & descriptions", nameKey: "pricing.feature.aiTitles", included: true }, - { name: "EU energy labels (EPREL)", nameKey: "pricing.feature.eprel", included: true }, - { - name: "Unlimited export feeds", - nameKey: "pricing.feature.unlimitedExports", - included: true - }, - { name: "Full API", nameKey: "pricing.feature.fullApiWebhooks", included: true }, - { name: "Buy extra AI credit packs", nameKey: "pricing.feature.creditPacks", included: true }, - { - name: "Bring your own AI key included", - nameKey: "pricing.feature.byokIncluded", - included: true - }, - { name: "Priority email support", nameKey: "pricing.feature.priorityEmail", included: true } - ], - ctaText: "Get started", - ctaHref: "/register" - }, - { - id: 6, - name: "Scale", - description: "Up to 12,000 SKUs, AI (~12k credits), packs/BYOK for more", - descriptionKey: "pricing.plan.scale.description", - pricePerMonth: 999, - maxProducts: 12_000, - maxFeeds: 12, - monthlyCredits: 12000, - extraCreditCost: 0.009, - features: [ - { name: "Up to 12,000 SKUs", nameKey: "pricing.feature.upTo12kSkus", included: true }, - { name: "12 feed sources", nameKey: "pricing.feature.twelveFeedSources", included: true }, - { - name: "12,000 AI credits / month (~50% catalog cover)", - nameKey: "pricing.feature.credits12000", - included: true - }, - { name: "AI titles & descriptions", nameKey: "pricing.feature.aiTitles", included: true }, - { name: "EU energy labels (EPREL)", nameKey: "pricing.feature.eprel", included: true }, - { - name: "Unlimited export feeds", - nameKey: "pricing.feature.unlimitedExports", - included: true - }, - { name: "Full API", nameKey: "pricing.feature.fullApiWebhooks", included: true }, - { name: "Buy extra AI credit packs", nameKey: "pricing.feature.creditPacks", included: true }, - { - name: "Bring your own AI key included", - nameKey: "pricing.feature.byokIncluded", - included: true - }, - { name: "Priority support + Slack", nameKey: "pricing.feature.prioritySlack", included: true } - ], - ctaText: "Get started", - ctaHref: "/register" - }, - { - id: 7, - name: "Enterprise", - description: "Unlimited capacity, SLA, and a dedicated account team", - descriptionKey: "pricing.plan.enterprise.description", - pricePerMonth: 0, - maxProducts: Number.POSITIVE_INFINITY, - maxFeeds: Number.POSITIVE_INFINITY, - monthlyCredits: 1_000_000, - extraCreditCost: 0, - customPrice: true, - features: [ - { name: "Unlimited SKUs", nameKey: "pricing.feature.unlimitedSkus", included: true }, - { name: "Unlimited feed sources", nameKey: "pricing.feature.unlimitedFeeds", included: true }, - { - name: "Unlimited AI credits / own key", - nameKey: "pricing.feature.unlimitedAiOwnKey", - included: true - }, - { - name: "SSO, dedicated account manager", - nameKey: "pricing.feature.ssoWebhooksAm", - included: true - }, - { - name: "Custom integrations", - nameKey: "pricing.feature.customIntegrations", - included: true - }, - { name: "SLA & priority support", nameKey: "pricing.feature.slaPriority", included: true } - ], - ctaText: "Contact sales", - ctaHref: "/contact-sales?source=pricing" - } + { + id: 1, + name: "Free", + description: + "Map a sample feed and clean your product data. No card required.", + descriptionKey: "pricing.plan.free.description", + pricePerMonth: 0, + maxProducts: 50, + maxFeeds: 1, + monthlyCredits: 0, + extraCreditCost: 0, + features: [ + { + name: "Up to 50 SKUs", + nameKey: "pricing.feature.upTo50Skus", + included: true, + }, + { + name: "1 feed source", + nameKey: "pricing.feature.oneFeedSource", + included: true, + }, + { + name: "Clean data, parse specs, and fill fields", + nameKey: "pricing.feature.cleanData", + included: true, + }, + { + name: "EU energy labels (EPREL)", + nameKey: "pricing.feature.eprel", + included: true, + }, + { + name: "0 AI credits / month", + nameKey: "pricing.feature.zeroCredits", + included: true, + }, + { + name: "1 manual export feed", + nameKey: "pricing.feature.oneManualExport", + included: true, + }, + { + name: "WooCommerce connection test only", + nameKey: "pricing.feature.wooTestOnly", + included: true, + }, + { + name: "Up to 2 seats", + nameKey: "pricing.feature.upTo2Seats", + included: true, + }, + { + name: "AI titles & descriptions", + nameKey: "pricing.feature.aiTitles", + included: false, + }, + { + name: "Live store sync", + nameKey: "pricing.feature.liveStoreSync", + included: false, + }, + { + name: "API access", + nameKey: "pricing.feature.apiAccess", + included: false, + }, + { + name: "Bring your own AI key", + nameKey: "pricing.feature.byok", + included: false, + }, + ], + ctaText: "Get started", + ctaHref: "/register", + }, + { + id: 2, + name: "Starter", + description: "For small catalogs that need AI content and WooCommerce sync", + descriptionKey: "pricing.plan.starter.description", + pricePerMonth: 49, + maxProducts: 100, + maxFeeds: 2, + monthlyCredits: 100, + extraCreditCost: 0.009, + features: [ + { + name: "Up to 100 SKUs", + nameKey: "pricing.feature.upTo100Skus", + included: true, + }, + { + name: "2 feed sources", + nameKey: "pricing.feature.twoFeedSources", + included: true, + }, + { + name: "100 AI credits / month (~50% catalog cover)", + nameKey: "pricing.feature.credits100starter", + included: true, + }, + { + name: "AI titles & descriptions", + nameKey: "pricing.feature.aiTitles", + included: true, + }, + { + name: "EU energy labels (EPREL)", + nameKey: "pricing.feature.eprel", + included: true, + }, + { + name: "2 export feeds", + nameKey: "pricing.feature.twoExports", + included: true, + }, + { + name: "Full WooCommerce sync", + nameKey: "pricing.feature.fullWooSync", + included: true, + }, + { + name: "Read API access", + nameKey: "pricing.feature.readApi", + included: true, + }, + { + name: "Email support", + nameKey: "pricing.feature.emailSupport", + included: true, + }, + { + name: "Buy extra AI credit packs", + nameKey: "pricing.feature.creditPacks", + included: true, + }, + { + name: "Bring your own AI key", + nameKey: "pricing.feature.byok", + included: false, + }, + ], + ctaText: "Get started", + ctaHref: "/register", + }, + { + id: 3, + name: "Plus", + description: + "More products, more AI credits, and support for two store platforms", + descriptionKey: "pricing.plan.plus.description", + pricePerMonth: 199, + maxProducts: 400, + maxFeeds: 3, + monthlyCredits: 400, + extraCreditCost: 0.009, + features: [ + { + name: "Up to 400 SKUs", + nameKey: "pricing.feature.upTo400Skus", + included: true, + }, + { + name: "3 feed sources", + nameKey: "pricing.feature.threeFeedSources", + included: true, + }, + { + name: "400 AI credits / month (~50% catalog cover)", + nameKey: "pricing.feature.credits400", + included: true, + }, + { + name: "AI titles & descriptions", + nameKey: "pricing.feature.aiTitles", + included: true, + }, + { + name: "EU energy labels (EPREL)", + nameKey: "pricing.feature.eprel", + included: true, + }, + { + name: "4 export feeds", + nameKey: "pricing.feature.fourExports", + included: true, + }, + { + name: "Full WooCommerce + Shopify sync", + nameKey: "pricing.feature.wooShopifySync", + included: true, + }, + { + name: "Read API access", + nameKey: "pricing.feature.readApi", + included: true, + }, + { + name: "Email support", + nameKey: "pricing.feature.emailSupport", + included: true, + }, + { + name: "Buy extra AI credit packs", + nameKey: "pricing.feature.creditPacks", + included: true, + }, + { + name: "Bring your own AI key", + nameKey: "pricing.feature.byok", + included: false, + }, + ], + ctaText: "Get started", + ctaHref: "/register", + }, + { + id: 4, + name: "Growth", + description: + "For growing catalogs that need flexible AI and store connections", + descriptionKey: "pricing.plan.growth.description", + pricePerMonth: 299, + maxProducts: 1200, + maxFeeds: 5, + monthlyCredits: 1200, + extraCreditCost: 0.009, + popular: true, + features: [ + { + name: "Up to 1,200 SKUs", + nameKey: "pricing.feature.upTo1200Skus", + included: true, + }, + { + name: "5 feed sources", + nameKey: "pricing.feature.fiveFeedSources", + included: true, + }, + { + name: "1,200 AI credits / month (~50% catalog cover)", + nameKey: "pricing.feature.credits1200", + included: true, + }, + { + name: "AI titles & descriptions", + nameKey: "pricing.feature.aiTitles", + included: true, + }, + { + name: "EU energy labels (EPREL)", + nameKey: "pricing.feature.eprel", + included: true, + }, + { + name: "8 export feeds", + nameKey: "pricing.feature.eightExports", + included: true, + }, + { + name: "Full formulas and variables", + nameKey: "pricing.feature.fullFormulas", + included: true, + }, + { + name: "Full API access", + nameKey: "pricing.feature.fullApi", + included: true, + }, + { + name: "Buy extra AI credit packs", + nameKey: "pricing.feature.creditPacks", + included: true, + }, + { + name: "Bring-your-own-key add-on", + nameKey: "pricing.feature.byokAddon", + included: true, + }, + { + name: "Email support (24h)", + nameKey: "pricing.feature.emailSupport24h", + included: true, + }, + ], + ctaText: "Get started", + ctaHref: "/register", + }, + { + id: 5, + name: "Business", + description: "Higher capacity, unlimited exports, and priority support", + descriptionKey: "pricing.plan.business.description", + pricePerMonth: 499, + maxProducts: 4000, + maxFeeds: 8, + monthlyCredits: 4000, + extraCreditCost: 0.009, + features: [ + { + name: "Up to 4,000 SKUs", + nameKey: "pricing.feature.upTo4kSkus", + included: true, + }, + { + name: "8 feed sources", + nameKey: "pricing.feature.eightFeedSources", + included: true, + }, + { + name: "4,000 AI credits / month (~50% catalog cover)", + nameKey: "pricing.feature.credits4000", + included: true, + }, + { + name: "AI titles & descriptions", + nameKey: "pricing.feature.aiTitles", + included: true, + }, + { + name: "EU energy labels (EPREL)", + nameKey: "pricing.feature.eprel", + included: true, + }, + { + name: "Unlimited export feeds", + nameKey: "pricing.feature.unlimitedExports", + included: true, + }, + { + name: "Full API", + nameKey: "pricing.feature.fullApiWebhooks", + included: true, + }, + { + name: "Buy extra AI credit packs", + nameKey: "pricing.feature.creditPacks", + included: true, + }, + { + name: "Bring your own AI key included", + nameKey: "pricing.feature.byokIncluded", + included: true, + }, + { + name: "Priority email support", + nameKey: "pricing.feature.priorityEmail", + included: true, + }, + ], + ctaText: "Get started", + ctaHref: "/register", + }, + { + id: 6, + name: "Scale", + description: "Built for large catalogs with demanding automation needs", + descriptionKey: "pricing.plan.scale.description", + pricePerMonth: 999, + maxProducts: 12_000, + maxFeeds: 12, + monthlyCredits: 12000, + extraCreditCost: 0.009, + features: [ + { + name: "Up to 12,000 SKUs", + nameKey: "pricing.feature.upTo12kSkus", + included: true, + }, + { + name: "12 feed sources", + nameKey: "pricing.feature.twelveFeedSources", + included: true, + }, + { + name: "12,000 AI credits / month (~50% catalog cover)", + nameKey: "pricing.feature.credits12000", + included: true, + }, + { + name: "AI titles & descriptions", + nameKey: "pricing.feature.aiTitles", + included: true, + }, + { + name: "EU energy labels (EPREL)", + nameKey: "pricing.feature.eprel", + included: true, + }, + { + name: "Unlimited export feeds", + nameKey: "pricing.feature.unlimitedExports", + included: true, + }, + { + name: "Full API", + nameKey: "pricing.feature.fullApiWebhooks", + included: true, + }, + { + name: "Buy extra AI credit packs", + nameKey: "pricing.feature.creditPacks", + included: true, + }, + { + name: "Bring your own AI key included", + nameKey: "pricing.feature.byokIncluded", + included: true, + }, + { + name: "Priority support + Slack", + nameKey: "pricing.feature.prioritySlack", + included: true, + }, + ], + ctaText: "Get started", + ctaHref: "/register", + }, + { + id: 7, + name: "Enterprise", + description: "Unlimited capacity, SLA, and a dedicated account team", + descriptionKey: "pricing.plan.enterprise.description", + pricePerMonth: 0, + maxProducts: Number.POSITIVE_INFINITY, + maxFeeds: Number.POSITIVE_INFINITY, + monthlyCredits: 1_000_000, + extraCreditCost: 0, + customPrice: true, + features: [ + { + name: "Unlimited SKUs", + nameKey: "pricing.feature.unlimitedSkus", + included: true, + }, + { + name: "Unlimited feed sources", + nameKey: "pricing.feature.unlimitedFeeds", + included: true, + }, + { + name: "Unlimited AI credits / own key", + nameKey: "pricing.feature.unlimitedAiOwnKey", + included: true, + }, + { + name: "SSO, dedicated account manager", + nameKey: "pricing.feature.ssoWebhooksAm", + included: true, + }, + { + name: "Custom integrations", + nameKey: "pricing.feature.customIntegrations", + included: true, + }, + { + name: "SLA & priority support", + nameKey: "pricing.feature.slaPriority", + included: true, + }, + ], + ctaText: "Contact sales", + ctaHref: "/contact-sales?source=pricing", + }, ]; export const PRICING_CAPABILITIES = [ - { - category: "Feeds to catalog", - categoryKey: "pricing.cap.feeds", - features: [ - { text: "CSV / XML / URL supplier feeds", key: "pricing.cap.feeds.f1" }, - { text: "Field mapping & validation", key: "pricing.cap.feeds.f2" }, - { text: "Multi-supplier merge", key: "pricing.cap.feeds.f3" }, - { text: "Scheduled sync", key: "pricing.cap.feeds.f4" }, - { text: "Category-aware transforms", key: "pricing.cap.feeds.f5" } - ] - }, - { - category: "Processing & AI", - categoryKey: "pricing.cap.processing", - features: [ - { text: "Data cleanup & attribute fill (all plans)", key: "pricing.cap.processing.f1" }, - { text: "AI titles & descriptions (paid)", key: "pricing.cap.processing.f2" }, - { text: "EU energy labels / EPREL (all plans)", key: "pricing.cap.processing.f3" }, - { text: "Formulas, brand voice, variables", key: "pricing.cap.processing.f4" }, - { text: "Managed credits or your own AI key", key: "pricing.cap.processing.f5" } - ] - }, - { - category: "Export & channels", - categoryKey: "pricing.cap.export", - features: [ - { text: "XML / CSV export feeds", key: "pricing.cap.export.f1" }, - { text: "WooCommerce / Shopify sync", key: "pricing.cap.export.f2" }, - { text: "Full API", key: "pricing.cap.export.f3" }, - { text: "Channel-specific formats", key: "pricing.cap.export.f4" }, - { text: "Bulk updates", key: "pricing.cap.export.f5" } - ] - }, - { - category: "Limits & control", - categoryKey: "pricing.cap.limits", - features: [ - { text: "SKU caps by plan (Scale reaches 55k; Enterprise for huge catalogs)", key: "pricing.cap.limits.f1" }, - { text: "Monthly AI credits + buyable top-up packs", key: "pricing.cap.limits.f2" }, - { text: "Team roles & invites", key: "pricing.cap.limits.f3" }, - { text: "Enterprise SLA options", key: "pricing.cap.limits.f4" } - ] - } + { + category: "Feeds to catalog", + categoryKey: "pricing.cap.feeds", + features: [ + { text: "CSV / XML / URL supplier feeds", key: "pricing.cap.feeds.f1" }, + { text: "Field mapping & validation", key: "pricing.cap.feeds.f2" }, + { text: "Multi-supplier merge", key: "pricing.cap.feeds.f3" }, + { text: "Scheduled sync", key: "pricing.cap.feeds.f4" }, + { text: "Category-aware transforms", key: "pricing.cap.feeds.f5" }, + ], + }, + { + category: "Processing & AI", + categoryKey: "pricing.cap.processing", + features: [ + { + text: "Data cleanup & attribute fill (all plans)", + key: "pricing.cap.processing.f1", + }, + { + text: "AI titles & descriptions (paid)", + key: "pricing.cap.processing.f2", + }, + { + text: "EU energy labels / EPREL (all plans)", + key: "pricing.cap.processing.f3", + }, + { + text: "Formulas, brand voice, variables", + key: "pricing.cap.processing.f4", + }, + { + text: "Managed credits or your own AI key", + key: "pricing.cap.processing.f5", + }, + ], + }, + { + category: "Export & channels", + categoryKey: "pricing.cap.export", + features: [ + { text: "XML / CSV export feeds", key: "pricing.cap.export.f1" }, + { text: "WooCommerce / Shopify sync", key: "pricing.cap.export.f2" }, + { text: "Full API", key: "pricing.cap.export.f3" }, + { text: "Channel-specific formats", key: "pricing.cap.export.f4" }, + { text: "Bulk updates", key: "pricing.cap.export.f5" }, + ], + }, + { + category: "Limits & control", + categoryKey: "pricing.cap.limits", + features: [ + { + text: "SKU caps by plan (Scale reaches 55k; Enterprise for huge catalogs)", + key: "pricing.cap.limits.f1", + }, + { + text: "Monthly AI credits + buyable top-up packs", + key: "pricing.cap.limits.f2", + }, + { text: "Team roles & invites", key: "pricing.cap.limits.f3" }, + { text: "Enterprise SLA options", key: "pricing.cap.limits.f4" }, + ], + }, ] as const; export const PRICING_FAQ: PricingFaqItem[] = [ - { - question: "What are AI credits?", - answer: - "AI credits pay for steps like generating titles and descriptions (~2 credits per product enhance). Free includes 0 AI. Paid plans have per-tier SKU caps (Starter 1.5k → Scale 55k; Enterprise unlimited); included AI is sized from a credit base (not the full SKU cap) and rises by tier: Starter ~4% (100) · Plus ~10% (600) · Growth ~15% (2,700) · Business ~20% (7,000) · Scale ~16% (16,000) credits/month for 1 primary language. Finish more AI with packs or BYOK (Growth+). Plan price is for platform + included AI — not a full-catalog AI bundle.", - questionKey: "pricing.faq.credits.q", - answerKey: "pricing.faq.credits.a" - }, - { - question: "Can I buy extra AI credits?", - answer: - "Yes. One-time AI packs from Billing (Checkout mode=payment — not subscription add-ons): Nano 25 credits / $29 · Starter 65 / $59 · Plus 200 / $149 · Growth 500 / $299 · Catalog 1,200 / $599 · Business 3,000 / $1,299 · Scale 8,000 / $2,999. Packs do not raise SKU limits.", - questionKey: "pricing.faq.packs.q", - answerKey: "pricing.faq.packs.a" - }, - { - question: "What happens if I hit my product or credit limit?", - answer: - "We warn you as you get close. Each plan has a SKU cap that grows by tier (Scale reaches 55k; 1M+ catalogs are Enterprise). AI credits are often the binding limit for enrichment. When you run out of AI credits, buy a credit pack, wait for the next monthly grant, upgrade for a larger grant, or use your own AI key (Growth+).", - questionKey: "pricing.faq.limits.q", - answerKey: "pricing.faq.limits.a" - }, - { - question: "Can I upgrade or downgrade?", - answer: - "Yes. Start on Free, then upgrade to Starter, Plus, Growth, Business, or Scale from Plans or Billing (Stripe Checkout). Enterprise is always sales-led.", - questionKey: "pricing.faq.change.q", - answerKey: "pricing.faq.change.a" - }, - { - question: "What does Free include?", - answer: - "Forever Free: 50 products, one feed, data cleanup and attribute fill, EU energy labels (EPREL — public data, no credits), plus one manual export — with 0 AI credits. No credit card. Upgrade when you need AI titles, descriptions, or more capacity.", - questionKey: "pricing.faq.free.q", - answerKey: "pricing.faq.free.a" - }, - { - question: "Why not pay per product like content-only tools?", - answer: - "Descrybe is built for the full path from supplier feed to catalog to WooCommerce or export — not only AI copy. You pay for platform capacity (products and feeds); AI is a usage layer on top.", - questionKey: "pricing.faq.why.q", - answerKey: "pricing.faq.why.a" - }, - { - question: "How does annual billing work?", - answer: - "Yearly billing is about 20% off the monthly list price. Start free, then choose yearly in Checkout when you upgrade (or contact sales).", - questionKey: "pricing.faq.annual.q", - answerKey: "pricing.faq.annual.a" - } + { + question: "What are AI credits?", + answer: + "AI credits are used when Descrybe generates content such as product titles and descriptions. Enhancing one product usually costs about two credits. Each paid plan includes a monthly allowance, and you can buy more credits when needed. Growth plans and above can also use their own AI key.", + questionKey: "pricing.faq.credits.q", + answerKey: "pricing.faq.credits.a", + }, + { + question: "Can I buy extra AI credits?", + answer: + "Yes. You can buy a one-time credit pack from Billing whenever you need more AI capacity. Credit packs do not change your plan's SKU limit.", + questionKey: "pricing.faq.packs.q", + answerKey: "pricing.faq.packs.a", + }, + { + question: "What happens if I hit my product or credit limit?", + answer: + "We warn you as you get close. Each plan has a SKU cap that grows by tier (Scale reaches 55k; 1M+ catalogs are Enterprise). AI credits are often the binding limit for enrichment. When you run out of AI credits, buy a credit pack, wait for the next monthly grant, upgrade for a larger grant, or use your own AI key (Growth+).", + questionKey: "pricing.faq.limits.q", + answerKey: "pricing.faq.limits.a", + }, + { + question: "Can I upgrade or downgrade?", + answer: + "Yes. Start on Free, then upgrade to Starter, Plus, Growth, Business, or Scale from Plans or Billing (Stripe Checkout). Enterprise is always sales-led.", + questionKey: "pricing.faq.change.q", + answerKey: "pricing.faq.change.a", + }, + { + question: "What does Free include?", + answer: + "The Free plan includes 50 products, one supplier feed, data cleanup, attribute filling, EU energy labels, and one manual export. It does not include AI credits, and no credit card is required.", + questionKey: "pricing.faq.free.q", + answerKey: "pricing.faq.free.a", + }, + { + question: "Why not pay per product like content-only tools?", + answer: + "Descrybe handles the full workflow from supplier feed to finished catalog, including mapping, enrichment, exports, and store sync. Your plan covers the products and feeds you manage, while AI usage is measured separately.", + questionKey: "pricing.faq.why.q", + answerKey: "pricing.faq.why.a", + }, + { + question: "How does annual billing work?", + answer: + "Yearly billing is about 20% off the monthly list price. Start free, then choose yearly in Checkout when you upgrade (or contact sales).", + questionKey: "pricing.faq.annual.q", + answerKey: "pricing.faq.annual.a", + }, ]; -export const SALES_CALENDLY_URL = "https://calendly.com/tim-berce/descrybe-demo"; +export const SALES_CALENDLY_URL = + "https://calendly.com/tim-berce/descrybe-demo"; /** In-app contact sales form (preferred CTA; Calendly remains optional on the form). */ export const CONTACT_SALES_HREF = "/contact-sales"; @@ -456,90 +708,105 @@ export const ANNUAL_DISCOUNT = 0.2; /** Plan meters from GET /api/public/plans (DB-backed). */ export type ApiPublicPlan = { - id: number; - name: string; - description?: string | null; - monthly_credits?: number; - max_products?: number | null; - ai_cover_percent?: number; - is_custom?: boolean; + id: number; + name: string; + description?: string | null; + monthly_credits?: number; + max_products?: number | null; + ai_cover_percent?: number; + is_custom?: boolean; }; function isSkuFeature(feature: PricingFeature): boolean { - return Boolean(feature.nameKey?.toLowerCase().includes("skus")) || /SKU/i.test(feature.name); + return ( + Boolean(feature.nameKey?.toLowerCase().includes("skus")) || + /SKU/i.test(feature.name) + ); } function isCreditsFeature(feature: PricingFeature): boolean { - return ( - Boolean(feature.nameKey?.toLowerCase().includes("credits")) || - /AI credits/i.test(feature.name) - ); + return ( + Boolean(feature.nameKey?.toLowerCase().includes("credits")) || + /AI credits/i.test(feature.name) + ); } /** Overlay DB meters onto marketing shells (price/CTA/feature matrix stay marketing). */ export function applyApiMetersToPricingPlan( - marketing: PricingPlan, - api: ApiPublicPlan | undefined + marketing: PricingPlan, + api: ApiPublicPlan | undefined, ): PricingPlan { - if (!api) return marketing; - const monthlyCredits = - typeof api.monthly_credits === "number" ? api.monthly_credits : marketing.monthlyCredits; - const cover = typeof api.ai_cover_percent === "number" ? api.ai_cover_percent : 0; - const description = api.description?.trim() || marketing.description; - const maxProducts = marketing.customPrice - ? marketing.maxProducts - : typeof api.max_products === "number" - ? api.max_products - : marketing.maxProducts; + if (!api) return marketing; + const monthlyCredits = + typeof api.monthly_credits === "number" + ? api.monthly_credits + : marketing.monthlyCredits; + const cover = + typeof api.ai_cover_percent === "number" ? api.ai_cover_percent : 0; + const description = api.description?.trim() || marketing.description; + const maxProducts = marketing.customPrice + ? marketing.maxProducts + : typeof api.max_products === "number" + ? api.max_products + : marketing.maxProducts; - const features = marketing.features.map((f) => { - if (isSkuFeature(f) && !marketing.customPrice && typeof maxProducts === "number" && Number.isFinite(maxProducts)) { - return { - ...f, - name: `Up to ${maxProducts.toLocaleString()} SKUs`, - nameKey: undefined - }; - } - if (isCreditsFeature(f)) { - if (monthlyCredits === 0) { - return { - ...f, - name: "0 AI credits / month", - nameKey: "pricing.feature.zeroCredits" - }; - } - if (marketing.customPrice) return f; - const coverLabel = cover > 0 ? ` (~${cover}% credit base)` : ""; - return { - ...f, - name: `${monthlyCredits.toLocaleString()} AI credits / month${coverLabel}`, - nameKey: undefined - }; - } - return f; - }); + const features = marketing.features.map((f) => { + if ( + isSkuFeature(f) && + !marketing.customPrice && + typeof maxProducts === "number" && + Number.isFinite(maxProducts) + ) { + return { + ...f, + name: `Up to ${maxProducts.toLocaleString()} SKUs`, + nameKey: undefined, + }; + } + if (isCreditsFeature(f)) { + if (monthlyCredits === 0) { + return { + ...f, + name: "0 AI credits / month", + nameKey: "pricing.feature.zeroCredits", + }; + } + if (marketing.customPrice) return f; + const coverLabel = cover > 0 ? ` (~${cover}% credit base)` : ""; + return { + ...f, + name: `${monthlyCredits.toLocaleString()} AI credits / month${coverLabel}`, + nameKey: undefined, + }; + } + return f; + }); - return { - ...marketing, - id: api.id > 0 ? api.id : marketing.id, - description, - maxProducts: typeof maxProducts === "number" ? maxProducts : marketing.maxProducts, - monthlyCredits, - features - }; + return { + ...marketing, + id: api.id > 0 ? api.id : marketing.id, + description, + maxProducts: + typeof maxProducts === "number" ? maxProducts : marketing.maxProducts, + monthlyCredits, + features, + }; } -export function mergePricingPlansFromApi(apiPlans: ApiPublicPlan[]): PricingPlan[] { - return PRICING_PLANS.map((marketing) => { - const api = apiPlans.find( - (p) => p.name.trim().toLowerCase() === marketing.name.trim().toLowerCase() - ); - return applyApiMetersToPricingPlan(marketing, api); - }); +export function mergePricingPlansFromApi( + apiPlans: ApiPublicPlan[], +): PricingPlan[] { + return PRICING_PLANS.map((marketing) => { + const api = apiPlans.find( + (p) => + p.name.trim().toLowerCase() === marketing.name.trim().toLowerCase(), + ); + return applyApiMetersToPricingPlan(marketing, api); + }); } /** True for the public product ladder only (excludes A1 / Merkur / legacy plans). */ export function isPublicProductPlan(name: string | null | undefined): boolean { - const key = (name ?? "").trim().toLowerCase(); - return PRICING_PLANS.some((p) => p.name.toLowerCase() === key); + const key = (name ?? "").trim().toLowerCase(); + return PRICING_PLANS.some((p) => p.name.toLowerCase() === key); } diff --git a/apps/web/src/lib/components/site/CtaBanner.svelte b/apps/web/src/lib/components/site/CtaBanner.svelte index 84a1837..b42fcf8 100644 --- a/apps/web/src/lib/components/site/CtaBanner.svelte +++ b/apps/web/src/lib/components/site/CtaBanner.svelte @@ -13,7 +13,7 @@ const featureKeys = ["home.cta.f1", "home.cta.f2", "home.cta.f3"] as const; -
+
diff --git a/apps/web/src/lib/components/site/Footer.svelte b/apps/web/src/lib/components/site/Footer.svelte index af2087d..8f88121 100644 --- a/apps/web/src/lib/components/site/Footer.svelte +++ b/apps/web/src/lib/components/site/Footer.svelte @@ -27,33 +27,33 @@ } -