(null);
25 | const [isLoading, setIsLoading] = useState(false);
26 | const [error, setError] = useState("");
27 | const [isSuccess, setIsSuccess] = useState(false);
28 |
29 | const isInView = useInView(ref, { margin: "-100px" });
30 |
31 | const handleSubmit = async (e: FormEvent) => {
32 | e.preventDefault();
33 | setIsLoading(true);
34 |
35 | try {
36 | await sendEmail(formRef.current!);
37 | setIsSuccess(true);
38 | formRef.current?.reset();
39 | } catch (err) {
40 | if (err instanceof Error) {
41 | setError(err.message);
42 | } else {
43 | setError("Something went wrong.");
44 | }
45 | } finally {
46 | setIsLoading(false);
47 | }
48 | };
49 |
50 | return (
51 |
58 |
62 |
63 | Let's work together
64 |
65 | {contactInfo.map(item => (
66 |
67 |
68 | {item.title}
69 |
70 | {item.info}
71 |
72 | ))}
73 |
74 |
75 |
81 |
85 |
86 |
94 |
101 |
108 |
115 |
122 | {!!error && {error}
}
123 | {isSuccess && Email sent
}
124 |
125 |
126 |
127 | );
128 | };
129 |
--------------------------------------------------------------------------------
/src/components/home/Hero.tsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import Link from "next/link";
3 |
4 | const textVariants = {
5 | initial: {
6 | x: -500,
7 | opacity: 0,
8 | },
9 | animate: {
10 | x: 0,
11 | opacity: 1,
12 | transition: {
13 | duration: 1,
14 | staggerChildren: 0.1,
15 | },
16 | },
17 | scrollButton: {
18 | opacity: 0,
19 | y: 10,
20 | transition: {
21 | duration: 2,
22 | repeat: Infinity,
23 | },
24 | },
25 | };
26 | const sliderVariants = {
27 | initial: {
28 | x: 0,
29 | },
30 | animate: {
31 | x: -220,
32 | transition: {
33 | repeat: Infinity,
34 | duration: 20,
35 | },
36 | },
37 | };
38 |
39 | export const Hero = () => {
40 | return (
41 |
42 |
48 |
52 | Ikram Ul Haq
53 |
54 |
58 | Full Stack Developer
59 |
60 |
64 |
65 |
70 | See the Latest Works
71 |
72 |
73 |
74 |
78 | Contact Me
79 |
80 |
81 |
82 |
91 |
92 |
98 | Programmer, Developer, Problem Solver
99 |
100 | {/*
101 |
108 | */}
109 |
110 | );
111 | };
112 |
--------------------------------------------------------------------------------
/src/components/home/Parallax.tsx:
--------------------------------------------------------------------------------
1 | import { useRef } from "react";
2 | import { cn } from "@/utils";
3 | import { useScroll, useTransform, motion } from "framer-motion";
4 |
5 | interface ParallaxProps {
6 | type: "services" | "portfolio";
7 | }
8 |
9 | export const Parallax = ({ type }: ParallaxProps) => {
10 | const ref = useRef(null);
11 |
12 | const { scrollYProgress } = useScroll({
13 | target: ref,
14 | offset: ["start start", "end start"],
15 | });
16 |
17 | const yText = useTransform(scrollYProgress, [0, 1], ["0%", "500%"]);
18 | const yBg = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);
19 |
20 | return (
21 |
31 |
35 | {type === "services" ? "What We Do?" : "What We did?"}
36 |
37 |
42 |
54 |
60 |
61 | );
62 | };
63 |
--------------------------------------------------------------------------------
/src/components/home/Services.tsx:
--------------------------------------------------------------------------------
1 | import { servicesData } from "@/data";
2 | import { motion, useInView } from "framer-motion";
3 | import Image from "next/image";
4 | import { useRef } from "react";
5 |
6 | const variants = {
7 | initial: {
8 | x: -500,
9 | y: 100,
10 | opacity: 0,
11 | },
12 | animate: {
13 | x: 0,
14 | opacity: 1,
15 | y: 0,
16 | transition: {
17 | duration: 1,
18 | staggerChildren: 0.1,
19 | },
20 | },
21 | };
22 |
23 | export const Services = () => {
24 | const ref = useRef(null);
25 |
26 | const isInView = useInView(ref, { margin: "-100px" });
27 |
28 | return (
29 |
36 |
40 |
41 | I focus on helping your brand grow and move forward
42 |
43 |
44 |
45 |
49 |
50 |
57 |
58 |
59 | Unique
60 | {" "}
61 | Ideas
62 |
63 |
64 |
65 |
66 |
67 | For Your
68 | {" "}
69 | Business
70 |
71 |
77 |
78 |
79 |
83 | {servicesData.map(item => (
84 |
88 | {item.title}
89 | {item.description}
90 |
96 |
97 | ))}
98 |
99 |
100 | );
101 | };
102 |
--------------------------------------------------------------------------------
/src/components/home/index.ts:
--------------------------------------------------------------------------------
1 | export { Hero } from "./Hero";
2 | export { Parallax } from "./Parallax";
3 | export { Services } from "./Services";
4 | export { Portfolio } from "./portfolio/Portfolio";
5 | export { Contact } from "./Contact";
6 |
--------------------------------------------------------------------------------
/src/components/home/portfolio/Portfolio.tsx:
--------------------------------------------------------------------------------
1 | import { motion, useScroll, useSpring } from "framer-motion";
2 | import { useRef } from "react";
3 | import { ProjectDetails } from "./ProjectDetails";
4 | import { portfolioData } from "@/data";
5 |
6 | export const Portfolio = () => {
7 | const ref = useRef(null);
8 |
9 | const { scrollYProgress } = useScroll({
10 | target: ref,
11 | offset: ["end end", "start start"],
12 | });
13 |
14 | const scaleX = useSpring(scrollYProgress, {
15 | stiffness: 100,
16 | damping: 30,
17 | });
18 | return (
19 |
20 |
21 |
22 | Featured works
23 |
24 |
25 |
26 | {portfolioData.map(item => (
27 |
28 | ))}
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/src/components/home/portfolio/ProjectDetails.tsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import Image from "next/image";
3 | import Link from "next/link";
4 | import { useRef } from "react";
5 |
6 | interface ProjectDetailsProps {
7 | id: number;
8 | title: string;
9 | image: string;
10 | description: string;
11 | link: string;
12 | }
13 |
14 | export const ProjectDetails = ({
15 | title,
16 | image,
17 | description,
18 | link,
19 | }: ProjectDetailsProps) => {
20 | const ref = useRef(null);
21 |
22 | return (
23 |
24 |
25 |
26 |
33 |
34 |
35 |
36 | {title}
37 |
38 | {description}
39 |
40 |
46 |
47 |
48 |
49 |
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/src/components/layout/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import { Sidebar } from "../layout/sidebar";
3 | import { FacebookIcon, GithubIcon, LinkedinIcon } from "@/assets";
4 | import Link from "next/link";
5 |
6 | export const Navbar = () => {
7 | return (
8 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/src/components/layout/index.ts:
--------------------------------------------------------------------------------
1 | export { Navbar } from "./Navbar";
2 |
--------------------------------------------------------------------------------
/src/components/layout/sidebar/Links.tsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import Link from "next/link";
3 | import { linksList } from "@/data";
4 |
5 | const variants = {
6 | open: {
7 | transition: {
8 | staggerChildren: 0.1,
9 | },
10 | },
11 | closed: {
12 | transition: {
13 | staggerChildren: 0.05,
14 | staggerDirection: -1,
15 | },
16 | },
17 | };
18 |
19 | const linkVariants = {
20 | open: {
21 | y: 0,
22 | opacity: 1,
23 | },
24 | closed: {
25 | y: 50,
26 | opacity: 0,
27 | },
28 | };
29 |
30 | export const Links = () => {
31 | return (
32 |
38 | {linksList.map(item => (
39 |
44 |
51 | {item.label}
52 |
53 |
54 | ))}
55 |
56 | );
57 | };
58 |
--------------------------------------------------------------------------------
/src/components/layout/sidebar/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { motion } from "framer-motion";
3 | import { Links } from "./Links";
4 | import { ToggleButton } from "./ToggleButton";
5 | import { cn } from "@/utils";
6 |
7 | const variants = {
8 | open: {
9 | clipPath: "circle(1200px at 50px 50px)",
10 | transition: {
11 | type: "spring",
12 | stiffness: 20,
13 | },
14 | },
15 | closed: {
16 | clipPath: "circle(24px at 48px 46px)",
17 | transition: {
18 | delay: 0.5,
19 | type: "spring",
20 | stiffness: 400,
21 | damping: 40,
22 | },
23 | },
24 | };
25 |
26 | export const Sidebar = () => {
27 | const [isLoaded, setIsLoaded] = useState(false);
28 | const [isSidebarOpened, setIsSidebarOpened] = useState(false);
29 |
30 | const handleToggle = () => {
31 | setIsSidebarOpened(pre => !pre);
32 | };
33 |
34 | useEffect(() => {
35 | setIsLoaded(true);
36 | }, []);
37 |
38 | return (
39 |
43 |
49 |
50 |
51 |
52 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/src/components/layout/sidebar/ToggleButton.tsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 |
3 | interface ToggleButtonProps {
4 | handleToggle: () => void;
5 | }
6 |
7 | export const ToggleButton = ({ handleToggle }: ToggleButtonProps) => {
8 | return (
9 |
45 | );
46 | };
47 |
--------------------------------------------------------------------------------
/src/components/layout/sidebar/index.ts:
--------------------------------------------------------------------------------
1 | export { Sidebar } from "./Sidebar";
2 |
--------------------------------------------------------------------------------
/src/data/contact.data.ts:
--------------------------------------------------------------------------------
1 | export const contactInfo = [
2 | {
3 | title: "Email",
4 | info: "ikramdeveloper24@gmail.com",
5 | },
6 | {
7 | title: "Phone",
8 | info: "+92 335 0044927",
9 | },
10 | {
11 | title: "Address",
12 | info: "Lahore, Pakistan",
13 | },
14 | ];
15 |
--------------------------------------------------------------------------------
/src/data/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./sidebar.data";
2 | export * from "./portfolio.data";
3 | export * from "./services.data";
4 | export * from "./contact.data";
5 |
--------------------------------------------------------------------------------
/src/data/portfolio.data.ts:
--------------------------------------------------------------------------------
1 | const currentUrl = "https://github.com/ikramdeveloper";
2 |
3 | export const portfolioData = [
4 | {
5 | id: 1,
6 | title: "React Commerce",
7 | image:
8 | "https://images.pexels.com/photos/18073372/pexels-photo-18073372/free-photo-of-young-man-sitting-in-a-car-on-a-night-street.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load",
9 | description:
10 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolores ab id ad nesciunt quo aut corporis modi? Voluptate, quos sunt dolorum facilis, id eum sequi placeat accusantium saepe eos laborum.",
11 | link: currentUrl,
12 | },
13 | {
14 | id: 2,
15 | title: "Next.js Blog",
16 | image:
17 | "https://images.pexels.com/photos/18023772/pexels-photo-18023772/free-photo-of-close-up-of-a-person-holding-a-wristwatch.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load",
18 | description:
19 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolores ab id ad nesciunt quo aut corporis modi? Voluptate, quos sunt dolorum facilis, id eum sequi placeat accusantium saepe eos laborum.",
20 | link: currentUrl,
21 | },
22 | {
23 | id: 3,
24 | title: "Vanilla JS App",
25 | image:
26 | "https://images.pexels.com/photos/6894528/pexels-photo-6894528.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load",
27 | description:
28 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolores ab id ad nesciunt quo aut corporis modi? Voluptate, quos sunt dolorum facilis, id eum sequi placeat accusantium saepe eos laborum.",
29 | link: currentUrl,
30 | },
31 | {
32 | id: 4,
33 | title: "Music App",
34 | image:
35 | "https://images.pexels.com/photos/18540208/pexels-photo-18540208/free-photo-of-wood-landscape-water-hill.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
36 | description:
37 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolores ab id ad nesciunt quo aut corporis modi? Voluptate, quos sunt dolorum facilis, id eum sequi placeat accusantium saepe eos laborum.",
38 | link: currentUrl,
39 | },
40 | ];
41 |
--------------------------------------------------------------------------------
/src/data/services.data.ts:
--------------------------------------------------------------------------------
1 | export const servicesData = [
2 | {
3 | title: "Branding",
4 | description:
5 | " Lorem ipsum dolor sit amet consectetur adipisicing elit. Nostrum libero enim nisi aliquam consectetur expedita magni eius ex corruptianimi! Ad nam pariatur assumenda quaemollitia libero repellat explicabo maiores?",
6 | },
7 | {
8 | title: "Branding2",
9 | description:
10 | " Lorem ipsum dolor sit amet consectetur adipisicing elit. Nostrum libero enim nisi aliquam consectetur expedita magni eius ex corruptianimi! Ad nam pariatur assumenda quaemollitia libero repellat explicabo maiores?",
11 | },
12 | {
13 | title: "Branding3",
14 | description:
15 | " Lorem ipsum dolor sit amet consectetur adipisicing elit. Nostrum libero enim nisi aliquam consectetur expedita magni eius ex corruptianimi! Ad nam pariatur assumenda quaemollitia libero repellat explicabo maiores?",
16 | },
17 | ];
18 |
--------------------------------------------------------------------------------
/src/data/sidebar.data.ts:
--------------------------------------------------------------------------------
1 | export const linksList = [
2 | {
3 | label: "Home",
4 | value: "home",
5 | },
6 | {
7 | label: "Services",
8 | value: "services",
9 | },
10 | {
11 | label: "Portfolio",
12 | value: "portfolio",
13 | },
14 | {
15 | label: "Contact",
16 | value: "contact",
17 | },
18 | ];
19 |
--------------------------------------------------------------------------------
/src/env.js:
--------------------------------------------------------------------------------
1 | import { createEnv } from "@t3-oss/env-nextjs";
2 | import { z } from "zod";
3 |
4 | export const env = createEnv({
5 | /**
6 | * Specify your server-side environment variables schema here. This way you can ensure the app
7 | * isn't built with invalid env vars.
8 | */
9 | server: {
10 | NODE_ENV: z.enum(["development", "test", "production"]),
11 | },
12 |
13 | /**
14 | * Specify your client-side environment variables schema here. This way you can ensure the app
15 | * isn't built with invalid env vars. To expose them to the client, prefix them with
16 | * `NEXT_PUBLIC_`.
17 | */
18 | client: {
19 | NEXT_PUBLIC_EMAILJS_SERVICE_ID: z.string(),
20 | NEXT_PUBLIC_EMAILJS_TEMPLATE_ID: z.string(),
21 | NEXT_PUBLIC_EMAILJS_PUBLIC_KEY: z.string(),
22 | },
23 |
24 | /**
25 | * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
26 | * middlewares) or client-side so we need to destruct manually.
27 | */
28 | runtimeEnv: {
29 | NODE_ENV: process.env.NODE_ENV,
30 | NEXT_PUBLIC_EMAILJS_SERVICE_ID: process.env.NEXT_PUBLIC_EMAILJS_SERVICE_ID,
31 | NEXT_PUBLIC_EMAILJS_TEMPLATE_ID: process.env.NEXT_PUBLIC_EMAILJS_SERVICE_ID,
32 | NEXT_PUBLIC_EMAILJS_PUBLIC_KEY: process.env.NEXT_PUBLIC_EMAILJS_PUBLIC_KEY,
33 | },
34 | /**
35 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
36 | * useful for Docker builds.
37 | */
38 | skipValidation: !!process.env.SKIP_ENV_VALIDATION,
39 | /**
40 | * Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
41 | * `SOME_VAR=''` will throw an error.
42 | */
43 | emptyStringAsUndefined: true,
44 | });
45 |
--------------------------------------------------------------------------------
/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { type AppType } from "next/dist/shared/lib/utils";
2 | import "@/styles/globals.scss";
3 |
4 | const MyApp: AppType = ({ Component, pageProps }) => {
5 | return ;
6 | };
7 |
8 | export default MyApp;
9 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { Cursor } from "@/components/_shared";
2 | import { Navbar } from "@/components/layout";
3 | import {
4 | Hero,
5 | Parallax,
6 | Services,
7 | Portfolio,
8 | Contact,
9 | } from "@/components/home";
10 |
11 | const HomePage = () => {
12 | return (
13 |
14 |
15 |
22 |
25 |
31 |
34 |
37 |
43 |
44 | );
45 | };
46 |
47 | export default HomePage;
48 |
--------------------------------------------------------------------------------
/src/styles/globals.scss:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | html {
6 | scroll-snap-type: y mandatory;
7 | scroll-behavior: smooth;
8 | }
9 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./svg.type";
2 |
--------------------------------------------------------------------------------
/src/types/svg.type.ts:
--------------------------------------------------------------------------------
1 | import { type SVGProps } from "react";
2 |
3 | export type SvgProps = SVGProps;
4 |
--------------------------------------------------------------------------------
/src/utils/email.util.ts:
--------------------------------------------------------------------------------
1 | import { sendForm } from "@emailjs/browser";
2 |
3 | const serviceId = String(process.env.NEXT_PUBLIC_EMAILJS_SERVICE_ID);
4 | const templateId = String(process.env.NEXT_PUBLIC_EMAILJS_TEMPLATE_ID);
5 | const publicKey = String(process.env.NEXT_PUBLIC_EMAILJS_PUBLIC_KEY);
6 |
7 | export const sendEmail = async (form: HTMLFormElement) => {
8 | await sendForm(serviceId, templateId, form, publicKey);
9 | };
10 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./util";
2 | export * from "./email.util";
3 |
--------------------------------------------------------------------------------
/src/utils/util.ts:
--------------------------------------------------------------------------------
1 | import clsx, { type ClassValue } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 | export const cn = (...classes: ClassValue[]) => twMerge(clsx(...classes));
4 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import { type Config } from "tailwindcss";
2 | import { fontFamily } from "tailwindcss/defaultTheme";
3 |
4 | export default {
5 | content: ["./src/**/*.tsx"],
6 | theme: {
7 | extend: {
8 | fontFamily: {
9 | sans: ["var(--font-sans)", ...fontFamily.sans],
10 | },
11 | backgroundImage: {
12 | "past-work-gradient": "linear-gradient(180deg, #111132, #0c0c1d)",
13 | "portfolio-gradient": "linear-gradient(180deg, #111132, #505064)",
14 | "hero-gradient": "linear-gradient(180deg, #0c0c1d, #111132)",
15 | "mountains-image": "url('/assets/mountains.png')",
16 | "planets-image": "url('/assets/planets.png')",
17 | "sun-image": "url('/assets/sun.png')",
18 | "stars-image": "url('/assets/stars.png')",
19 | },
20 | colors: {
21 | "purple-shade": "rebeccapurple",
22 | "hero-bottom": "#ffffff09",
23 | },
24 | },
25 | },
26 | plugins: [require("@tailwindcss/line-clamp")],
27 | } satisfies Config;
28 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Base Options: */
4 | "esModuleInterop": true,
5 | "skipLibCheck": true,
6 | "target": "es2022",
7 | "allowJs": true,
8 | "resolveJsonModule": true,
9 | "moduleDetection": "force",
10 | "isolatedModules": true,
11 |
12 | /* Strictness */
13 | "strict": true,
14 | "noUncheckedIndexedAccess": true,
15 | "checkJs": true,
16 |
17 | /* Bundled projects */
18 | "lib": ["dom", "dom.iterable", "ES2022"],
19 | "noEmit": true,
20 | "module": "ESNext",
21 | "moduleResolution": "Bundler",
22 | "jsx": "preserve",
23 | "plugins": [{ "name": "next" }],
24 | "incremental": true,
25 |
26 | /* Path Aliases */
27 | "baseUrl": ".",
28 | "paths": {
29 | "@/*": ["./src/*"]
30 | }
31 | },
32 | "include": [
33 | ".eslintrc.cjs",
34 | "next-env.d.ts",
35 | "**/*.ts",
36 | "**/*.tsx",
37 | "**/*.cjs",
38 | "**/*.js",
39 | ".next/types/**/*.ts"
40 | ],
41 | "exclude": ["node_modules"]
42 | }
43 |
--------------------------------------------------------------------------------