From ec75ba291969d8770da88bdc7499514cc956d4ed Mon Sep 17 00:00:00 2001 From: Xsidz Date: Mon, 8 Sep 2025 02:17:11 +0530 Subject: [PATCH] contact-section --- src/App.jsx | 2 + src/components/Alert.jsx | 37 +++++ src/components/Particles.jsx | 270 +++++++++++++++++++++++++++++++++++ src/sections/Contact.jsx | 135 ++++++++++++++++++ 4 files changed, 444 insertions(+) create mode 100644 src/components/Alert.jsx create mode 100644 src/components/Particles.jsx create mode 100644 src/sections/Contact.jsx diff --git a/src/App.jsx b/src/App.jsx index 8eab6cf..9b137e0 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,6 +5,7 @@ import About from './sections/About' import Projects from './sections/Projects' import Experiences from './sections/Experiences' import Testimonial from './sections/Testimonial' +import Contact from './sections/Contact' const App = () => { return ( @@ -16,6 +17,7 @@ const App = () => { +
diff --git a/src/components/Alert.jsx b/src/components/Alert.jsx new file mode 100644 index 0000000..5bcde68 --- /dev/null +++ b/src/components/Alert.jsx @@ -0,0 +1,37 @@ +import { motion } from "framer-motion"; + +const Alert = ({ type, text }) => { + const alertVariants = { + hidden: { opacity: 0, y: 50, scale: 0.8 }, + visible: { opacity: 1, y: 0, scale: 1 }, + exit: { opacity: 0, y: -50, scale: 0.8 }, + }; + + return ( + +
+

+ {type === "danger" ? "Failed" : "Success"} +

+

{text}

+
+
+ ); +}; + +export default Alert; diff --git a/src/components/Particles.jsx b/src/components/Particles.jsx new file mode 100644 index 0000000..10703ab --- /dev/null +++ b/src/components/Particles.jsx @@ -0,0 +1,270 @@ +import { twMerge } from "tailwind-merge"; +import React, { useEffect, useRef, useState } from "react"; + +function MousePosition() { + const [mousePosition, setMousePosition] = useState({ + x: 0, + y: 0, + }); + + useEffect(() => { + const handleMouseMove = (event) => { + setMousePosition({ x: event.clientX, y: event.clientY }); + }; + + window.addEventListener("mousemove", handleMouseMove); + + return () => { + window.removeEventListener("mousemove", handleMouseMove); + }; + }, []); + + return mousePosition; +} + +function hexToRgb(hex) { + hex = hex.replace("#", ""); + + if (hex.length === 3) { + hex = hex + .split("") + .map((char) => char + char) + .join(""); + } + + const hexInt = parseInt(hex, 16); + const red = (hexInt >> 16) & 255; + const green = (hexInt >> 8) & 255; + const blue = hexInt & 255; + return [red, green, blue]; +} + +export const Particles = ({ + className = "", + quantity = 100, + staticity = 50, + ease = 50, + size = 0.4, + refresh = false, + color = "#ffffff", + vx = 0, + vy = 0, + ...props +}) => { + const canvasRef = useRef(null); + const canvasContainerRef = useRef(null); + const context = useRef(null); + const circles = useRef([]); + const mousePosition = MousePosition(); + const mouse = useRef({ x: 0, y: 0 }); + const canvasSize = useRef({ w: 0, h: 0 }); + const dpr = typeof window !== "undefined" ? window.devicePixelRatio : 1; + const rafID = useRef(null); + const resizeTimeout = useRef(null); + + useEffect(() => { + if (canvasRef.current) { + context.current = canvasRef.current.getContext("2d"); + } + initCanvas(); + animate(); + + const handleResize = () => { + if (resizeTimeout.current) { + clearTimeout(resizeTimeout.current); + } + resizeTimeout.current = setTimeout(() => { + initCanvas(); + }, 200); + }; + + window.addEventListener("resize", handleResize); + + return () => { + if (rafID.current != null) { + window.cancelAnimationFrame(rafID.current); + } + if (resizeTimeout.current) { + clearTimeout(resizeTimeout.current); + } + window.removeEventListener("resize", handleResize); + }; + }, [color]); + + useEffect(() => { + onMouseMove(); + }, [mousePosition.x, mousePosition.y]); + + useEffect(() => { + initCanvas(); + }, [refresh]); + + const initCanvas = () => { + resizeCanvas(); + drawParticles(); + }; + + const onMouseMove = () => { + if (canvasRef.current) { + const rect = canvasRef.current.getBoundingClientRect(); + const { w, h } = canvasSize.current; + const x = mousePosition.x - rect.left - w / 2; + const y = mousePosition.y - rect.top - h / 2; + const inside = x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2; + if (inside) { + mouse.current.x = x; + mouse.current.y = y; + } + } + }; + + const resizeCanvas = () => { + if (canvasContainerRef.current && canvasRef.current && context.current) { + canvasSize.current.w = canvasContainerRef.current.offsetWidth; + canvasSize.current.h = canvasContainerRef.current.offsetHeight; + + canvasRef.current.width = canvasSize.current.w * dpr; + canvasRef.current.height = canvasSize.current.h * dpr; + canvasRef.current.style.width = `${canvasSize.current.w}px`; + canvasRef.current.style.height = `${canvasSize.current.h}px`; + context.current.scale(dpr, dpr); + + // Clear existing particles and create new ones with exact quantity + circles.current = []; + for (let i = 0; i < quantity; i++) { + const circle = circleParams(); + drawCircle(circle); + } + } + }; + + const circleParams = () => { + const x = Math.floor(Math.random() * canvasSize.current.w); + const y = Math.floor(Math.random() * canvasSize.current.h); + const translateX = 0; + const translateY = 0; + const pSize = Math.floor(Math.random() * 2) + size; + const alpha = 0; + const targetAlpha = parseFloat((Math.random() * 0.6 + 0.1).toFixed(1)); + const dx = (Math.random() - 0.5) * 0.1; + const dy = (Math.random() - 0.5) * 0.1; + const magnetism = 0.1 + Math.random() * 4; + return { + x, + y, + translateX, + translateY, + size: pSize, + alpha, + targetAlpha, + dx, + dy, + magnetism, + }; + }; + + const rgb = hexToRgb(color); + + const drawCircle = (circle, update = false) => { + if (context.current) { + const { x, y, translateX, translateY, size, alpha } = circle; + context.current.translate(translateX, translateY); + context.current.beginPath(); + context.current.arc(x, y, size, 0, 2 * Math.PI); + context.current.fillStyle = `rgba(${rgb.join(", ")}, ${alpha})`; + context.current.fill(); + context.current.setTransform(dpr, 0, 0, dpr, 0, 0); + + if (!update) { + circles.current.push(circle); + } + } + }; + + const clearContext = () => { + if (context.current) { + context.current.clearRect( + 0, + 0, + canvasSize.current.w, + canvasSize.current.h + ); + } + }; + + const drawParticles = () => { + clearContext(); + const particleCount = quantity; + for (let i = 0; i < particleCount; i++) { + const circle = circleParams(); + drawCircle(circle); + } + }; + + const remapValue = (value, start1, end1, start2, end2) => { + const remapped = + ((value - start1) * (end2 - start2)) / (end1 - start1) + start2; + return remapped > 0 ? remapped : 0; + }; + + const animate = () => { + clearContext(); + circles.current.forEach((circle, i) => { + // Handle the alpha value + const edge = [ + circle.x + circle.translateX - circle.size, // distance from left edge + canvasSize.current.w - circle.x - circle.translateX - circle.size, // distance from right edge + circle.y + circle.translateY - circle.size, // distance from top edge + canvasSize.current.h - circle.y - circle.translateY - circle.size, // distance from bottom edge + ]; + const closestEdge = edge.reduce((a, b) => Math.min(a, b)); + const remapClosestEdge = parseFloat( + remapValue(closestEdge, 0, 20, 0, 1).toFixed(2) + ); + if (remapClosestEdge > 1) { + circle.alpha += 0.02; + if (circle.alpha > circle.targetAlpha) { + circle.alpha = circle.targetAlpha; + } + } else { + circle.alpha = circle.targetAlpha * remapClosestEdge; + } + circle.x += circle.dx + vx; + circle.y += circle.dy + vy; + circle.translateX += + (mouse.current.x / (staticity / circle.magnetism) - circle.translateX) / + ease; + circle.translateY += + (mouse.current.y / (staticity / circle.magnetism) - circle.translateY) / + ease; + + drawCircle(circle, true); + + // circle gets out of the canvas + if ( + circle.x < -circle.size || + circle.x > canvasSize.current.w + circle.size || + circle.y < -circle.size || + circle.y > canvasSize.current.h + circle.size + ) { + // remove the circle from the array + circles.current.splice(i, 1); + // create a new circle + const newCircle = circleParams(); + drawCircle(newCircle); + } + }); + rafID.current = window.requestAnimationFrame(animate); + }; + + return ( + + ); +}; \ No newline at end of file diff --git a/src/sections/Contact.jsx b/src/sections/Contact.jsx new file mode 100644 index 0000000..6fa3558 --- /dev/null +++ b/src/sections/Contact.jsx @@ -0,0 +1,135 @@ +import { useState } from "react"; +import emailjs from "@emailjs/browser"; +import Alert from "../components/Alert"; +import { Particles } from "../components/Particles"; +import { AnimatePresence } from "framer-motion"; +const Contact = () => { + const [formData, setFormData] = useState({ + name: "", + email: "", + message: "", + }); + const [isLoading, setIsLoading] = useState(false); + const [showAlert, setShowAlert] = useState(false); + const [alertType, setAlertType] = useState("success"); + const [alertMessage, setAlertMessage] = useState(""); + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + const showAlertMessage = (type, message) => { + setAlertType(type); + setAlertMessage(message); + setShowAlert(true); + setTimeout(() => { + setShowAlert(false); + }, 5000); + }; + const handleSubmit = async (e) => { + e.preventDefault(); + setIsLoading(true); + + try { + console.log("From submitted:", formData); + await emailjs.send( + "service_g8pia9k", + "template_lrhppet", + { + from_name: formData.name, + to_name: "Siddhesh", + from_email: formData.email, + to_email: "siddhesh.kabraa@gmail.com", + message: formData.message, + }, + "r9yHhOC1sPqo-kVE7" + ); + setIsLoading(false); + setFormData({ name: "", email: "", message: "" }); + showAlertMessage("success", "You message has been sent!"); + } catch (error) { + setIsLoading(false); + console.log(error); + showAlertMessage("danger", "Somthing went wrong!"); + } + }; + return ( +
+ + + {showAlert && } + +
+
+

Let's Talk

+

+ Whether you're loking to build a new website, improve your existing + platform, or bring a unique project to life, I'm here to help +

+
+
+
+ + +
+
+ + +
+
+ +