Skip to content

Commit

Permalink
hover effect and instructors
Browse files Browse the repository at this point in the history
  • Loading branch information
barchakuz committed May 11, 2024
1 parent 9188da1 commit ed887dc
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 1 deletion.
50 changes: 50 additions & 0 deletions app/components/Instructor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use client'
import { WavyBackground } from "./ui/wavy-background"
import { AnimatedTooltip } from "./ui/animated-tooltip";

const instructors = [
{
id: 1,
name: 'Elena Briggs',
designation: 'Vocal Coach',
image:
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTB8fGF2YXRhcnxlbnwwfHwwfHx8MA%3D%3D&auto=format&fit=crop&w=800&q=60',
},
{
id: 2,
name: 'Marcus Reid',
designation: 'Guitar Instructor',
image:
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=3540&q=80',
},
{
id: 3,
name: 'Julia Zhang',
designation: 'Piano Teacher',
image:
'https://images.unsplash.com/photo-1580489944761-15a19d654956?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NXx8YXZhdGFyfGVufDB8fDB8fHww&auto=format&fit=crop&w=800&q=60',
},
{
id: 4,
name: 'Andre Gomez',
designation: 'Drumming Expert',
image:
'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8YXZhdGFyfGVufDB8fDB8fHww&auto=format&fit=crop&w=800&q=60',
},
];

function Instructor() {
return (
<div className="relative h-[40rem] overflow-hidden flex items-center justify-center">
<WavyBackground className="w-full max-w-7xl mx-auto flex flex-col items-center justify-center h-full">
<h2 className="text-2xl md:text-4xl lg:text-7xl text-white font-bold text-center mb-8">Meet Our Instructors</h2>
<p className="text-base md:text-lg text-white text-center mb-4">Discover the talented professionals who will guide your musical journey</p>
<div className="flex flex-row items-center justify-center mb-10 w-full">
<AnimatedTooltip items={instructors} />
</div>
</WavyBackground>
</div>
)
}

export default Instructor
90 changes: 90 additions & 0 deletions app/components/ui/animated-tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"use client";
import Image from "next/image";
import React, { useState } from "react";
import {
motion,
useTransform,
AnimatePresence,
useMotionValue,
useSpring,
} from "framer-motion";

export const AnimatedTooltip = ({
items,
}: {
items: {
id: number;
name: string;
designation: string;
image: string;
}[];
}) => {
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const springConfig = { stiffness: 100, damping: 5 };
const x = useMotionValue(0); // going to set this value on mouse move
// rotate the tooltip
const rotate = useSpring(
useTransform(x, [-100, 100], [-45, 45]),
springConfig
);
// translate the tooltip
const translateX = useSpring(
useTransform(x, [-100, 100], [-50, 50]),
springConfig
);
const handleMouseMove = (event: any) => {
const halfWidth = event.target.offsetWidth / 2;
x.set(event.nativeEvent.offsetX - halfWidth); // set the x value, which is then used in transform and rotate
};

return (
<>
{items.map((item, idx) => (
<div
className="-mr-4 relative group"
key={item.name}
onMouseEnter={() => setHoveredIndex(item.id)}
onMouseLeave={() => setHoveredIndex(null)}
>
{hoveredIndex === item.id && (
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.6 }}
animate={{
opacity: 1,
y: 0,
scale: 1,
transition: {
type: "spring",
stiffness: 260,
damping: 10,
},
}}
exit={{ opacity: 0, y: 20, scale: 0.6 }}
style={{
translateX: translateX,
rotate: rotate,
whiteSpace: "nowrap",
}}
className="absolute -top-16 -left-1/2 translate-x-1/2 flex text-xs flex-col items-center justify-center rounded-md bg-black z-50 shadow-xl px-4 py-2"
>
<div className="absolute inset-x-10 z-30 w-[20%] -bottom-px bg-gradient-to-r from-transparent via-emerald-500 to-transparent h-px " />
<div className="absolute left-10 w-[40%] z-30 -bottom-px bg-gradient-to-r from-transparent via-sky-500 to-transparent h-px " />
<div className="font-bold text-white relative z-30 text-base">
{item.name}
</div>
<div className="text-white text-xs">{item.designation}</div>
</motion.div>
)}
<Image
onMouseMove={handleMouseMove}
height={100}
width={100}
src={item.image}
alt={item.name}
className="object-cover !m-0 !p-0 object-top rounded-full h-14 w-14 border-2 group-hover:scale-105 group-hover:z-30 border-white relative transition duration-500"
/>
</div>
))}
</>
);
};
132 changes: 132 additions & 0 deletions app/components/ui/wavy-background.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"use client";
import { cn } from "@/app/utils/cn";
import React, { useEffect, useRef, useState } from "react";
import { createNoise3D } from "simplex-noise";

