gitea
This commit is contained in:
255
frontend/components/landing/PricingComparison.tsx
Normal file
255
frontend/components/landing/PricingComparison.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import { useState } from 'react'
|
||||
import { TrendingDown, DollarSign } from 'lucide-react'
|
||||
|
||||
export function PricingComparison() {
|
||||
const [monitorCount, setMonitorCount] = useState(50)
|
||||
|
||||
// Pricing calculation logic
|
||||
const calculatePricing = (monitors: number) => {
|
||||
// Competitors: tiered pricing
|
||||
let competitorMin, competitorMax
|
||||
if (monitors <= 10) {
|
||||
competitorMin = 29
|
||||
competitorMax = 49
|
||||
} else if (monitors <= 50) {
|
||||
competitorMin = 79
|
||||
competitorMax = 129
|
||||
} else if (monitors <= 100) {
|
||||
competitorMin = 129
|
||||
competitorMax = 199
|
||||
} else {
|
||||
competitorMin = 199
|
||||
competitorMax = 299
|
||||
}
|
||||
|
||||
// Our pricing: simpler, fairer
|
||||
let ourPrice
|
||||
if (monitors <= 10) {
|
||||
ourPrice = 19
|
||||
} else if (monitors <= 50) {
|
||||
ourPrice = 49
|
||||
} else if (monitors <= 100) {
|
||||
ourPrice = 89
|
||||
} else {
|
||||
ourPrice = 149
|
||||
}
|
||||
|
||||
const competitorAvg = (competitorMin + competitorMax) / 2
|
||||
const savings = competitorAvg - ourPrice
|
||||
const savingsPercent = Math.round((savings / competitorAvg) * 100)
|
||||
|
||||
return {
|
||||
competitorMin,
|
||||
competitorMax,
|
||||
competitorAvg,
|
||||
ourPrice,
|
||||
savings,
|
||||
savingsPercent
|
||||
}
|
||||
}
|
||||
|
||||
const pricing = calculatePricing(monitorCount)
|
||||
|
||||
return (
|
||||
<section className="py-32 bg-gradient-to-b from-[hsl(var(--section-bg-6))] to-[hsl(var(--section-bg-3))] relative overflow-hidden">
|
||||
{/* Background Pattern - Enhanced Dot Grid */}
|
||||
<div className="absolute inset-0 opacity-8">
|
||||
<div className="absolute inset-0" style={{
|
||||
backgroundImage: `radial-gradient(circle, hsl(var(--teal)) 1.5px, transparent 1.5px)`,
|
||||
backgroundSize: '30px 30px'
|
||||
}} />
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-5xl px-6 relative z-10">
|
||||
{/* Section Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<div className="inline-flex items-center gap-2 rounded-full bg-[hsl(var(--teal))]/10 border border-[hsl(var(--teal))]/20 px-4 py-1.5 text-sm font-medium text-[hsl(var(--teal))] mb-6">
|
||||
<DollarSign className="h-4 w-4" />
|
||||
Fair Pricing
|
||||
</div>
|
||||
<h2 className="text-4xl lg:text-5xl font-display font-bold text-foreground mb-6">
|
||||
See how much you{' '}
|
||||
<span className="text-[hsl(var(--teal))]">save</span>
|
||||
</h2>
|
||||
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
|
||||
Compare our transparent pricing with typical competitors. No hidden fees, no surprises.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Interactive Comparison Card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="rounded-3xl border-2 border-border bg-card p-8 lg:p-12 shadow-2xl"
|
||||
>
|
||||
{/* Monitor Count Slider */}
|
||||
<div className="mb-12">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<label className="text-sm font-bold text-muted-foreground uppercase tracking-wider">
|
||||
Number of Monitors
|
||||
</label>
|
||||
<motion.div
|
||||
key={monitorCount}
|
||||
initial={{ scale: 1.2 }}
|
||||
animate={{ scale: 1 }}
|
||||
className="text-4xl font-bold text-foreground font-mono"
|
||||
>
|
||||
{monitorCount}
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Slider */}
|
||||
<div className="relative">
|
||||
<input
|
||||
type="range"
|
||||
min="5"
|
||||
max="200"
|
||||
step="5"
|
||||
value={monitorCount}
|
||||
onChange={(e) => setMonitorCount(Number(e.target.value))}
|
||||
className="w-full h-3 bg-secondary rounded-full appearance-none cursor-pointer
|
||||
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-6 [&::-webkit-slider-thumb]:h-6
|
||||
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[hsl(var(--teal))]
|
||||
[&::-webkit-slider-thumb]:shadow-lg [&::-webkit-slider-thumb]:cursor-grab
|
||||
[&::-webkit-slider-thumb]:active:cursor-grabbing [&::-webkit-slider-thumb]:hover:scale-110
|
||||
[&::-webkit-slider-thumb]:transition-transform
|
||||
[&::-moz-range-thumb]:w-6 [&::-moz-range-thumb]:h-6
|
||||
[&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-[hsl(var(--teal))]
|
||||
[&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:shadow-lg
|
||||
[&::-moz-range-thumb]:cursor-grab [&::-moz-range-thumb]:active:cursor-grabbing"
|
||||
/>
|
||||
{/* Tick marks - positioned by percentage based on slider range (5-200) */}
|
||||
<div className="relative mt-2 h-4">
|
||||
<span className="absolute text-xs text-muted-foreground" style={{ left: '0%', transform: 'translateX(0)' }}>5</span>
|
||||
<span className="absolute text-xs text-muted-foreground" style={{ left: `${((50 - 5) / (200 - 5)) * 100}%`, transform: 'translateX(-50%)' }}>50</span>
|
||||
<span className="absolute text-xs text-muted-foreground" style={{ left: `${((100 - 5) / (200 - 5)) * 100}%`, transform: 'translateX(-50%)' }}>100</span>
|
||||
<span className="absolute text-xs text-muted-foreground" style={{ left: '100%', transform: 'translateX(-100%)' }}>200</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Price Comparison Bars */}
|
||||
<div className="grid lg:grid-cols-2 gap-8 mb-8">
|
||||
{/* Competitors */}
|
||||
<motion.div
|
||||
layout
|
||||
className="space-y-4"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-bold text-muted-foreground uppercase tracking-wider">
|
||||
Typical Competitors
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Bar */}
|
||||
<motion.div
|
||||
className="relative h-24 rounded-2xl bg-gradient-to-r from-red-500/10 to-red-500/20 border-2 border-red-500/30 overflow-hidden"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<motion.div
|
||||
className="absolute inset-0 bg-gradient-to-r from-red-500/20 to-red-500/40"
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
|
||||
style={{ transformOrigin: 'left' }}
|
||||
/>
|
||||
<div className="relative h-full flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<motion.div
|
||||
key={`comp-${pricing.competitorMin}-${pricing.competitorMax}`}
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
className="text-4xl font-bold text-red-700 font-mono"
|
||||
>
|
||||
${pricing.competitorMin}-{pricing.competitorMax}
|
||||
</motion.div>
|
||||
<div className="text-xs font-medium text-red-600">per month</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* Us */}
|
||||
<motion.div
|
||||
layout
|
||||
className="space-y-4"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-bold text-[hsl(var(--teal))] uppercase tracking-wider">
|
||||
Our Pricing
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Bar */}
|
||||
<motion.div
|
||||
className="relative h-24 rounded-2xl bg-gradient-to-r from-[hsl(var(--teal))]/10 to-[hsl(var(--teal))]/20 border-2 border-[hsl(var(--teal))]/30 overflow-hidden"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<motion.div
|
||||
className="absolute inset-0 bg-gradient-to-r from-[hsl(var(--teal))]/20 to-[hsl(var(--teal))]/40"
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={{ scaleX: pricing.ourPrice / pricing.competitorMax }}
|
||||
transition={{ duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
|
||||
style={{ transformOrigin: 'left' }}
|
||||
/>
|
||||
<div className="relative h-full flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<motion.div
|
||||
key={`our-${pricing.ourPrice}`}
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
className="text-5xl font-bold text-[hsl(var(--teal))] font-mono"
|
||||
>
|
||||
${pricing.ourPrice}
|
||||
</motion.div>
|
||||
<div className="text-xs font-medium text-[hsl(var(--teal))]">per month</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Savings Badge */}
|
||||
<motion.div
|
||||
key={`savings-${pricing.savings}`}
|
||||
initial={{ scale: 0.9, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
|
||||
className="flex items-center justify-center gap-4 p-6 rounded-2xl bg-gradient-to-r from-[hsl(var(--primary))]/10 via-[hsl(var(--teal))]/10 to-[hsl(var(--burgundy))]/10 border-2 border-[hsl(var(--teal))]/30"
|
||||
>
|
||||
<TrendingDown className="h-8 w-8 text-[hsl(var(--teal))]" />
|
||||
<div className="text-center">
|
||||
<div className="text-sm font-medium text-muted-foreground">You save</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className="text-4xl font-bold text-foreground">
|
||||
${Math.round(pricing.savings)}
|
||||
</span>
|
||||
<span className="text-xl text-muted-foreground">/month</span>
|
||||
<span className="ml-2 px-3 py-1 rounded-full bg-[hsl(var(--teal))]/20 text-sm font-bold text-[hsl(var(--teal))]">
|
||||
{pricing.savingsPercent}% off
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Fine Print */}
|
||||
<p className="mt-6 text-center text-xs text-muted-foreground">
|
||||
* Based on average pricing from Visualping, Distill.io, and similar competitors as of Jan 2026
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user