Skip to content

Commit cd30484

Browse files
committed
added ribbon
1 parent 1052ac9 commit cd30484

File tree

5 files changed

+279
-12
lines changed

5 files changed

+279
-12
lines changed

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,19 @@
5050
"framer-motion": "^12.23.24",
5151
"i18next": "^25.0.1",
5252
"i18next-browser-languagedetector": "^8.0.5",
53-
"input-otp": "^1.2.4",
53+
"input-t-icons": "^5.5.0",
54+
"react-resizable-panels": "^2.1.3",
55+
"react-router-dom": "^6.26.2",
56+
"recharts": "^2.12.7",
57+
"sonotp": "^1.2.4",
5458
"lucide-react": "^0.462.0",
5559
"next-themes": "^0.3.0",
5660
"react": "^18.3.1",
5761
"react-day-picker": "^8.10.1",
5862
"react-dom": "^18.3.1",
5963
"react-hook-form": "^7.53.0",
6064
"react-i18next": "^15.4.1",
61-
"react-icons": "^5.5.0",
62-
"react-resizable-panels": "^2.1.3",
63-
"react-router-dom": "^6.26.2",
64-
"recharts": "^2.12.7",
65-
"sonner": "^1.5.0",
65+
"reacner": "^1.5.0",
6666
"tailwind-merge": "^2.5.2",
6767
"tailwindcss-animate": "^1.0.7",
6868
"twilio": "^5.5.2",