export const WavyBackground = ({
children,
className,
containerClassName,
colors,
waveWidth,
backgroundFill,
blur = 10,
speed = "fast",
waveOpacity = 0.5,
...props
}: {
children?: any;
className?: string;
containerClassName?: string;
colors?: string[];
waveWidth?: number;
backgroundFill?: string;
blur?: number;
speed?: "slow" | "fast";
waveOpacity?: number;
[key: string]: any;
}) => {
const noise = createNoise3D();
let w: number,
h: number,
nt: number,
i: number,
x: number,
ctx: any,
canvas: any;
const canvasRef = useRef<HTMLCanvasElement>(null);
const getSpeed = () => {
switch (speed) {
case "slow":
return 0.001;
case "fast":
return 0.002;
default:
return 0.001;
}
};

const init = () => {
canvas = canvasRef.current;
ctx = canvas.getContext("2d");
w = ctx.canvas.width = window.innerWidth;
h = ctx.canvas.height = window.innerHeight;
ctx.filter = `blur(${blur}px)`;
nt = 0;
window.onresize = function () {
w = ctx.canvas.width = window.innerWidth;
h = ctx.canvas.height = window.innerHeight;
ctx.filter = `blur(${blur}px)`;
};
render();
};

const waveColors = colors ?? [
"#38bdf8",
"#818cf8",
"#c084fc",
"#e879f9",
"#22d3ee",
];
const drawWave = (n: number) => {
nt += getSpeed();
for (i = 0; i < n; i++) {
ctx.beginPath();
ctx.lineWidth = waveWidth || 50;
ctx.strokeStyle = waveColors[i % waveColors.length];
for (x = 0; x < w; x += 5) {
var y = noise(x / 800, 0.3 * i, nt) * 100;
ctx.lineTo(x, y + h * 0.5); // adjust for height, currently at 50% of the container
}
ctx.stroke();
ctx.closePath();
}
};

let animationId: number;
const render = () => {
ctx.fillStyle = backgroundFill || "black";
ctx.globalAlpha = waveOpacity || 0.5;
ctx.fillRect(0, 0, w, h);
drawWave(5);
animationId = requestAnimationFrame(render);
};

useEffect(() => {
init();
return () => {
cancelAnimationFrame(animationId);
};
}, []);

const [isSafari, setIsSafari] = useState(false);
useEffect(() => {
// I'm sorry but i have got to support it on safari.
setIsSafari(
typeof window !== "undefined" &&
navigator.userAgent.includes("Safari") &&
!navigator.userAgent.includes("Chrome")
);
}, []);

return (
<div
className={cn(
"h-screen flex flex-col items-center justify-center",
containerClassName
)}
>
<canvas
className="absolute inset-0 z-0"
ref={canvasRef}
id="canvas"
style={{
...(isSafari ? { filter: `blur(${blur}px)` } : {}),
}}
></canvas>
<div className={cn("relative z-10", className)} {...props}>
{children}
</div>
</div>
);
};
2 changes: 2 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import FeaturedCourse from "./components/FeaturedCourse";
import WhyChooseUs from "./components/WhyChooseUs";
import MusicSchoolCards from "./components/MusicSchoolCards";
import UpcomingWebinars from "./components/UpcomingWebinars";
import Instructor from "./components/Instructor";

export default function Home() {
return (
Expand All @@ -13,6 +14,7 @@ export default function Home() {
<WhyChooseUs />
<MusicSchoolCards />
<UpcomingWebinars />
<Instructor />
</>
);
}
4 changes: 4 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output : "export",
images : {
domains: ['images.unsplash.com'],
unoptimized: true,
}
};

export default nextConfig;
23 changes: 23 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
"react-dom": "^18",


"simplex-noise": "^4.0.1",


"tailwind-merge": "^2.3.0"

},
Expand Down
9 changes: 8 additions & 1 deletion tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@ const defaultTheme = require("tailwindcss/defaultTheme");
const colors = require("tailwindcss/colors");
const svgToDataUri = require("mini-svg-data-uri");

import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

const {
default: flattenColorPalette,
} = require("tailwindcss/lib/util/flattenColorPalette");


export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}


const config: Config = {
content: [
Expand Down

0 comments on commit ed887dc

Please sign in to comment.