diff --git a/app/components/Instructor.tsx b/app/components/Instructor.tsx new file mode 100644 index 0000000..46ebcad --- /dev/null +++ b/app/components/Instructor.tsx @@ -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 ( +
+ +

Meet Our Instructors

+

Discover the talented professionals who will guide your musical journey

+
+ +
+
+
+ ) +} + +export default Instructor \ No newline at end of file diff --git a/app/components/ui/animated-tooltip.tsx b/app/components/ui/animated-tooltip.tsx new file mode 100644 index 0000000..6ea36be --- /dev/null +++ b/app/components/ui/animated-tooltip.tsx @@ -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(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) => ( +
setHoveredIndex(item.id)} + onMouseLeave={() => setHoveredIndex(null)} + > + {hoveredIndex === item.id && ( + +
+
+
+ {item.name} +
+
{item.designation}
+ + )} + {item.name} +
+ ))} + + ); +}; diff --git a/app/components/ui/wavy-background.tsx b/app/components/ui/wavy-background.tsx new file mode 100644 index 0000000..90a070d --- /dev/null +++ b/app/components/ui/wavy-background.tsx @@ -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(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 ( +
+ +
+ {children} +
+
+ ); +}; diff --git a/app/page.tsx b/app/page.tsx index 871a1af..677f143 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -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 ( @@ -13,6 +14,7 @@ export default function Home() { + ); } diff --git a/next.config.mjs b/next.config.mjs index 7b491cb..a4de04c 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,6 +1,10 @@ /** @type {import('next').NextConfig} */ const nextConfig = { output : "export", + images : { + domains: ['images.unsplash.com'], + unoptimized: true, + } }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index 19e2fd2..cdd8489 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,11 @@ + "simplex-noise": "^4.0.1", + + + + "tailwind-merge": "^2.3.0" @@ -16789,6 +16794,24 @@ }, + "node_modules/simplex-noise": { + + + + "version": "4.0.1", + + + + "resolved": "https://registry.npmjs.org/simplex-noise/-/simplex-noise-4.0.1.tgz", + + + + "integrity": "sha512-zl/+bdSqW7HJOQ0oDbxrNYaF4F5ik0i7M6YOYmEoIJNtg16NpvWaTTM1Y7oV/7T0jFljawLgYPS81Uu2rsfo1A==" + + + }, + + "node_modules/slash": { diff --git a/package.json b/package.json index f8a17e8..4a7384d 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,9 @@ "react-dom": "^18", + "simplex-noise": "^4.0.1", + + "tailwind-merge": "^2.3.0" }, diff --git a/tailwind.config.ts b/tailwind.config.ts index 95cc26a..ef49966 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -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: [