src/components/OfferRibbon.css

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
.offer-ribbon {
2+
width: 100%;
3+
min-height: 2cm;
4+
overflow: hidden;
5+
display: flex;
6+
align-items: center;
7+
background: #ffffff;
8+
border-top: 1px solid rgba(2,6,23,0.04);
9+
border-bottom: 1px solid rgba(2,6,23,0.04);
10+
padding-top: 1rem;
11+
padding-bottom: 1rem;
12+
}
13+
14+
.offer-track {
15+
display: flex;
16+
width: 220%;
17+
align-items: center;
18+
animation: slideRight 15s linear infinite; /* slower for better readability */
19+
}
20+
21+
.offer-ribbon:hover .offer-track {
22+
animation-play-state: paused; /* pause on hover for readability */
23+
}
24+
25+
.offer-group {
26+
display: flex;
27+
gap: 3rem; /* more breathing room between cards */
28+
align-items: center;
29+
padding-left: 2rem;
30+
}
31+
32+
.offer-group + .offer-group {
33+
margin-left: 4rem; /* extra space between duplicated sequences for visual gap */
34+
}
35+
36+
.offer-card {
37+
display: inline-flex;
38+
align-items: center;
39+
gap: 1.6rem; /* doubled gap */
40+
min-width: 300px; /* double size */
41+
padding: 1rem 2rem; /* double padding */
42+
border-radius: 9999px; /* pill */
43+
background: #f8fbff; /* very light blue/white to sit on white background */
44+
border: 1px solid rgba(2,6,23,0.04);
45+
color: #05295f; /* deep blue text */
46+
box-shadow: 0 8px 30px rgba(2,6,23,0.06);
47+
transform: translateY(0);
48+
transition: transform 220ms cubic-bezier(.2,.9,.2,1), box-shadow 220ms;
49+
white-space: nowrap;
50+
}
51+
52+
.offer-card:hover {
53+
transform: translateY(-10px) scale(1.04); /* slightly larger lift */
54+
box-shadow: 0 18px 40px rgba(2,6,23,0.18);
55+
}
56+
57+
.icon-box {
58+
width: 80px; /* double icon area */
59+
height: 80px;
60+
border-radius: 12px;
61+
display: flex;
62+
align-items: center;
63+
justify-content: center;
64+
/* gradient is applied inline per-offer to keep them colorful */
65+
color: #fff; /* icon stays white on colorful gradient */
66+
border: 1px solid rgba(2,6,23,0.04);
67+
}
68+
69+
.offer-text {
70+
display: flex;
71+
flex-direction: column;
72+
}
73+
74+
.offer-title {
75+
font-weight: 800;
76+
font-size: 1.9rem; /* double font size */
77+
line-height: 1;
78+
color: #05295f;
79+
}
80+
81+
.offer-sub {
82+
font-size: 1rem; /* larger subtitle */
83+
opacity: 0.95;
84+
color: #0b4bd6;
85+
}
86+
87+
.offer-badge {
88+
margin-left: 0.6rem;
89+
background: linear-gradient(90deg,#eef7ff,#d9efff);
90+
color: #fff; /* badges use white now because we set inline bg colors earlier */
91+
padding: 0.4rem 0.9rem; /* larger badge padding */
92+
border-radius: 9999px;
93+
font-weight: 800;
94+
font-size: 0.95rem;
95+
border: 1px solid rgba(2,6,23,0.04);
96+
}
97+
98+
/* stagger vertical position so cards don't look like a single row of boxes */
99+
.offer-group .offer-card:nth-child(3n+1) { transform: translateY(-16px); }
100+
.offer-group .offer-card:nth-child(3n+2) { transform: translateY(0); }
101+
.offer-group .offer-card:nth-child(3n) { transform: translateY(12px); }
102+
103+
104+
@keyframes slideRight {
105+
0% { transform: translateX(-50%); }
106+
100% { transform: translateX(0%); }
107+
}
108+
109+
/* responsive tweaks */
110+
@media (max-width: 1024px) {
111+
.offer-track { animation-duration: 30s; }
112+
.offer-group { gap: 2rem; padding-left: 1.25rem; }
113+
.offer-card { min-width: 220px; padding: 0.75rem 1.25rem; gap: 1rem; }
114+
.icon-box { width: 64px; height: 64px; }
115+
.icon-box svg { width: 36px; height: 36px; }
116+
.offer-title { font-size: 1.4rem; }
117+
.offer-sub { font-size: 0.95rem; }
118+
.offer-badge { padding: 0.32rem 0.7rem; font-size: 0.9rem; }
119+
.offer-group + .offer-group { margin-left: 3rem; }
120+
.offer-group .offer-card:nth-child(3n+1) { transform: translateY(-12px); }
121+
.offer-group .offer-card:nth-child(3n) { transform: translateY(8px); }
122+
}
123+
124+
@media (max-width: 640px) {
125+
.offer-ribbon { padding-top: 0.6rem; padding-bottom: 0.6rem; }
126+
.offer-track { animation: slideRight 10s linear infinite; width: 240%; }
127+
.offer-group { gap: 1rem; padding-left: 0.75rem; }
128+
.offer-card { min-width: 160px; padding: 0.5rem 0.9rem; gap: 0.6rem; }
129+
.icon-box { width: 44px; height: 44px; border-radius: 8px; }
130+
.icon-box svg { width: 22px; height: 22px; }
131+
.offer-title { font-size: 1rem; }
132+
.offer-sub { font-size: 0.75rem; }
133+
.offer-badge { padding: 0.18rem 0.5rem; font-size: 0.78rem; }
134+
.offer-card:hover { transform: translateY(-6px) scale(1.02); }
135+
136+
/* reduce staggered offsets on small screens */
137+
.offer-group .offer-card:nth-child(3n+1) { transform: translateY(-8px); }
138+
.offer-group .offer-card:nth-child(3n+2) { transform: translateY(0); }
139+
.offer-group .offer-card:nth-child(3n) { transform: translateY(6px); }
140+
}
141+
142+
/* svg sizing fallback for icons (in case inline classes vary) */
143+
.icon-box svg { width: 40px; height: 40px; }

src/components/OfferRibbon.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React from 'react';
2+
import './OfferRibbon.css';
3+
import { Gift, Percent, Star, Sparkles, Zap } from 'lucide-react';
4+
5+
const offers = [
6+
{ icon: Gift, title: 'Festival Sale', sub: 'Up to 50% off', badge: 'Limited', from: '#ff7a18', to: '#ffb199' },
7+
{ icon: Percent, title: 'New User', sub: '₹500 OFF', badge: 'New', from: '#00c6ff', to: '#0072ff' },
8+
{ icon: Star, title: 'Top Rated', sub: 'Verified pros', badge: 'Trusted', from: '#7b61ff', to: '#a78bfa' },
9+
{ icon: Sparkles, title: 'Diwali Special', sub: 'Gifts & deals', badge: 'Festival', from: '#ff9068', to: '#ff4d6d' },
10+
{ icon: Zap, title: 'Flash Deal', sub: 'Today only', badge: 'Hurry', from: '#00d2ff', to: '#3a7bd5' },
11+
];
12+
13+
const OfferRibbon: React.FC = () => {
14+
return (
15+
<div className="offer-ribbon hero-pattern from-primary/5 to-background py-8" role="region" aria-label="Offers ribbon">
16+
<div className="offer-track" aria-hidden={false}>
17+
<div className="offer-group">
18+
{offers.map((o, i) => {
19+
const Icon = o.icon;
20+
return (
21+
<div className="offer-card" key={i}>
22+
<div
23+
className="icon-box"
24+
style={{
25+
background: `linear-gradient(135deg, ${o.from}, ${o.to})`,
26+
boxShadow: `0 6px 18px ${o.to}33`,
27+
}}
28+
>
29+
<Icon className="w-8 h-8 text-white" />
30+
</div>
31+
<div className="offer-text">
32+
<div className="offer-title">{o.title}</div>
33+
<div className="offer-sub">{o.sub}</div>
34+
</div>
35+
<div
36+
className="offer-badge"
37+
style={{ background: o.from, color: '#fff', borderColor: 'transparent' }}
38+
>
39+
{o.badge}
40+
</div>
41+
</div>
42+
);
43+
})}
44+
</div>
45+
46+
{/* duplicate for seamless loop */}
47+
<div className="offer-group" aria-hidden="true">
48+
{offers.map((o, i) => {
49+
const Icon = o.icon;
50+
return (
51+
<div className="offer-card" key={`dup-${i}`}>
52+
<div
53+
className="icon-box"
54+
style={{
55+
background: `linear-gradient(135deg, ${o.from}, ${o.to})`,
56+
boxShadow: `0 6px 18px ${o.to}33`,
57+
}}
58+
>
59+
<Icon className="w-8 h-8 text-white" />
60+
</div>
61+
<div className="offer-text">
62+
<div className="offer-title">{o.title}</div>
63+
<div className="offer-sub">{o.sub}</div>
64+
</div>
65+
<div
66+
className="offer-badge"
67+
style={{ background: o.from, color: '#fff', borderColor: 'transparent' }}
68+
>
69+
{o.badge}
70+
</div>
71+
</div>
72+
);
73+
})}
74+
</div>
75+
</div>
76+
</div>
77+
);
78+
};
79+
80+
export default OfferRibbon;

src/index.css

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,46 @@
275275
.animate-slideOutToRight {
276276
animation: slideOutToRight 0.3s ease-in forwards;
277277
}
278+
279+
/* Offer Ribbon Animations */
280+
@keyframes scroll-left {
281+
0% {
282+
transform: translateX(0);
283+
}
284+
100% {
285+
transform: translateX(-33.333%);
286+
}
287+
}
288+
289+
@keyframes shimmer {
290+
0% {
291+
transform: translateX(-100%);
292+
}
293+
100% {
294+
transform: translateX(100%);
295+
}
296+
}
297+
298+
@keyframes pulse-slow {
299+
0%, 100% {
300+
opacity: 1;
301+
transform: scale(1);
302+
}
303+
50% {
304+
opacity: 0.8;
305+
transform: scale(1.1);
306+
}
307+
}
308+
309+
.animate-scroll-left {
310+
animation: scroll-left 30s linear infinite;
311+
}
312+
313+
.animate-shimmer {
314+
animation: shimmer 3s ease-in-out infinite;
315+
}
316+
317+
.animate-pulse-slow {
318+
animation: pulse-slow 2s ease-in-out infinite;
319+
}
278320
}

