Skip to content

Commit 67266c7

Browse files
author
Ravi Singh
committed
feat(landing): SVG icons, water effects, dark mode fix, Pro pricing update
- Replace all emoji icons with themed SVG icons (sky-400 stroke style) - Add animated water effects: floating bubbles, wave dividers, wave surfaces - Force dark mode on landing page (fixes light theme contrast issues) - Update Pro tier: ₹599/$9.99, cap at 10 sites/50 tanks (no unlimited) - Add CSS keyframes for bubble-rise and wave-drift animations
1 parent 47c5ede commit 67266c7

2 files changed

Lines changed: 222 additions & 26 deletions

File tree

pwa/client/src/index.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,33 @@ body {
113113
border-radius: 8px;
114114
}
115115

116+
/* ─── Landing page water effects ─── */
117+
@keyframes bubble-rise {
118+
0% { transform: translateY(0) scale(1); opacity: 0; }
119+
10% { opacity: 0.6; }
120+
90% { opacity: 0.2; }
121+
100% { transform: translateY(-100vh) scale(0.5); opacity: 0; }
122+
}
123+
124+
.landing-bubble {
125+
background: radial-gradient(circle at 30% 30%, rgba(56, 189, 248, 0.4), rgba(14, 165, 233, 0.1));
126+
border: 1px solid rgba(56, 189, 248, 0.15);
127+
animation: bubble-rise linear infinite;
128+
}
129+
130+
@keyframes wave-drift-1 {
131+
0%, 100% { d: path("M0,30 C240,10 480,50 720,30 C960,10 1200,50 1440,30 L1440,60 L0,60Z"); }
132+
50% { d: path("M0,25 C300,50 540,10 780,35 C1020,55 1260,15 1440,25 L1440,60 L0,60Z"); }
133+
}
134+
135+
@keyframes wave-drift-2 {
136+
0%, 100% { d: path("M0,35 C300,55 600,15 900,35 C1200,55 1350,20 1440,35 L1440,60 L0,60Z"); }
137+
50% { d: path("M0,40 C200,20 500,50 800,30 C1100,10 1300,45 1440,40 L1440,60 L0,60Z"); }
138+
}
139+
140+
.landing-wave-1 { animation: wave-drift-1 6s ease-in-out infinite; }
141+
.landing-wave-2 { animation: wave-drift-2 8s ease-in-out infinite; }
142+
116143
/* Scrollbar styling */
117144
::-webkit-scrollbar { width: 4px; }
118145
::-webkit-scrollbar-track { background: transparent; }

pwa/client/src/pages/Landing.jsx

Lines changed: 195 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,172 @@ import { useNavigate } from 'react-router-dom';
77
export default function Landing() {
88
const navigate = useNavigate();
99

10+
// Landing page is always dark — temporarily disable light theme if active
11+
useEffect(() => {
12+
const html = document.documentElement;
13+
const wasLight = html.classList.contains('light');
14+
if (wasLight) html.classList.remove('light');
15+
return () => { if (wasLight) html.classList.add('light'); };
16+
}, []);
17+
1018
return (
1119
<div className="bg-slate-950 text-white overflow-x-hidden">
1220
<Nav onLogin={() => navigate('/login')} />
1321
<Hero onGetStarted={() => navigate('/login')} />
22+
<WaveDivider flip />
1423
<Problem />
24+
<WaveDivider />
1525
<Solution />
26+
<WaveDivider flip />
1627
<HowItWorks />
28+
<WaveDivider />
1729
<Features />
30+
<WaveDivider flip />
1831
<TechSpecs />
32+
<WaveDivider />
1933
<Pricing onGetStarted={() => navigate('/login')} />
34+
<WaveDivider flip />
2035
<Hardware />
2136
<Footer />
2237
</div>
2338
);
2439
}
2540

