jashdoshi77 commited on
Commit
45c9f16
·
1 Parent(s): 86fbe37

Optimize performance: Reduce WebGL, simplify GSAP, fix UI

Browse files
src/components/PortfolioScroll.tsx CHANGED
@@ -72,8 +72,8 @@ export default function PortfolioScroll() {
72
  });
73
 
74
  const smoothProgress = useSpring(scrollYProgress, {
75
- stiffness: 100,
76
- damping: 30,
77
  restDelta: 0.001,
78
  });
79
 
 
72
  });
73
 
74
  const smoothProgress = useSpring(scrollYProgress, {
75
+ stiffness: 60,
76
+ damping: 25,
77
  restDelta: 0.001,
78
  });
79
 
src/components/ServicesSection.tsx CHANGED
@@ -193,51 +193,51 @@ export default function ServicesSection() {
193
 
194
  const viewportWidth = window.innerWidth;
195
  const viewportCenter = viewportWidth / 2;
196
-
197
  // Get card dimensions - measure actual rendered size after layout
198
  const cardWidth = cards[0].getBoundingClientRect().width;
199
  const gap = viewportWidth >= 768 ? 32 : 24;
200
-
201
  // Calculate positions: cards are positioned with paddingLeft = 8vw initially
202
  const paddingLeft = viewportWidth * 0.08;
203
-
204
  // First card's center position when at initial position (paddingLeft)
205
  const firstCardCenterInitial = paddingLeft + (cardWidth / 2);
206
-
207
  // Calculate offset needed to center first card initially
208
  // We need to shift container left so first card's center aligns with viewport center
209
  const initialOffset = firstCardCenterInitial - viewportCenter;
210
-
211
  // Last card's left edge position
212
  const lastCardLeftEdge = paddingLeft + (numCards - 1) * (cardWidth + gap);
213
  // Last card's center position when at initial position
214
  const lastCardCenterInitial = lastCardLeftEdge + (cardWidth / 2);
215
-
216
  // Calculate how much we need to scroll to center the last card
217
  // Scroll amount = difference between last card center and first card center positions
218
  const scrollAmount = lastCardCenterInitial - firstCardCenterInitial;
219
-
220
  // Each card gets equal time in the scroll sequence
221
- const scrollDistancePerCard = Math.max(1200, viewportWidth * 1.0);
222
  const totalScrollDistance = scrollDistancePerCard * numCards;
223
 
224
  // Setup initial card states with optimized rendering
225
  cards.forEach((card, index) => {
226
  // Enable hardware acceleration
227
- gsap.set(card, {
228
- willChange: "transform, opacity, filter",
229
  force3D: true,
230
  });
231
-
232
  if (index === 0) {
233
- gsap.set(card, { scale: 1, opacity: 1, filter: "blur(0px)" });
234
  } else {
235
- gsap.set(card, { scale: 0.88, opacity: 0.5, filter: "blur(3px)" });
236
  }
237
  });
238
 
239
  // Set initial position to center first card
240
- gsap.set(scrollContainer, {
241
  x: -initialOffset,
242
  willChange: "transform",
243
  force3D: true,
@@ -265,86 +265,68 @@ export default function ServicesSection() {
265
  fastScrollEnd: true,
266
  onUpdate: (self) => {
267
  const progress = Math.min(1, Math.max(0, self.progress));
268
-
269
  // Update horizontal scroll position (centered)
270
  const currentX = -initialOffset - (progress * scrollAmount);
271
  gsap.set(scrollContainer, { x: currentX });
272
-
273
  // Update card states based on progress
274
  // Each card gets 1/numCards of the scroll progress
275
  const cardProgress = progress * numCards;
276
  const activeCardIndex = Math.min(Math.floor(cardProgress), numCards - 1);
277
  const cardLocalProgress = Math.max(0, Math.min(1, cardProgress - activeCardIndex));
278
-
279
- // Batch DOM updates for better performance
280
  cards.forEach((card, index) => {
281
  const distance = Math.abs(index - activeCardIndex);
282
-
283
  let scale: number;
284
  let opacity: number;
285
- let blur: number;
286
-
287
  if (distance === 0 && index === activeCardIndex) {
288
- // Active card - transitioning into focus
289
  const focusProgress = Math.min(1, cardLocalProgress * 2);
290
- scale = 0.88 + (0.12 * focusProgress);
291
- opacity = 0.5 + (0.5 * focusProgress);
292
- blur = 3 - (3 * focusProgress);
293
  } else if (distance === 1 && index === activeCardIndex + 1) {
294
- // Next card - coming into view
295
  const nextProgress = Math.max(0, Math.min(1, (cardLocalProgress - 1) * 2));
296
- scale = 0.88 + (0.12 * nextProgress);
297
- opacity = 0.5 + (0.5 * nextProgress);
298
- blur = 3 - (3 * nextProgress);
299
  } else if (distance === 1 && index === activeCardIndex - 1) {
300
- // Previous card - fading out
301
  const fadeProgress = 1 - Math.max(0, Math.min(1, (cardLocalProgress + 1) * 2));
302
- scale = 1 - (0.12 * fadeProgress);
303
- opacity = 1 - (0.6 * fadeProgress);
304
- blur = 0 + (3 * fadeProgress);
305
  } else {
306
- // Cards further away - keep dimmed
307
- scale = 0.88;
308
- opacity = index < activeCardIndex ? 0.3 : 0.4;
309
- blur = 3;
310
  }
311
-
312
- // Ensure last card is fully visible at end
313
  if (progress >= 0.99 && index === numCards - 1) {
314
  scale = 1;
315
  opacity = 1;
316
- blur = 0;
317
  }
318
-
319
- // Update card with batched properties
320
- gsap.set(card, {
321
- scale,
322
- opacity,
323
- filter: `blur(${blur}px)`,
324
- });
325
  });
326
  },
327
  onLeave: () => {
328
- // Ensure last card is fully visible and centered when leaving section
329
- gsap.set(cards[numCards - 1], { scale: 1, opacity: 1, filter: "blur(0px)" });
330
  gsap.set(scrollContainer, { x: -initialOffset - scrollAmount });
331
  },
332
  onEnterBack: () => {
333
  // Reset to first card centered when scrolling back up
334
- gsap.set(cards[0], { scale: 1, opacity: 1, filter: "blur(0px)" });
335
  for (let i = 1; i < numCards; i++) {
336
- gsap.set(cards[i], { scale: 0.88, opacity: 0.5, filter: "blur(3px)" });
337
  }
338
  gsap.set(scrollContainer, { x: -initialOffset });
339
  },
340
- onRefresh: function(this: ScrollTrigger) {
341
  // Ensure proper state after refresh
342
- const progress = this.progress || 0;
343
  if (progress === 0) {
344
- gsap.set(cards[0], { scale: 1, opacity: 1, filter: "blur(0px)" });
345
  gsap.set(scrollContainer, { x: -initialOffset });
346
  } else if (progress >= 0.99) {
347
- gsap.set(cards[numCards - 1], { scale: 1, opacity: 1, filter: "blur(0px)" });
348
  gsap.set(scrollContainer, { x: -initialOffset - scrollAmount });
349
  }
350
  },
@@ -360,9 +342,9 @@ export default function ServicesSection() {
360
  ScrollTrigger.refresh();
361
  }, 300);
362
  };
363
-
364
  window.addEventListener("resize", handleResize);
365
-
366
  // Store cleanup function
367
  (sectionRef.current as any)._scrollCleanup = () => {
368
  window.removeEventListener("resize", handleResize);
@@ -398,7 +380,7 @@ export default function ServicesSection() {
398
  <section
399
  id="services"
400
  ref={sectionRef}
401
- className="relative bg-[#050505] overflow-hidden"
402
  style={{ perspective: "1000px", minHeight: "100vh" }}
403
  >
404
  {/* Full-screen dark overlay to hide other sections */}
@@ -409,15 +391,15 @@ export default function ServicesSection() {
409
  <div className="absolute inset-0 bg-gradient-radial from-blue-500/8 via-purple-500/5 to-transparent blur-3xl animate-pulse" />
410
  </div>
411
 
412
- {/* Main content - flexbox centered vertically */}
413
- <div className="relative z-10 min-h-screen flex flex-col justify-center py-20 md:py-24">
414
  {/* Header */}
415
- <div ref={headerRef} className="text-center mb-6 md:mb-10 px-4">
416
- <p className="services-label text-xs md:text-sm font-medium text-white/40 uppercase tracking-widest mb-3 md:mb-4">
417
  Services
418
  </p>
419
- <div className="services-line h-px w-16 md:w-24 bg-gradient-to-r from-transparent via-white/30 to-transparent mx-auto mb-4 md:mb-6" />
420
- <h2 className="text-3xl md:text-4xl lg:text-5xl font-light text-white/90 tracking-tight leading-tight" style={{ perspective: "1000px" }}>
421
  {headingText.split(" ").map((word, i) => (
422
  <span key={i} className="word inline-block mr-[0.25em]" style={{ transformStyle: "preserve-3d" }}>
423
  {word}
@@ -436,11 +418,10 @@ export default function ServicesSection() {
436
  {/* Services Horizontal Scroll Container */}
437
  <div
438
  ref={cardsRef}
439
- className="flex gap-6 md:gap-8 items-center py-12 md:py-16"
440
- style={{
441
- paddingLeft: "8vw",
442
  paddingRight: "8vw",
443
- minHeight: "auto",
444
  overflow: "visible",
445
  willChange: "transform",
446
  }}
@@ -451,7 +432,7 @@ export default function ServicesSection() {
451
  <motion.div
452
  key={service.title}
453
  ref={(el) => { cardRefs.current[index] = el; }}
454
- className="service-card group relative p-6 md:p-8 bg-white/[0.02] backdrop-blur-sm border border-white/5 rounded-2xl md:rounded-3xl overflow-hidden cursor-none flex-shrink-0 w-[80vw] sm:w-[60vw] md:w-[350px] lg:w-[380px] max-h-none"
455
  onMouseMove={(e) => handleMouseMove(e, index)}
456
  onMouseEnter={() => handleMouseEnter(index)}
457
  onMouseLeave={() => handleMouseLeave(index)}
@@ -465,104 +446,37 @@ export default function ServicesSection() {
465
  : "transform 0.5s ease-out",
466
  }}
467
  >
468
- {/* Custom cursor follower */}
469
- {mouseState.isHovering && (
470
- <motion.div
471
- className="absolute w-40 h-40 rounded-full pointer-events-none z-0"
472
- style={{
473
- background: "radial-gradient(circle, rgba(255,255,255,0.08) 0%, transparent 70%)",
474
- left: `${(mouseState.x + 1) * 50}%`,
475
- top: `${(mouseState.y + 1) * 50}%`,
476
- transform: "translate(-50%, -50%)",
477
- }}
478
- initial={{ opacity: 0, scale: 0.8 }}
479
- animate={{ opacity: 1, scale: 1 }}
480
- exit={{ opacity: 0, scale: 0.8 }}
481
- transition={{ duration: 0.2 }}
482
- />
483
- )}
484
-
485
- {/* Animated gradient border */}
486
- <div
487
- className="absolute inset-0 rounded-3xl opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none"
488
- style={{
489
- background: "linear-gradient(135deg, rgba(139,92,246,0.3) 0%, rgba(59,130,246,0.2) 50%, rgba(236,72,153,0.3) 100%)",
490
- padding: "1px",
491
- mask: "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)",
492
- maskComposite: "exclude",
493
- WebkitMaskComposite: "xor",
494
- }}
495
- />
496
-
497
- {/* Glow effect on hover */}
498
- <motion.div
499
- className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none"
500
- style={{
501
- background: `radial-gradient(800px circle at ${(mouseState.x + 1) * 50}% ${(mouseState.y + 1) * 50}%, rgba(139,92,246,0.06), transparent 50%)`,
502
- }}
503
- />
504
-
505
  <div className="card-content relative z-10">
506
- {/* Icon with enhanced glow */}
507
- <div className="relative w-16 h-16 md:w-20 md:h-20 mb-6 md:mb-8">
508
- <div className="absolute inset-0 bg-gradient-to-br from-white/10 to-white/5 rounded-2xl" />
509
- <motion.div
510
- className="absolute inset-0 flex items-center justify-center text-white/70 group-hover:text-white transition-colors duration-300"
511
- animate={mouseState.isHovering ? { scale: 1.1 } : { scale: 1 }}
512
- transition={{ duration: 0.3 }}
513
- >
514
  {service.icon}
515
- </motion.div>
516
- {/* Animated glow ring */}
517
- <motion.div
518
- className="absolute -inset-2 rounded-2xl opacity-0 group-hover:opacity-100"
519
- style={{
520
- background: "conic-gradient(from 0deg, rgba(139,92,246,0.4), rgba(59,130,246,0.4), rgba(236,72,153,0.4), rgba(139,92,246,0.4))",
521
- filter: "blur(8px)",
522
- }}
523
- animate={{
524
- rotate: [0, 360],
525
- }}
526
- transition={{
527
- duration: 4,
528
- repeat: Infinity,
529
- ease: "linear",
530
- }}
531
- />
532
  </div>
533
 
534
  {/* Title */}
535
- <h3 className="text-2xl md:text-3xl font-medium text-white/90 mb-4 md:mb-5 group-hover:text-white transition-colors duration-300">
536
  {service.title}
537
  </h3>
538
 
539
  {/* Description */}
540
- <p className="text-base md:text-lg text-white/50 leading-relaxed mb-6 md:mb-8 group-hover:text-white/70 transition-colors duration-300">
541
  {service.description}
542
  </p>
543
 
544
  {/* Features */}
545
- <div className="flex flex-wrap gap-2 md:gap-3">
546
- {service.features.map((feature, featureIndex) => (
547
- <motion.span
548
  key={feature}
549
- className="px-4 md:px-5 py-2 md:py-2.5 text-xs md:text-sm bg-white/5 text-white/50 rounded-full border border-white/10 group-hover:bg-white/10 group-hover:text-white/80 group-hover:border-white/20 transition-all duration-300"
550
- whileHover={{ scale: 1.05, y: -2 }}
551
  >
552
  {feature}
553
- </motion.span>
554
  ))}
555
  </div>
556
 
557
- {/* Arrow indicator */}
558
- <motion.div
559
- className="absolute top-8 md:top-10 right-8 md:right-10 w-10 h-10 md:w-12 md:h-12 rounded-full bg-white/5 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300"
560
- whileHover={{ scale: 1.2, rotate: 45 }}
561
- >
562
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-white/60">
563
- <path d="M7 17L17 7M17 7H7M17 7V17" />
564
- </svg>
565
- </motion.div>
566
  </div>
567
  </motion.div>
568
  );
@@ -580,7 +494,6 @@ export default function ServicesSection() {
580
  <path d="M5 12h14M12 5l7 7-7 7" />
581
  </svg>
582
  </motion.div>
583
- <span className="text-xs uppercase tracking-widest">Scroll to explore</span>
584
  </div>
585
  </div>
586
  </div>
 
193
 
194
  const viewportWidth = window.innerWidth;
195
  const viewportCenter = viewportWidth / 2;
196
+
197
  // Get card dimensions - measure actual rendered size after layout
198
  const cardWidth = cards[0].getBoundingClientRect().width;
199
  const gap = viewportWidth >= 768 ? 32 : 24;
200
+
201
  // Calculate positions: cards are positioned with paddingLeft = 8vw initially
202
  const paddingLeft = viewportWidth * 0.08;
203
+
204
  // First card's center position when at initial position (paddingLeft)
205
  const firstCardCenterInitial = paddingLeft + (cardWidth / 2);
206
+
207
  // Calculate offset needed to center first card initially
208
  // We need to shift container left so first card's center aligns with viewport center
209
  const initialOffset = firstCardCenterInitial - viewportCenter;
210
+
211
  // Last card's left edge position
212
  const lastCardLeftEdge = paddingLeft + (numCards - 1) * (cardWidth + gap);
213
  // Last card's center position when at initial position
214
  const lastCardCenterInitial = lastCardLeftEdge + (cardWidth / 2);
215
+
216
  // Calculate how much we need to scroll to center the last card
217
  // Scroll amount = difference between last card center and first card center positions
218
  const scrollAmount = lastCardCenterInitial - firstCardCenterInitial;
219
+
220
  // Each card gets equal time in the scroll sequence
221
+ const scrollDistancePerCard = Math.max(400, viewportWidth * 0.4);
222
  const totalScrollDistance = scrollDistancePerCard * numCards;
223
 
224
  // Setup initial card states with optimized rendering
225
  cards.forEach((card, index) => {
226
  // Enable hardware acceleration
227
+ gsap.set(card, {
228
+ willChange: "transform, opacity",
229
  force3D: true,
230
  });
231
+
232
  if (index === 0) {
233
+ gsap.set(card, { scale: 1, opacity: 1 });
234
  } else {
235
+ gsap.set(card, { scale: 0.94, opacity: 0.6 });
236
  }
237
  });
238
 
239
  // Set initial position to center first card
240
+ gsap.set(scrollContainer, {
241
  x: -initialOffset,
242
  willChange: "transform",
243
  force3D: true,
 
265
  fastScrollEnd: true,
266
  onUpdate: (self) => {
267
  const progress = Math.min(1, Math.max(0, self.progress));
268
+
269
  // Update horizontal scroll position (centered)
270
  const currentX = -initialOffset - (progress * scrollAmount);
271
  gsap.set(scrollContainer, { x: currentX });
272
+
273
  // Update card states based on progress
274
  // Each card gets 1/numCards of the scroll progress
275
  const cardProgress = progress * numCards;
276
  const activeCardIndex = Math.min(Math.floor(cardProgress), numCards - 1);
277
  const cardLocalProgress = Math.max(0, Math.min(1, cardProgress - activeCardIndex));
278
+
279
+ // Batch DOM updates for better performance - NO BLUR for performance
280
  cards.forEach((card, index) => {
281
  const distance = Math.abs(index - activeCardIndex);
 
282
  let scale: number;
283
  let opacity: number;
284
+
 
285
  if (distance === 0 && index === activeCardIndex) {
 
286
  const focusProgress = Math.min(1, cardLocalProgress * 2);
287
+ scale = 0.94 + (0.06 * focusProgress);
288
+ opacity = 0.7 + (0.3 * focusProgress);
 
289
  } else if (distance === 1 && index === activeCardIndex + 1) {
 
290
  const nextProgress = Math.max(0, Math.min(1, (cardLocalProgress - 1) * 2));
291
+ scale = 0.94 + (0.06 * nextProgress);
292
+ opacity = 0.7 + (0.3 * nextProgress);
 
293
  } else if (distance === 1 && index === activeCardIndex - 1) {
 
294
  const fadeProgress = 1 - Math.max(0, Math.min(1, (cardLocalProgress + 1) * 2));
295
+ scale = 1 - (0.06 * fadeProgress);
296
+ opacity = 1 - (0.3 * fadeProgress);
 
297
  } else {
298
+ scale = 0.94;
299
+ opacity = index < activeCardIndex ? 0.5 : 0.6;
 
 
300
  }
301
+
 
302
  if (progress >= 0.99 && index === numCards - 1) {
303
  scale = 1;
304
  opacity = 1;
 
305
  }
306
+
307
+ gsap.set(card, { scale, opacity });
 
 
 
 
 
308
  });
309
  },
310
  onLeave: () => {
311
+ gsap.set(cards[numCards - 1], { scale: 1, opacity: 1 });
 
312
  gsap.set(scrollContainer, { x: -initialOffset - scrollAmount });
313
  },
314
  onEnterBack: () => {
315
  // Reset to first card centered when scrolling back up
316
+ gsap.set(cards[0], { scale: 1, opacity: 1 });
317
  for (let i = 1; i < numCards; i++) {
318
+ gsap.set(cards[i], { scale: 0.94, opacity: 0.6 });
319
  }
320
  gsap.set(scrollContainer, { x: -initialOffset });
321
  },
322
+ onRefresh: (self) => {
323
  // Ensure proper state after refresh
324
+ const progress = self.progress || 0;
325
  if (progress === 0) {
326
+ gsap.set(cards[0], { scale: 1, opacity: 1 });
327
  gsap.set(scrollContainer, { x: -initialOffset });
328
  } else if (progress >= 0.99) {
329
+ gsap.set(cards[numCards - 1], { scale: 1, opacity: 1 });
330
  gsap.set(scrollContainer, { x: -initialOffset - scrollAmount });
331
  }
332
  },
 
342
  ScrollTrigger.refresh();
343
  }, 300);
344
  };
345
+
346
  window.addEventListener("resize", handleResize);
347
+
348
  // Store cleanup function
349
  (sectionRef.current as any)._scrollCleanup = () => {
350
  window.removeEventListener("resize", handleResize);
 
380
  <section
381
  id="services"
382
  ref={sectionRef}
383
+ className="relative bg-[#050505] overflow-visible"
384
  style={{ perspective: "1000px", minHeight: "100vh" }}
385
  >
386
  {/* Full-screen dark overlay to hide other sections */}
 
391
  <div className="absolute inset-0 bg-gradient-radial from-blue-500/8 via-purple-500/5 to-transparent blur-3xl animate-pulse" />
392
  </div>
393
 
394
+ {/* Main content - flexbox with top alignment for proper pinning */}
395
+ <div className="relative z-10 h-screen flex flex-col pt-12 md:pt-16">
396
  {/* Header */}
397
+ <div ref={headerRef} className="text-center mb-4 md:mb-6 px-4 flex-shrink-0">
398
+ <p className="services-label text-xs md:text-sm font-medium text-white/40 uppercase tracking-widest mb-2 md:mb-3">
399
  Services
400
  </p>
401
+ <div className="services-line h-px w-16 md:w-24 bg-gradient-to-r from-transparent via-white/30 to-transparent mx-auto mb-3 md:mb-4" />
402
+ <h2 className="text-2xl md:text-3xl lg:text-4xl font-light text-white/90 tracking-tight leading-tight" style={{ perspective: "1000px" }}>
403
  {headingText.split(" ").map((word, i) => (
404
  <span key={i} className="word inline-block mr-[0.25em]" style={{ transformStyle: "preserve-3d" }}>
405
  {word}
 
418
  {/* Services Horizontal Scroll Container */}
419
  <div
420
  ref={cardsRef}
421
+ className="flex gap-6 md:gap-8 items-start py-6 md:py-8 flex-1"
422
+ style={{
423
+ paddingLeft: "8vw",
424
  paddingRight: "8vw",
 
425
  overflow: "visible",
426
  willChange: "transform",
427
  }}
 
432
  <motion.div
433
  key={service.title}
434
  ref={(el) => { cardRefs.current[index] = el; }}
435
+ className="service-card group relative p-5 md:p-6 bg-white/[0.02] backdrop-blur-sm border border-white/5 rounded-2xl md:rounded-3xl overflow-hidden cursor-pointer flex-shrink-0 w-[80vw] sm:w-[60vw] md:w-[320px] lg:w-[350px] h-auto"
436
  onMouseMove={(e) => handleMouseMove(e, index)}
437
  onMouseEnter={() => handleMouseEnter(index)}
438
  onMouseLeave={() => handleMouseLeave(index)}
 
446
  : "transform 0.5s ease-out",
447
  }}
448
  >
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
449
  <div className="card-content relative z-10">
450
+ {/* Icon */}
451
+ <div className="relative w-12 h-12 md:w-14 md:h-14 mb-4 md:mb-5">
452
+ <div className="absolute inset-0 bg-gradient-to-br from-white/10 to-white/5 rounded-xl" />
453
+ <div className="absolute inset-0 flex items-center justify-center text-white/70 group-hover:text-white transition-colors duration-300">
 
 
 
 
454
  {service.icon}
455
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  </div>
457
 
458
  {/* Title */}
459
+ <h3 className="text-xl md:text-2xl font-medium text-white/90 mb-2 md:mb-3 group-hover:text-white transition-colors duration-300">
460
  {service.title}
461
  </h3>
462
 
463
  {/* Description */}
464
+ <p className="text-sm md:text-base text-white/50 leading-relaxed mb-4 md:mb-5 group-hover:text-white/70 transition-colors duration-300">
465
  {service.description}
466
  </p>
467
 
468
  {/* Features */}
469
+ <div className="flex flex-wrap gap-2">
470
+ {service.features.map((feature) => (
471
+ <span
472
  key={feature}
473
+ className="px-3 py-1.5 text-xs bg-white/5 text-white/50 rounded-full border border-white/10 group-hover:bg-white/10 group-hover:text-white/80 group-hover:border-white/20 transition-all duration-300"
 
474
  >
475
  {feature}
476
+ </span>
477
  ))}
478
  </div>
479
 
 
 
 
 
 
 
 
 
 
480
  </div>
481
  </motion.div>
482
  );
 
494
  <path d="M5 12h14M12 5l7 7-7 7" />
495
  </svg>
496
  </motion.div>
 
497
  </div>
498
  </div>
499
  </div>
src/components/SplashCursor.jsx CHANGED
@@ -3,17 +3,17 @@
3
  import { useEffect, useRef } from 'react';
4
 
5
  function SplashCursor({
6
- SIM_RESOLUTION = 128,
7
- DYE_RESOLUTION = 1440,
8
- CAPTURE_RESOLUTION = 512,
9
  DENSITY_DISSIPATION = 3.5,
10
  VELOCITY_DISSIPATION = 2,
11
  PRESSURE = 0.1,
12
- PRESSURE_ITERATIONS = 20,
13
  CURL = 3,
14
  SPLAT_RADIUS = 0.2,
15
- SPLAT_FORCE = 6000,
16
- SHADING = true,
17
  COLOR_UPDATE_SPEED = 10,
18
  BACK_COLOR = { r: 0.5, g: 0, b: 0 },
19
  TRANSPARENT = true
 
3
  import { useEffect, useRef } from 'react';
4
 
5
  function SplashCursor({
6
+ SIM_RESOLUTION = 64,
7
+ DYE_RESOLUTION = 512,
8
+ CAPTURE_RESOLUTION = 256,
9
  DENSITY_DISSIPATION = 3.5,
10
  VELOCITY_DISSIPATION = 2,
11
  PRESSURE = 0.1,
12
+ PRESSURE_ITERATIONS = 10,
13
  CURL = 3,
14
  SPLAT_RADIUS = 0.2,
15
+ SPLAT_FORCE = 4000,
16
+ SHADING = false,
17
  COLOR_UPDATE_SPEED = 10,
18
  BACK_COLOR = { r: 0.5, g: 0, b: 0 },
19
  TRANSPARENT = true