apex-ui-kit 2.0.7 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/ApexUI-Kit/templates/AccordionMarquee/AccordionMarquee.jsx +74 -53
  2. package/dist/ApexUI-Kit/templates/BeamBar/BeamBar.jsx +5 -3
  3. package/dist/ApexUI-Kit/templates/BluePrintBackground/BluePrintBackground.jsx +14 -2
  4. package/dist/ApexUI-Kit/templates/CardStack/CardStack.jsx +56 -28
  5. package/dist/ApexUI-Kit/templates/Cards/BasicCard.jsx +3 -3
  6. package/dist/ApexUI-Kit/templates/DragCarousel/DragCarousel.jsx +1 -1
  7. package/dist/ApexUI-Kit/templates/EdgeGlowForm/EdgeGlowFrom.jsx +116 -98
  8. package/dist/ApexUI-Kit/templates/FlipProfile/FlipProfile.jsx +70 -37
  9. package/dist/ApexUI-Kit/templates/GlareCard/GlareCard.jsx +2 -2
  10. package/dist/ApexUI-Kit/templates/HoverText/HoverText.jsx +14 -13
  11. package/dist/ApexUI-Kit/templates/HyperCard/HyperCard.jsx +59 -42
  12. package/dist/ApexUI-Kit/templates/KineticThreadsBackground/KineticThreadBackground.jsx +17 -2
  13. package/dist/ApexUI-Kit/templates/LensFlareBackground/LensFlareBackground.jsx +18 -3
  14. package/dist/ApexUI-Kit/templates/LuminousParticleOcean/LuminousParticleOcean.jsx +15 -1
  15. package/dist/ApexUI-Kit/templates/MaskView/MaskView.jsx +345 -0
  16. package/dist/ApexUI-Kit/templates/MeteorCard/MeteorCard.jsx +34 -45
  17. package/dist/ApexUI-Kit/templates/NavDropDown/NavDropDown.jsx +272 -101
  18. package/dist/ApexUI-Kit/templates/NavMenu/NavMenu.jsx +8 -2
  19. package/dist/ApexUI-Kit/templates/OrbsBackground/FloatingOrbs.jsx +59 -59
  20. package/dist/ApexUI-Kit/templates/ParallaxCard/ParallaxCard.jsx +13 -11
  21. package/dist/ApexUI-Kit/templates/ParallaxCarousel/ParallaxCarousel.jsx +9 -9
  22. package/dist/ApexUI-Kit/templates/ParallaxProfile/ParallaxProfile.jsx +41 -38
  23. package/dist/ApexUI-Kit/templates/PointerFollower/PointerFollower.jsx +7 -7
  24. package/dist/ApexUI-Kit/templates/SmartGridCard/SmartGridCard.jsx +13 -11
  25. package/dist/ApexUI-Kit/templates/SmokeBackground/SmokeBackground.jsx +13 -2
  26. package/dist/ApexUI-Kit/templates/Testimonials/Testimonials.jsx +11 -8
  27. package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeAnimation.js +40 -69
  28. package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.css +1 -1
  29. package/dist/ApexUI-Kit/templates/ThemeToggle/ThemeToggle.jsx +25 -7
  30. package/dist/ApexUI-Kit/templates/ToolTip/ToolTip.jsx +71 -52
  31. package/dist/cli/index.js +24 -13
  32. package/package.json +2 -2
  33. package/dist/ApexUI-Kit/templates/WaterDropReveal/WaterDropReveal.jsx +0 -151
