Compare commits
8 Commits
version_3
...
version_11
| Author | SHA1 | Date | |
|---|---|---|---|
| 8dabc243d4 | |||
| da75632918 | |||
| d5200e8504 | |||
| 8d6bbad260 | |||
| 4fa640318a | |||
| 3206659f83 | |||
| 7722a14781 | |||
| 8323a15bbe |
@@ -31,6 +31,125 @@ export default function RootLayout({
|
|||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `
|
__html: `
|
||||||
(function() {
|
(function() {
|
||||||
|
// Initialize custom cursor and click sound
|
||||||
|
if (!window.__customCursorInitialized) {
|
||||||
|
window.__customCursorInitialized = true;
|
||||||
|
|
||||||
|
// Create custom cursor elements
|
||||||
|
const cursorDot = document.createElement('div');
|
||||||
|
cursorDot.id = 'custom-cursor-dot';
|
||||||
|
cursorDot.style.cssText = 'position: fixed; width: 12px; height: 12px; background: radial-gradient(circle, #4d96ff, #2563eb); border-radius: 50%; pointer-events: none; z-index: 9999; box-shadow: 0 0 10px rgba(77, 150, 255, 0.6); display: none;';
|
||||||
|
document.body.appendChild(cursorDot);
|
||||||
|
|
||||||
|
const cursorRing = document.createElement('div');
|
||||||
|
cursorRing.id = 'custom-cursor-ring';
|
||||||
|
cursorRing.style.cssText = 'position: fixed; width: 32px; height: 32px; border: 2px solid #4d96ff; border-radius: 50%; pointer-events: none; z-index: 9998; display: none; box-shadow: 0 0 15px rgba(77, 150, 255, 0.4);';
|
||||||
|
document.body.appendChild(cursorRing);
|
||||||
|
|
||||||
|
// Trail particles
|
||||||
|
const trails = [];
|
||||||
|
const maxTrails = 8;
|
||||||
|
|
||||||
|
function createTrail(x, y) {
|
||||||
|
const trail = document.createElement('div');
|
||||||
|
trail.style.cssText = 'position: fixed; width: 6px; height: 6px; background: rgba(77, 150, 255, 0.6); border-radius: 50%; pointer-events: none; z-index: 9997;';
|
||||||
|
trail.style.left = x + 'px';
|
||||||
|
trail.style.top = y + 'px';
|
||||||
|
document.body.appendChild(trail);
|
||||||
|
|
||||||
|
trails.push({ element: trail, life: 1 });
|
||||||
|
if (trails.length > maxTrails) {
|
||||||
|
const old = trails.shift();
|
||||||
|
old.element.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
let opacity = 1;
|
||||||
|
const fadeInterval = setInterval(() => {
|
||||||
|
opacity -= 0.15;
|
||||||
|
trail.style.opacity = opacity;
|
||||||
|
if (opacity <= 0) {
|
||||||
|
clearInterval(fadeInterval);
|
||||||
|
trail.remove();
|
||||||
|
}
|
||||||
|
}, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse move listener
|
||||||
|
let mouseX = 0, mouseY = 0;
|
||||||
|
let dotX = 0, dotY = 0;
|
||||||
|
let ringX = 0, ringY = 0;
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', (e) => {
|
||||||
|
mouseX = e.clientX;
|
||||||
|
mouseY = e.clientY;
|
||||||
|
|
||||||
|
cursorDot.style.display = 'block';
|
||||||
|
cursorRing.style.display = 'block';
|
||||||
|
|
||||||
|
createTrail(mouseX - 3, mouseY - 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Smooth animation loop
|
||||||
|
function animateCursor() {
|
||||||
|
dotX += (mouseX - dotX) * 0.3;
|
||||||
|
dotY += (mouseY - dotY) * 0.3;
|
||||||
|
ringX += (mouseX - ringX) * 0.15;
|
||||||
|
ringY += (mouseY - ringY) * 0.15;
|
||||||
|
|
||||||
|
cursorDot.style.left = (dotX - 6) + 'px';
|
||||||
|
cursorDot.style.top = (dotY - 6) + 'px';
|
||||||
|
cursorRing.style.left = (ringX - 16) + 'px';
|
||||||
|
cursorRing.style.top = (ringY - 16) + 'px';
|
||||||
|
|
||||||
|
requestAnimationFrame(animateCursor);
|
||||||
|
}
|
||||||
|
animateCursor();
|
||||||
|
|
||||||
|
// Hide cursor on leave
|
||||||
|
document.addEventListener('mouseleave', () => {
|
||||||
|
cursorDot.style.display = 'none';
|
||||||
|
cursorRing.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click sound effect
|
||||||
|
function playClickSound() {
|
||||||
|
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
const oscillator = audioContext.createOscillator();
|
||||||
|
const gainNode = audioContext.createGain();
|
||||||
|
|
||||||
|
oscillator.connect(gainNode);
|
||||||
|
gainNode.connect(audioContext.destination);
|
||||||
|
|
||||||
|
oscillator.frequency.setValueAtTime(800, audioContext.currentTime);
|
||||||
|
oscillator.frequency.exponentialRampToValueAtTime(200, audioContext.currentTime + 0.1);
|
||||||
|
|
||||||
|
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
|
||||||
|
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
|
||||||
|
|
||||||
|
oscillator.start(audioContext.currentTime);
|
||||||
|
oscillator.stop(audioContext.currentTime + 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click listener
|
||||||
|
document.addEventListener('click', () => {
|
||||||
|
playClickSound();
|
||||||
|
|
||||||
|
// Pulse effect on ring
|
||||||
|
cursorRing.style.animation = 'none';
|
||||||
|
setTimeout(() => {
|
||||||
|
cursorRing.style.animation = 'pulse 0.4s ease-out';
|
||||||
|
}, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add pulse animation
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = '@keyframes pulse { 0% { transform: scale(1); opacity: 1; } 100% { transform: scale(1.5); opacity: 0; } }';
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
// Hide default cursor
|
||||||
|
document.documentElement.style.cursor = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
if (window.self === window.top) return;
|
if (window.self === window.top) return;
|
||||||
|
|
||||||
if (window.__webildEditorInitialized) return;
|
if (window.__webildEditorInitialized) return;
|
||||||
@@ -105,9 +224,76 @@ export default function RootLayout({
|
|||||||
' pointer-events: none !important;' +
|
' pointer-events: none !important;' +
|
||||||
' z-index: 999998 !important;' +
|
' z-index: 999998 !important;' +
|
||||||
' transition: all 0.15s ease !important;' +
|
' transition: all 0.15s ease !important;' +
|
||||||
|
'}' +
|
||||||
|
'.lofi-music-button {' +
|
||||||
|
' position: fixed !important;' +
|
||||||
|
' bottom: 30px !important;' +
|
||||||
|
' right: 30px !important;' +
|
||||||
|
' z-index: 999999 !important;' +
|
||||||
|
' width: 50px !important;' +
|
||||||
|
' height: 50px !important;' +
|
||||||
|
' border-radius: 50% !important;' +
|
||||||
|
' background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;' +
|
||||||
|
' border: none !important;' +
|
||||||
|
' color: white !important;' +
|
||||||
|
' font-size: 24px !important;' +
|
||||||
|
' cursor: pointer !important;' +
|
||||||
|
' box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important;' +
|
||||||
|
' transition: all 0.3s ease !important;' +
|
||||||
|
' display: flex !important;' +
|
||||||
|
' align-items: center !important;' +
|
||||||
|
' justify-content: center !important;' +
|
||||||
|
'}' +
|
||||||
|
'.lofi-music-button:hover {' +
|
||||||
|
' transform: scale(1.1) !important;' +
|
||||||
|
' box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6) !important;' +
|
||||||
|
'}' +
|
||||||
|
'.lofi-music-button.playing {' +
|
||||||
|
' animation: pulse-glow 2s ease-in-out infinite !important;' +
|
||||||
|
'}' +
|
||||||
|
'@keyframes pulse-glow {' +
|
||||||
|
' 0%, 100% { box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important; }' +
|
||||||
|
' 50% { box-shadow: 0 4px 25px rgba(102, 126, 234, 0.8) !important; }' +
|
||||||
'}';
|
'}';
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
// Create lofi music player
|
||||||
|
const audioContainer = document.createElement('div');
|
||||||
|
audioContainer.id = 'lofi-music-container';
|
||||||
|
|
||||||
|
const audio = document.createElement('audio');
|
||||||
|
audio.id = 'lofi-audio';
|
||||||
|
audio.loop = true;
|
||||||
|
audio.volume = 0.3;
|
||||||
|
audio.src = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
|
||||||
|
audioContainer.appendChild(audio);
|
||||||
|
|
||||||
|
const musicButton = document.createElement('button');
|
||||||
|
musicButton.className = 'lofi-music-button playing';
|
||||||
|
musicButton.innerHTML = '🎵';
|
||||||
|
musicButton.title = 'Toggle lofi music';
|
||||||
|
|
||||||
|
let isPlaying = true;
|
||||||
|
audio.play().catch(() => {
|
||||||
|
isPlaying = false;
|
||||||
|
musicButton.classList.remove('playing');
|
||||||
|
});
|
||||||
|
|
||||||
|
musicButton.addEventListener('click', () => {
|
||||||
|
if (isPlaying) {
|
||||||
|
audio.pause();
|
||||||
|
musicButton.classList.remove('playing');
|
||||||
|
isPlaying = false;
|
||||||
|
} else {
|
||||||
|
audio.play();
|
||||||
|
musicButton.classList.add('playing');
|
||||||
|
isPlaying = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(audioContainer);
|
||||||
|
document.body.appendChild(musicButton);
|
||||||
|
|
||||||
const getUniqueSelector = (element, assignId = false) => {
|
const getUniqueSelector = (element, assignId = false) => {
|
||||||
if (element.dataset && element.dataset.webildSelector) {
|
if (element.dataset && element.dataset.webildSelector) {
|
||||||
return element.dataset.webildSelector;
|
return element.dataset.webildSelector;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { memo } from "react";
|
"use client"
|
||||||
|
|
||||||
|
import { memo, useState, useRef, useEffect } from "react";
|
||||||
import CardStack from "@/components/cardStack/CardStack";
|
import CardStack from "@/components/cardStack/CardStack";
|
||||||
import MediaContent from "@/components/shared/MediaContent";
|
import MediaContent from "@/components/shared/MediaContent";
|
||||||
import { cls } from "@/lib/utils";
|
import { cls } from "@/lib/utils";
|
||||||
@@ -83,14 +85,134 @@ const TestimonialCard = memo(({
|
|||||||
overlayClassName = "",
|
overlayClassName = "",
|
||||||
ratingClassName = "",
|
ratingClassName = "",
|
||||||
nameClassName = "",
|
nameClassName = "",
|
||||||
roleClassName = "",
|
roleClassName = "",
|
||||||
companyClassName = "",
|
companyClassName = "",
|
||||||
}: TestimonialCardProps) => {
|
}: TestimonialCardProps) => {
|
||||||
|
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
|
||||||
|
const [scrollY, setScrollY] = useState(0);
|
||||||
|
const cardRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => setScrollY(window.scrollY);
|
||||||
|
window.addEventListener("scroll", handleScroll);
|
||||||
|
return () => window.removeEventListener("scroll", handleScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
if (!cardRef.current) return;
|
||||||
|
const rect = cardRef.current.getBoundingClientRect();
|
||||||
|
setMousePosition({
|
||||||
|
x: e.clientX - rect.left,
|
||||||
|
y: e.clientY - rect.top,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const parallaxOffset = scrollY * 0.05;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cls("relative h-full aspect-[8/10] rounded-theme-capped overflow-hidden group perspective", cardClassName)} style={{ perspective: "1000px" }}>
|
<div
|
||||||
<div className="relative w-full h-full transition-transform duration-500 ease-out group-hover:[transform:rotateY(180deg)]" style={{ transformStyle: "preserve-3d" }}>
|
ref={cardRef}
|
||||||
|
className={cls("relative h-full aspect-[8/10] rounded-theme-capped overflow-hidden group", cardClassName)}
|
||||||
|
style={{animation: "cardEntrance 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards"}}
|
||||||
|
onMouseMove={handleMouseMove}
|
||||||
|
>
|
||||||
|
<style>{`
|
||||||
|
@keyframes cardEntrance {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px) scale(0.95);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes glowPulse {
|
||||||
|
0%, 100% {
|
||||||
|
box-shadow: 0 0 20px rgba(var(--accent-rgb), 0.3), 0 20px 40px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 40px rgba(var(--accent-rgb), 0.5), 0 25px 50px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes rotateBorder {
|
||||||
|
from {
|
||||||
|
background-position: 0% 0%;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
background-position: 360% 360%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes colorShift {
|
||||||
|
0%, 100% {
|
||||||
|
filter: hue-rotate(0deg);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
filter: hue-rotate(15deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes sparkle {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes subtleBounce {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.group:hover .testimonial-inner {
|
||||||
|
animation: glowPulse 2s ease-in-out infinite, colorShift 3s ease-in-out infinite, subtleBounce 0.6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.group:hover .rotating-border {
|
||||||
|
animation: rotateBorder 3s linear infinite;
|
||||||
|
}
|
||||||
|
.sparkle-particle {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.sparkle-particle.active {
|
||||||
|
animation: sparkle 0.8s ease-out forwards;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
<div
|
||||||
|
className="testimonial-inner relative w-full h-full transition-all duration-500 ease-out group-hover:shadow-2xl group-hover:-translate-y-12 group-hover:scale-105 group-hover:[transform:perspective(1000px)_rotateX(2deg)_rotateY(-3deg)_translateY(-48px)_scale(1.05)]"
|
||||||
|
style={{transform: `translateY(${parallaxOffset}px)`}}
|
||||||
|
>
|
||||||
|
{/* Rotating border effect */}
|
||||||
|
<div
|
||||||
|
className="rotating-border absolute inset-0 rounded-theme-capped pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
||||||
|
style={{
|
||||||
|
background: "conic-gradient(from 0deg, rgba(var(--accent-rgb), 0.5), rgba(var(--accent-rgb), 0.1), rgba(var(--accent-rgb), 0.5))",
|
||||||
|
backgroundSize: "200% 200%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Sparkle particles */}
|
||||||
|
{[...Array(6)].map((_, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="sparkle-particle active"
|
||||||
|
style={{
|
||||||
|
left: `${mousePosition.x}px`,
|
||||||
|
top: `${mousePosition.y}px`,
|
||||||
|
width: "8px",
|
||||||
|
height: "8px",
|
||||||
|
background: "radial-gradient(circle, rgba(var(--accent-rgb), 0.8), transparent)",
|
||||||
|
borderRadius: "50%",
|
||||||
|
transform: `translate(-50%, -50%) rotate(${(i / 6) * 360}deg) translateX(20px)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
{/* Front of card */}
|
{/* Front of card */}
|
||||||
<div style={{ backfaceVisibility: "hidden" }} className="absolute w-full h-full">
|
<div className="absolute w-full h-full">
|
||||||
<MediaContent
|
<MediaContent
|
||||||
imageSrc={testimonial.imageSrc}
|
imageSrc={testimonial.imageSrc}
|
||||||
videoSrc={testimonial.videoSrc}
|
videoSrc={testimonial.videoSrc}
|
||||||
|
|||||||
Reference in New Issue
Block a user