src/pages/Index.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,22 @@ import FAQ from "../components/FAQ";
1313
import Bmodel from "@/components/Bmodel";
1414
import VideoTestimonials from "@/components/VideoTestimonials";
1515
import Solutions from "@/components/Solutions";
16+
import OfferRibbon from "@/components/OfferRibbon";
1617
// import { Banner } from "@/components/Banner";
1718

1819
const Index: React.FC = () => {
1920
// Smooth scroll functionality
2021
useEffect(() => {
2122
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
22-
anchor.addEventListener('click', function(e) {
23+
anchor.addEventListener('click', function (e) {
2324
e.preventDefault();
2425
const targetId = this.getAttribute('href');
2526
if (targetId && targetId !== "#") {
2627
const targetElement = document.querySelector(targetId);
2728
if (targetElement) {
2829
const navbarHeight = 80; // Approximate navbar height
2930
const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - navbarHeight;
30-
31+
3132
window.scrollTo({
3233
top: targetPosition,
3334
behavior: 'smooth'
@@ -39,7 +40,7 @@ const Index: React.FC = () => {
3940

4041
return () => {
4142
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
42-
anchor.removeEventListener('click', function(e) {});
43+
anchor.removeEventListener('click', function (e) { });
4344
});
4445
};
4546
}, []);
@@ -49,9 +50,10 @@ const Index: React.FC = () => {
4950
<Navbar />
5051
<Hero />
5152
{/* <Banner/> */}
52-
<Bmodel/>
53-
<HeroForm/>
54-
<Solutions/>
53+
<Bmodel />
54+
<OfferRibbon />
55+
<HeroForm />
56+
<Solutions />
5557
<WorkerCategories />
5658
{/* <ServiceCategories /> */}
5759
<HowItWorks />

0 commit comments

Comments
 (0)