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
+
+
+
+
+
+ );
+};
+
+export default Contact;