@@ -0,0 +1,345 @@
1
+ import React, { useState, useRef, useEffect } from "react";
2
+ import gsap from "gsap";
3
+
4
+ /*
5
+ * Mask View: three ways to reveal content through a mask.
6
+ * variant="window" – big letters are a window onto an image; it pans with the pointer, tap opens it fully.
7
+ * variant="spotlight" – near-hidden text lit by a soft spotlight that follows the pointer (wanders on its own when idle).
8
+ * variant="wipe" – text under steamed-up glass; wipe it clear with the pointer or a finger, it slowly fogs back.
9
+ */
10
+
11
+ const DEFAULT_IMAGE = "https://ik.imagekit.io/ny6yinyut/apexui/assets/ironman.png?updatedAt=1792562203671";
12
+ const DEFAULT_TEXT = {
13
+ window: "MASK\nVIEW",
14
+ spotlight: "Look through the mask.\nOnly what sits under the light\ncomes into focus.",
15
+ wipe: "Wipe the glass.\nThe words underneath\nare waiting for you.",
16
+ };
17
+
18
+ const textClass = "text-center text-2xl sm:text-4xl font-semibold leading-tight tracking-tight";
19
+
20
+ const Lines = ({ text }) => text.split("\n").map((line, i) => <span key={i} className="block">{line}</span>);
21
+
22
+ // Shared card: dark panel, corner label and a hint that hides while the effect is in use.
23
+ const Frame = ({ boxRef, accent, label, hint, active, className = "", children, ...handlers }) => (
24
+ <div
25
+ ref={boxRef}
26
+ className={`relative isolate w-full max-w-2xl min-h-[300px] overflow-hidden rounded-3xl border border-white/10 bg-[#07070c] select-none ${className}`}
27
+ {...handlers}
28
+ >
29
+ <div
30
+ className="pointer-events-none absolute inset-0 opacity-[0.06]"
31
+ style={{ backgroundImage: "radial-gradient(rgba(255,255,255,0.9) 1px, transparent 1px)", backgroundSize: "18px 18px" }}
32
+ />
33
+ {children}
34
+ <span className="pointer-events-none absolute left-5 top-4 z-30 flex items-center gap-2 font-mono text-[10px] uppercase tracking-[0.2em] text-white/45">
35
+ <span className="h-1.5 w-1.5 rounded-full" style={{ backgroundColor: accent }} />
36
+ {label}
37
+ </span>
38
+ <span
39
+ className={`pointer-events-none absolute bottom-4 left-1/2 z-30 -translate-x-1/2 whitespace-nowrap rounded-full border border-white/10 bg-black/40 px-3.5 py-1.5 text-xs text-white/65 backdrop-blur-md transition-all duration-500 ${active ? "translate-y-3 opacity-0" : "opacity-100"}`}
40
+ >
41
+ {hint}
42
+ </span>
43
+ </div>
44
+ );
45
+
46
+ /* ---------------------------------------------------------------- window */
47
+ const TextWindow = ({ text, image, accent, speed }) => {
48
+ const boxRef = useRef(null);
49
+ const pan = useRef({ x: 50, y: 50 });
50
+ const [hovered, setHovered] = useState(false);
51
+ const [open, setOpen] = useState(false);
52
+ const [origin, setOrigin] = useState({ x: 50, y: 50 });
53
+
54
+ const paint = () => {
55
+ boxRef.current?.style.setProperty("--px", `${pan.current.x}%`);
56
+ boxRef.current?.style.setProperty("--py", `${pan.current.y}%`);
57
+ };
58
+ useEffect(() => {
59
+ const state = pan.current;
60
+ paint();
61
+ return () => gsap.killTweensOf(state);
62
+ }, []);
63
+
64
+ const handleMove = (e) => {
65
+ const rect = boxRef.current.getBoundingClientRect();
66
+ // Pan the image the opposite way to the pointer, for a little depth.
67
+ gsap.to(pan.current, {
68
+ x: 100 - ((e.clientX - rect.left) / rect.width) * 100,
69
+ y: 100 - ((e.clientY - rect.top) / rect.height) * 100,
70
+ duration: speed * 2,
71
+ ease: "power3.out",
72
+ overwrite: "auto",
73
+ onUpdate: paint,
74
+ });
75
+ };
76
+ const handleLeave = () => {
77
+ setHovered(false);
78
+ gsap.to(pan.current, { x: 50, y: 50, duration: 0.9, ease: "power3.out", onUpdate: paint });
79
+ };
80
+ const handleClick = (e) => {
81
+ const rect = boxRef.current.getBoundingClientRect();
82
+ setOrigin({ x: ((e.clientX - rect.left) / rect.width) * 100, y: ((e.clientY - rect.top) / rect.height) * 100 });
83
+ setOpen((o) => !o);
84
+ };
85
+
86
+ return (
87
+ <Frame
88
+ boxRef={boxRef}
89
+ accent={accent}
90
+ label="Mask View"
91
+ hint={open ? "Tap to close" : "Move to pan · tap to open"}
92
+ active={hovered && !open}
93
+ className="cursor-pointer touch-pan-y"
94
+ onPointerEnter={() => setHovered(true)}
95
+ onPointerMove={handleMove}
96
+ onPointerLeave={handleLeave}
97
+ onClick={handleClick}
98
+ >
99
+ {/* Full image, opened with a circle from the tap point */}
100
+ <div
101
+ className="absolute inset-0 z-10 transition-[clip-path] duration-700 ease-[cubic-bezier(0.65,0,0.35,1)]"
102
+ style={{
103
+ backgroundImage: `url("${image}")`,
104
+ backgroundSize: "cover",
105
+ backgroundPosition: "var(--px) var(--py)",
106
+ clipPath: `circle(${open ? "150%" : "0%"} at ${origin.x}% ${origin.y}%)`,
107
+ }}
108
+ />
109
+ {/* Letters as the window */}
110
+ <div className="absolute inset-0 z-20 flex items-center justify-center px-4">
111
+ <p
112
+ className={`text-center text-[clamp(4rem,19vw,9rem)] font-black uppercase leading-[0.85] tracking-[-0.04em] transition-[color,text-shadow] duration-500 ${open ? "text-white [text-shadow:0_8px_40px_rgba(0,0,0,0.6)]" : "text-transparent"}`}
113
+ style={open ? undefined : {
114
+ backgroundImage: `url("${image}")`,
115
+ backgroundSize: "180%",
116
+ backgroundPosition: "var(--px) var(--py)",
117
+ WebkitBackgroundClip: "text",
118
+ backgroundClip: "text",
119
+ WebkitTextStroke: "1px rgba(255,255,255,0.12)",
120
+ }}
121
+ >
122
+ <Lines text={text} />
123
+ </p>
124
+ </div>
125
+ </Frame>
126
+ );
127
+ };
128
+
129
+ /* ------------------------------------------------------------- spotlight */
130
+ const Spotlight = ({ text, accent, speed }) => {
131
+ const boxRef = useRef(null);
132
+ const light = useRef({ x: 0, y: 0, r: 0 });
133
+ const idle = useRef(null);
134
+ const [active, setActive] = useState(false);
135
+
136
+ const paint = () => {
137
+ const el = boxRef.current;
138
+ if (!el) return;
139
+ el.style.setProperty("--x", `${light.current.x}px`);
140
+ el.style.setProperty("--y", `${light.current.y}px`);
141
+ el.style.setProperty("--r", `${light.current.r}px`);
142
+ };
143
+
144
+ // When nobody is pointing, the light drifts slowly across the text.
145
+ const startIdle = () => {
146
+ const el = boxRef.current;
147
+ if (!el) return;
148
+ const w = el.offsetWidth, h = el.offsetHeight;
149
+ idle.current?.kill();
150
+ idle.current = gsap.timeline({ repeat: -1, yoyo: true, defaults: { duration: 2.6, ease: "sine.inOut", onUpdate: paint } })
151
+ .to(light.current, { x: w * 0.3, y: h * 0.42, r: Math.min(120, w * 0.22) })
152
+ .to(light.current, { x: w * 0.7, y: h * 0.58 });
153
+ };
154
+
155
+ useEffect(() => {
156
+ const el = boxRef.current;
157
+ const state = light.current;
158
+ if (!el) return;
159
+ state.x = el.offsetWidth * 0.3;
160
+ state.y = el.offsetHeight * 0.42;
161
+ paint();
162
+ startIdle();
163
+ return () => {
164
+ idle.current?.kill();
165
+ gsap.killTweensOf(state);
166
+ };
167
+ // eslint-disable-next-line react-hooks/exhaustive-deps
168
+ }, []);
169
+
170
+ const handleEnter = () => {
171
+ setActive(true);
172
+ idle.current?.kill();
173
+ const w = boxRef.current.offsetWidth;
174
+ gsap.to(light.current, { r: Math.min(170, w * 0.3), duration: 0.6, ease: "power3.out", onUpdate: paint });
175
+ };
176
+ const handleMove = (e) => {
177
+ if (!active) handleEnter();
178
+ const rect = boxRef.current.getBoundingClientRect();
179
+ gsap.to(light.current, { x: e.clientX - rect.left, y: e.clientY - rect.top, duration: speed, ease: "power3.out", overwrite: "auto", onUpdate: paint });
180
+ };
181
+ const handleLeave = () => {
182
+ setActive(false);
183
+ startIdle();
184
+ };
185
+
186
+ const mask = "radial-gradient(circle var(--r) at var(--x) var(--y), #000 0%, rgba(0,0,0,0.85) 35%, transparent 100%)";
187
+
188
+ return (
189
+ <Frame
190
+ boxRef={boxRef}
191
+ accent={accent}
192
+ label="Spotlight"
193
+ hint="Hover or touch to shine the light"
194
+ active={active}
195
+ className="cursor-none touch-pan-y"
196
+ onPointerEnter={handleEnter}
197
+ onPointerMove={handleMove}
198
+ onPointerLeave={handleLeave}
199
+ onPointerCancel={handleLeave}
200
+ >
201
+ {/* Text in the dark */}
202
+ <div className="absolute inset-0 flex items-center justify-center px-6 sm:px-10">
203
+ <p className={`${textClass} text-white/[0.07]`}><Lines text={text} /></p>
204
+ </div>
205
+ {/* Lit text */}
206
+ <div className="absolute inset-0 flex items-center justify-center px-6 sm:px-10" style={{ WebkitMaskImage: mask, maskImage: mask }} aria-hidden="true">
207
+ <p className={`${textClass} text-white`}><Lines text={text} /></p>
208
+ </div>
209
+ {/* Faint pool of light on the surface */}
210
+ <div
211
+ className="pointer-events-none absolute inset-0"
212
+ style={{ background: "radial-gradient(circle calc(var(--r) * 1.4) at var(--x) var(--y), rgba(255,255,255,0.05), transparent 70%)" }}
213
+ />
214
+ </Frame>
215
+ );
216
+ };
217
+
218
+ /* ------------------------------------------------------------------ wipe */
219
+ const FOG = "#10101a";
220
+
221
+ const WipeReveal = ({ text, accent }) => {
222
+ const boxRef = useRef(null);
223
+ const canvasRef = useRef(null);
224
+ const last = useRef(null);
225
+ const regrowUntil = useRef(0);
226
+ const frame = useRef(0);
227
+ const [active, setActive] = useState(false);
228
+
229
+ useEffect(() => {
230
+ const canvas = canvasRef.current;
231
+ const box = boxRef.current;
232
+ if (!canvas || !box) return;
233
+ const ctx = canvas.getContext("2d");
234
+
235
+ const fill = () => {
236
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
237
+ canvas.width = box.offsetWidth * dpr;
238
+ canvas.height = box.offsetHeight * dpr;
239
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
240
+ ctx.globalCompositeOperation = "source-over";
241
+ ctx.fillStyle = FOG;
242
+ ctx.fillRect(0, 0, box.offsetWidth, box.offsetHeight);
243
+ };
244
+ fill();
245
+ const ro = new ResizeObserver(fill);
246
+ ro.observe(box);
247
+ return () => {
248
+ ro.disconnect();
249
+ cancelAnimationFrame(frame.current);
250
+ };
251
+ }, []);
252
+
253
+ // Fog creeps back over cleared glass for a few seconds after the last wipe.
254
+ const regrow = () => {
255
+ const canvas = canvasRef.current;
256
+ const box = boxRef.current;
257
+ if (!canvas || !box) return;
258
+ const ctx = canvas.getContext("2d");
259
+ ctx.globalCompositeOperation = "source-over";
260
+ ctx.globalAlpha = 0.018;
261
+ ctx.fillStyle = FOG;
262
+ ctx.fillRect(0, 0, box.offsetWidth, box.offsetHeight);
263
+ ctx.globalAlpha = 1;
264
+ frame.current = performance.now() < regrowUntil.current ? requestAnimationFrame(regrow) : 0;
265
+ };
266
+
267
+ const wipe = (x, y) => {
268
+ const ctx = canvasRef.current.getContext("2d");
269
+ const radius = Math.max(36, Math.min(60, boxRef.current.offsetWidth * 0.09));
270
+ const from = last.current ?? { x, y };
271
+ const steps = Math.max(1, Math.ceil(Math.hypot(x - from.x, y - from.y) / (radius / 3)));
272
+ ctx.globalCompositeOperation = "destination-out";
273
+ // Stamp soft brush dots along the path so fast moves leave no gaps.
274
+ for (let i = 1; i <= steps; i++) {
275
+ const px = from.x + ((x - from.x) * i) / steps;
276
+ const py = from.y + ((y - from.y) * i) / steps;
277
+ const brush = ctx.createRadialGradient(px, py, 0, px, py, radius);
278
+ brush.addColorStop(0, "rgba(0,0,0,0.9)");
279
+ brush.addColorStop(1, "rgba(0,0,0,0)");
280
+ ctx.fillStyle = brush;
281
+ ctx.fillRect(px - radius, py - radius, radius * 2, radius * 2);
282
+ }
283
+ last.current = { x, y };
284
+ regrowUntil.current = performance.now() + 4000;
285
+ if (!frame.current) frame.current = requestAnimationFrame(regrow);
286
+ };
287
+
288
+ const handleMove = (e) => {
289
+ const rect = boxRef.current.getBoundingClientRect();
290
+ setActive(true);
291
+ wipe(e.clientX - rect.left, e.clientY - rect.top);
292
+ };
293
+ const handleLeave = () => {
294
+ last.current = null;
295
+ setActive(false);
296
+ };
297
+
298
+ return (
299
+ <Frame
300
+ boxRef={boxRef}
301
+ accent={accent}
302
+ label="Wipe Reveal"
303
+ hint="Wipe the glass with your pointer or finger"
304
+ active={active}
305
+ className="cursor-crosshair touch-pan-y"
306
+ onPointerMove={handleMove}
307
+ onPointerLeave={handleLeave}
308
+ onPointerCancel={handleLeave}
309
+ >
310
+ {/* Clear text under the glass */}
311
+ <div className="absolute inset-0 flex items-center justify-center px-6 sm:px-10">
312
+ <p className={`${textClass} text-white`}><Lines text={text} /></p>
313
+ </div>
314
+ {/* Steam on the glass */}
315
+ <canvas ref={canvasRef} className="pointer-events-none absolute inset-0 h-full w-full opacity-[0.93]" />
316
+ {/* Condensation droplets */}
317
+ <div
318
+ className="pointer-events-none absolute inset-0 opacity-30"
319
+ style={{
320
+ backgroundImage: "radial-gradient(rgba(255,255,255,0.35) 1px, transparent 1.5px), radial-gradient(rgba(255,255,255,0.2) 1px, transparent 1.5px)",
321
+ backgroundSize: "23px 29px, 37px 31px",
322
+ backgroundPosition: "0 0, 11px 7px",
323
+ }}
324
+ />
325
+ </Frame>
326
+ );
327
+ };
328
+
329
+ const MaskView = ({
330
+ variant = "spotlight",
331
+ text,
332
+ image = DEFAULT_IMAGE,
333
+ accentColor,
334
+ dropColor, // older name for accentColor, still accepted
335
+ animationSpeed = 0.4,
336
+ }) => {
337
+ const kind = DEFAULT_TEXT[variant] ? variant : "spotlight";
338
+ const content = text ?? DEFAULT_TEXT[kind];
339
+ const accent = accentColor ?? dropColor ?? "#a855f7";
340
+ if (kind === "window") return <TextWindow text={content} image={image} accent={accent} speed={animationSpeed} />;
341
+ if (kind === "wipe") return <WipeReveal text={content} accent={accent} />;
342
+ return <Spotlight text={content} accent={accent} speed={animationSpeed} />;
343
+ };
344
+
345
+ export default MaskView;
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import './MeteorCard.css';
3
3
  const ArrowRightIcon = () => (
4
4
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
@@ -11,51 +11,40 @@ const ArrowRightIcon = () => (
11
11
  );
12
12
 
13
13
  // Meteor Effect Component
14
+ // Positions are picked once (useMemo), so re-renders don't make the meteors jump.
14
15
  const Meteors = ({ number = 20 }) => {
15
- const meteors = [...Array(number)].map((_, i) => (
16
- <span
17
- key={i}
18
- className="meteor-bg"
19
- style={{
20
- top: `${Math.random() * 100}%`,
21
- left: `${Math.random() * 100}%`,
22
- animationDelay: `${Math.random() * 8}s`,
23
- animationDuration: `${Math.floor(Math.random() * 6) + 4}s`,
24
- }}
25
- ></span>
26
- ));
27
- return <>{meteors}</>;
16
+ const meteors = useMemo(() => [...Array(number)].map(() => ({
17
+ top: `${Math.random() * 100}%`,
18
+ left: `${Math.random() * 100}%`,
19
+ animationDelay: `${Math.random() * 8}s`,
20
+ animationDuration: `${Math.floor(Math.random() * 6) + 4}s`,
21
+ })), [number]);
22
+ return <>{meteors.map((style, i) => <span key={i} className="meteor-bg" style={style}></span>)}</>;
28
23
  };
29
24
 
30
25
  // Particle Effect Component
31
26
  const Particles = ({ number = 50, color = '#ffffff', speed = 1 }) => {
32
- const particles = [...Array(number)].map((_, i) => {
27
+ const particles = useMemo(() => [...Array(number)].map(() => {
33
28
  const animationDuration = (Math.random() * 5 + 3) / speed;
34
- return (
35
- <span
36
- key={i}
37
- className="particle-bg"
38
- style={{
39
- '--particle-color': color,
40
- top: `${Math.random() * 100}%`,
41
- left: `${Math.random() * 100}%`,
42
- animationDelay: `${Math.random() * animationDuration}s`,
43
- animationDuration: `${animationDuration}s`,
44
- }}
45
- ></span>
46
- );
47
- });
48
- return <>{particles}</>;
29
+ return {
30
+ top: `${Math.random() * 100}%`,
31
+ left: `${Math.random() * 100}%`,
32
+ animationDelay: `${Math.random() * animationDuration}s`,
33
+ animationDuration: `${animationDuration}s`,
34
+ };
35
+ }), [number, speed]);
36
+ return <>{particles.map((style, i) => <span key={i} className="particle-bg" style={{ '--particle-color': color, ...style }}></span>)}</>;
49
37
  };
50
38
 
51
39
  // Main MeteorCard Component
40
+ // A fixed-size card (image on top, content below) so it looks the same on every screen.
52
41
  const MeteorCard = ({ data, particleNumber, particleColor, particleSpeed }) => {
53
42
  return (
54
- <>
43
+ <div className="flex flex-wrap justify-center gap-6">
55
44
  {data.map((item, index) => (
56
- <div key={index} className="relative w-full lg:w-[30vw] max-w-4xl mx-auto bg-slate-800/80 p-0.5 rounded-lg moving-border-card">
45
+ <div key={index} className="relative w-[20rem] max-w-full bg-slate-800/80 p-0.5 rounded-2xl moving-border-card">
57
46
  {/* Card ka content container */}
58
- <div className="relative bg-slate-900 rounded-lg p-4 md:p-8 flex flex-col md:flex-row items-center gap-6 md:gap-8 overflow-hidden">
47
+ <div className="relative bg-slate-900 rounded-[calc(1rem-2px)] p-3 flex flex-col overflow-hidden">
59
48
  {/* Background Effects */}
60
49
  <div className="absolute inset-0 w-full h-full z-0">
61
50
  <Meteors number={5} />
@@ -66,27 +55,27 @@ const MeteorCard = ({ data, particleNumber, particleColor, particleSpeed }) => {
66
55
  />
67
56
  </div>
68
57
 
69
- {/* Spotlight Effect (hide on small screens to reduce visual noise) */}
70
- <div className="hidden md:block absolute -top-1/3 -left-1/3 w-2/3 h-2/3 bg-blue-400/90 rounded-full blur-[120px] pointer-events-none z-0"></div>
58
+ {/* Spotlight Effect */}
59
+ <div className="absolute -top-1/4 -left-1/4 w-3/4 h-1/2 bg-blue-400/60 rounded-full blur-[90px] pointer-events-none z-0"></div>
71
60
 
72
- {/* Left Side: Image */}
73
- <div className="relative w-full md:w-1/2 h-64 md:h-full rounded-md overflow-hidden z-10">
74
- <img src={item.imageUrl} alt={item.title} className="w-full h-full object-cover lg:aspect-square" />
61
+ {/* Image */}
62
+ <div className="relative h-[13.75rem] w-full rounded-xl overflow-hidden z-10 ring-1 ring-white/10">
63
+ <img src={item.imageUrl} alt={item.title} className="w-full h-full object-cover object-[center_25%]" />
75
64
  </div>
76
65
 
77
- {/* Right Side: Content */}
78
- <div className="relative w-full md:w-2/3 text-center md:text-left z-10">
79
- <h3 className="text-3xl font-bold mb-2 text-white">{item.title}</h3>
80
- <p className="text-gray-400 mb-6">{item.description}</p>
81
- <button className="group relative inline-flex md:inline-flex w-full md:w-auto items-center justify-center px-6 py-2 bg-lime-600 text-white font-semibold rounded-md overflow-hidden transition-all duration-300 hover:bg-lime-700">
82
- <span className="mx-auto">{item.buttonText}</span>
66
+ {/* Content */}
67
+ <div className="relative z-10 px-2 pt-4 pb-2 text-center">
68
+ <h3 className="text-2xl font-bold mb-2 text-white tracking-tight">{item.title}</h3>
69
+ <p className="text-sm leading-relaxed text-gray-400 mb-5">{item.description}</p>
70
+ <button className="group relative inline-flex w-full items-center justify-center whitespace-nowrap px-6 py-2.5 bg-lime-600 text-white font-semibold rounded-lg overflow-hidden transition-all duration-300 hover:bg-lime-700">
71
+ <span>{item.buttonText}</span>
83
72
  <ArrowRightIcon />
84
73
  </button>
85
74
  </div>
86
75
  </div>
87
76
  </div>
88
77
  ))}
89
- </>
78
+ </div>
90
79
  );
91
80
  };
92
81