41+
// ─── SVG ICONS ─────────────────────────────────────────────────────────────
42+
const icons = {
43+
droplet: (
44+
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#38BDF8" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
45+
<path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z" />
46+
</svg>
47+
),
48+
zap: (
49+
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#38BDF8" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
50+
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
51+
</svg>
52+
),
53+
home: (
54+
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#38BDF8" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
55+
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
56+
<polyline points="9 22 9 12 15 12 15 22" />
57+
</svg>
58+
),
59+
radio: (
60+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#38BDF8" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
61+
<path d="M16.72 11.06A10.94 10.94 0 0 1 19 17.94" />
62+
<path d="M7.28 11.06A10.94 10.94 0 0 0 5 17.94" />
63+
<path d="M14.34 13.5A6.97 6.97 0 0 1 16 17.94" />
64+
<path d="M9.66 13.5A6.97 6.97 0 0 0 8 17.94" />
65+
<circle cx="12" cy="18" r="1" />
66+
<path d="M12 2v10" />
67+
</svg>
68+
),
69+
battery: (
70+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#38BDF8" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
71+
<rect x="1" y="6" width="18" height="12" rx="2" ry="2" />
72+
<line x1="23" y1="10" x2="23" y2="14" />
73+
<rect x="4" y="9" width="6" height="6" rx="1" fill="#38BDF8" opacity="0.3" />
74+
</svg>
75+
),
76+
phone: (
77+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#38BDF8" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
78+
<rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
79+
<line x1="12" y1="18" x2="12.01" y2="18" />
80+
</svg>
81+
),
82+
bell: (
83+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#38BDF8" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
84+
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
85+
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
86+
</svg>
87+
),
88+
chart: (
89+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#38BDF8" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
90+
<line x1="18" y1="20" x2="18" y2="10" />
91+
<line x1="12" y1="20" x2="12" y2="4" />
92+
<line x1="6" y1="20" x2="6" y2="14" />
93+
</svg>
94+
),
95+
homeAssistant: (
96+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#38BDF8" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
97+
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
98+
<circle cx="12" cy="14" r="3" />
99+
<path d="M12 11v-1" />
100+
<path d="M14.6 12.5l.8-.5" />
101+
<path d="M14.6 15.5l.8.5" />
102+
<path d="M12 17v1" />
103+
<path d="M9.4 15.5l-.8.5" />
104+
<path d="M9.4 12.5l-.8-.5" />
105+
</svg>
106+
),
107+
shield: (
108+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#38BDF8" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
109+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
110+
<path d="M9 12l2 2 4-4" />
111+
</svg>
112+
),
113+
layers: (
114+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#38BDF8" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
115+
<polygon points="12 2 2 7 12 12 22 7 12 2" />
116+
<polyline points="2 17 12 22 22 17" />
117+
<polyline points="2 12 12 17 22 12" />
118+
</svg>
119+
),
120+
refresh: (
121+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#38BDF8" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
122+
<path d="M21 2v6h-6" />
123+
<path d="M3 12a9 9 0 0 1 15-6.7L21 8" />
124+
<path d="M3 22v-6h6" />
125+
<path d="M21 12a9 9 0 0 1-15 6.7L3 16" />
126+
</svg>
127+
),
128+
};
129+
130+
// ─── WAVE DIVIDER ──────────────────────────────────────────────────────────
131+
function WaveDivider({ flip = false }) {
132+
return (
133+
<div className={`relative w-full h-16 sm:h-24 overflow-hidden ${flip ? 'rotate-180' : ''}`}>
134+
<svg className="absolute bottom-0 w-full h-full" viewBox="0 0 1440 100" preserveAspectRatio="none" fill="none">
135+
<path d="M0,40 C360,100 1080,0 1440,60 L1440,100 L0,100Z" fill="#0EA5E9" opacity="0.05" />
136+
<path d="M0,60 C480,10 960,90 1440,40 L1440,100 L0,100Z" fill="#0EA5E9" opacity="0.03" />
137+
</svg>
138+
</div>
139+
);
140+
}
141+
142+
// ─── FLOATING BUBBLES ──────────────────────────────────────────────────────
143+
function Bubbles() {
144+
return (
145+
<div className="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
146+
{[...Array(12)].map((_, i) => (
147+
<div
148+
key={i}
149+
className="landing-bubble absolute rounded-full"
150+
style={{
151+
width: `${6 + (i % 5) * 4}px`,
152+
height: `${6 + (i % 5) * 4}px`,
153+
left: `${5 + (i * 8) % 90}%`,
154+
bottom: `-${10 + (i * 7) % 20}%`,
155+
animationDelay: `${i * 1.2}s`,
156+
animationDuration: `${8 + (i % 4) * 3}s`,
157+
}}
158+
/>
159+
))}
160+
</div>
161+
);
162+
}
163+
164+
// ─── WATER SURFACE ─────────────────────────────────────────────────────────
165+
function WaterSurface({ className = '' }) {
166+
return (
167+
<div className={`absolute left-0 right-0 overflow-hidden pointer-events-none ${className}`} aria-hidden="true">
168+
<svg className="w-full landing-water-surface" viewBox="0 0 1440 60" preserveAspectRatio="none">
169+
<path className="landing-wave-1" d="M0,30 C240,10 480,50 720,30 C960,10 1200,50 1440,30 L1440,60 L0,60Z" fill="#0EA5E9" opacity="0.06" />
170+
<path className="landing-wave-2" d="M0,35 C300,55 600,15 900,35 C1200,55 1350,20 1440,35 L1440,60 L0,60Z" fill="#38BDF8" opacity="0.04" />
171+
</svg>
172+
</div>
173+
);
174+
}
175+
26176
// ─── ANIMATIONS ─────────────────────────────────────────────────────────────
27177
function FadeIn({ children, className = '', delay = 0 }) {
28178
const ref = useRef(null);
@@ -80,6 +230,12 @@ function Hero({ onGetStarted }) {
80230
<div className="absolute inset-0 bg-gradient-to-b from-sky-500/10 via-transparent to-transparent" />
81231
<div className="absolute top-1/3 left-1/2 -translate-x-1/2 w-[600px] h-[600px] bg-sky-500/5 rounded-full blur-3xl" />
82232

233+
{/* Floating bubbles */}
234+
<Bubbles />
235+
236+
{/* Water surface at bottom */}
237+
<WaterSurface className="bottom-0 h-20" />
238+
83239
<FadeIn>
84240
<div className="inline-flex items-center gap-2 bg-sky-500/10 border border-sky-500/20 rounded-full px-4 py-1.5 mb-8">
85241
<div className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse" />
@@ -116,7 +272,6 @@ function Hero({ onGetStarted }) {
116272
</FadeIn>
117273

118274
<FadeIn delay={500} className="mt-16 w-full max-w-4xl">
119-
{/* PLACEHOLDER: Dashboard screenshot mockup on phone/laptop */}
120275
<div className="relative mx-auto">
121276
<div className="bg-slate-900 rounded-2xl border border-slate-800 p-4 shadow-2xl shadow-sky-500/10">
122277
<div className="bg-slate-800 rounded-xl aspect-[16/9] flex items-center justify-center">
@@ -138,13 +293,25 @@ function Hero({ onGetStarted }) {
138293
// ─── PROBLEM ────────────────────────────────────────────────────────────────
139294
function Problem() {
140295
const stats = [
141-
{ value: '135L', label: 'water wasted daily per household due to overflow', icon: '💧' },
142-
{ value: '23%', label: 'of water pumps fail from running dry', icon: '⚡' },
143-
{ value: '2x', label: 'daily roof climbs to check water level', icon: '🏠' },
296+
{
297+
value: '135L',
298+
label: 'water wasted daily per household due to overflow',
299+
icon: icons.droplet,
300+
},
301+
{
302+
value: '23%',
303+
label: 'of water pumps fail from running dry',
304+
icon: icons.zap,
305+
},
306+
{
307+
value: '2x',
308+
label: 'daily roof climbs to check water level',
309+
icon: icons.home,
310+
},
144311
];
145312

146313
return (
147-
<section className="py-24 px-6">
314+
<section className="py-24 px-6 relative">
148315
<div className="max-w-6xl mx-auto">
149316
<FadeIn>
150317
<p className="text-sky-400 font-medium text-sm tracking-wider uppercase mb-4 text-center">The Problem</p>
@@ -161,7 +328,9 @@ function Problem() {
161328
{stats.map((stat, i) => (
162329
<FadeIn key={i} delay={i * 150}>
163330
<div className="bg-slate-900/50 border border-slate-800 rounded-2xl p-8 text-center hover:border-sky-500/30 transition-all">
164-
<div className="text-4xl mb-4">{stat.icon}</div>
331+
<div className="w-14 h-14 rounded-xl bg-sky-500/10 flex items-center justify-center mx-auto mb-4">
332+
{stat.icon}
333+
</div>
165334
<div className="text-4xl font-bold text-white mb-2">{stat.value}</div>
166335
<p className="text-slate-400 text-sm">{stat.label}</p>
167336
</div>
@@ -176,7 +345,8 @@ function Problem() {
176345
// ─── SOLUTION ───────────────────────────────────────────────────────────────
177346
function Solution() {
178347
return (
179-
<section className="py-24 px-6 bg-gradient-to-b from-slate-950 via-slate-900 to-slate-950">
348+
<section className="py-24 px-6 bg-gradient-to-b from-slate-950 via-slate-900 to-slate-950 relative">
349+
<WaterSurface className="top-0 h-16 opacity-50" />
180350
<div className="max-w-6xl mx-auto grid md:grid-cols-2 gap-16 items-center">
181351
<FadeIn>
182352
<p className="text-sky-400 font-medium text-sm tracking-wider uppercase mb-4">The Solution</p>
@@ -208,7 +378,6 @@ function Solution() {
208378
</FadeIn>
209379

210380
<FadeIn delay={200}>
211-
{/* PLACEHOLDER: Product photo — receiver + transmitter */}
212381
<div className="bg-slate-800 rounded-2xl aspect-square flex items-center justify-center border border-slate-700">
213382
<div className="text-center p-8">
214383
<p className="text-slate-500 text-sm">Product photo placeholder</p>
@@ -254,7 +423,7 @@ function HowItWorks() {
254423

255424
<div className="space-y-24">
256425
{steps.map((step, i) => (
257-
<div key={i} className={`grid md:grid-cols-2 gap-12 items-center ${i % 2 === 1 ? 'md:flex-row-reverse' : ''}`}>
426+
<div key={i} className={`grid md:grid-cols-2 gap-12 items-center`}>
258427
<FadeIn className={i % 2 === 1 ? 'md:order-2' : ''}>
259428
<div className="text-sky-500 font-mono text-6xl font-bold opacity-20 mb-4">{step.num}</div>
260429
<h3 className="text-2xl font-bold mb-4">{step.title}</h3>
@@ -276,19 +445,20 @@ function HowItWorks() {
276445
// ─── FEATURES ───────────────────────────────────────────────────────────────
277446
function Features() {
278447
const features = [
279-
{ icon: '📡', title: 'Long Range LoRa', desc: 'Up to 5km line-of-sight. Works through walls and across buildings.' },
280-
{ icon: '🔋', title: 'Low Power', desc: '6+ months battery life with deep sleep. Solar charging optional.' },
281-
{ icon: '📱', title: 'Real-Time Dashboard', desc: 'See water levels, battery, signal strength — all from your phone.' },
282-
{ icon: '🔔', title: 'Smart Alerts', desc: 'Low water, overflow risk, battery low, device offline — push to your phone.' },
283-
{ icon: '📊', title: 'Usage History', desc: 'Track water consumption patterns. Know when to schedule delivery.' },
284-
{ icon: '🏠', title: 'Home Assistant', desc: 'Native MQTT integration. Auto-discovery for seamless smart home setup.' },
285-
{ icon: '🔒', title: 'Secure by Design', desc: 'MQTT over TLS, per-user credentials, encrypted cloud connection.' },
286-
{ icon: '📦', title: 'Multi-Tank', desc: 'Monitor up to 10 tanks from one receiver. Perfect for farms and buildings.' },
287-
{ icon: '🔄', title: 'OTA Updates', desc: 'Update firmware wirelessly. New features delivered over the air.' },
448+
{ icon: icons.radio, title: 'Long Range LoRa', desc: 'Up to 5km line-of-sight. Works through walls and across buildings.' },
449+
{ icon: icons.battery, title: 'Low Power', desc: '6+ months battery life with deep sleep. Solar charging optional.' },
450+
{ icon: icons.phone, title: 'Real-Time Dashboard', desc: 'See water levels, battery, signal strength — all from your phone.' },
451+
{ icon: icons.bell, title: 'Smart Alerts', desc: 'Low water, overflow risk, battery low, device offline — push to your phone.' },
452+
{ icon: icons.chart, title: 'Usage History', desc: 'Track water consumption patterns. Know when to schedule delivery.' },
453+
{ icon: icons.homeAssistant, title: 'Home Assistant', desc: 'Native MQTT integration. Auto-discovery for seamless smart home setup.' },
454+
{ icon: icons.shield, title: 'Secure by Design', desc: 'MQTT over TLS, per-user credentials, encrypted cloud connection.' },
455+
{ icon: icons.layers, title: 'Multi-Tank', desc: 'Monitor up to 10 tanks from one receiver. Perfect for farms and buildings.' },
456+
{ icon: icons.refresh, title: 'OTA Updates', desc: 'Update firmware wirelessly. New features delivered over the air.' },
288457
];
289458

290459
return (
291-
<section id="features" className="py-24 px-6 bg-gradient-to-b from-slate-950 via-slate-900 to-slate-950">
460+
<section id="features" className="py-24 px-6 bg-gradient-to-b from-slate-950 via-slate-900 to-slate-950 relative">
461+
<WaterSurface className="bottom-0 h-16 opacity-40" />
292462
<div className="max-w-6xl mx-auto">
293463
<FadeIn>
294464
<p className="text-sky-400 font-medium text-sm tracking-wider uppercase mb-4 text-center">Features</p>
@@ -299,7 +469,9 @@ function Features() {
299469
{features.map((f, i) => (
300470
<FadeIn key={i} delay={i * 80}>
301471
<div className="bg-slate-900/50 border border-slate-800 rounded-2xl p-6 hover:border-sky-500/30 hover:bg-slate-900 transition-all group">
302-
<div className="text-3xl mb-4 group-hover:scale-110 transition-transform">{f.icon}</div>
472+
<div className="w-12 h-12 rounded-xl bg-sky-500/10 flex items-center justify-center mb-4 group-hover:bg-sky-500/20 transition-colors">
473+
{f.icon}
474+
</div>
303475
<h3 className="font-semibold text-lg mb-2">{f.title}</h3>
304476
<p className="text-slate-400 text-sm leading-relaxed">{f.desc}</p>
305477
</div>
@@ -375,7 +547,6 @@ function TechSpecs() {
375547

376548
// ─── PRICING ────────────────────────────────────────────────────────────────
377549
function Pricing({ onGetStarted }) {
378-
// Detect India by timezone (simple, no API call)
379550
const isIndia = Intl.DateTimeFormat().resolvedOptions().timeZone?.startsWith('Asia/Calcutta') ||
380551
Intl.DateTimeFormat().resolvedOptions().timeZone?.startsWith('Asia/Kolkata');
381552

@@ -415,13 +586,13 @@ function Pricing({ onGetStarted }) {
415586
},
416587
{
417588
name: 'Pro',
418-
price: isIndia ? '299' : '4.99',
589+
price: isIndia ? '599' : '9.99',
419590
currency: isIndia ? '₹' : '$',
420591
period: '/month',
421-
annual: isIndia ? '₹2,499/year (save 30%)' : '$49/year (save 18%)',
592+
annual: isIndia ? '₹4,999/year (save 30%)' : '$99/year (save 17%)',
422593
desc: 'For installers and property managers',
423594
features: [
424-
'Unlimited sites and tanks',
595+
'Up to 10 sites, 50 tanks',
425596
'1-year history',
426597
'Fleet dashboard',
427598
'Client sharing (read-only links)',
@@ -513,7 +684,6 @@ function Hardware() {
513684
<div className="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto">
514685
<FadeIn>
515686
<div className="bg-slate-900/50 border border-slate-800 rounded-2xl overflow-hidden hover:border-sky-500/30 transition-all">
516-
{/* PLACEHOLDER: Single tank kit photo */}
517687
<div className="bg-slate-800 aspect-[4/3] flex items-center justify-center">
518688
<p className="text-slate-600 text-xs">Single Tank Kit photo — 800x600</p>
519689
</div>
@@ -534,7 +704,6 @@ function Hardware() {
534704

535705
<FadeIn delay={150}>
536706
<div className="bg-slate-900/50 border border-slate-800 rounded-2xl overflow-hidden hover:border-sky-500/30 transition-all">
537-
{/* PLACEHOLDER: Dual tank kit photo */}
538707
<div className="bg-slate-800 aspect-[4/3] flex items-center justify-center">
539708
<p className="text-slate-600 text-xs">Dual Tank Kit photo — 800x600</p>
540709
</div>

0 commit comments

Comments
 (0)