├── public ├── css.png ├── js.png ├── php.png ├── gear.png ├── html.png ├── java.png ├── mysql.png ├── nnode.png ├── laravel.png ├── python.png ├── three js.png ├── bootstrap.png ├── download.jpeg ├── express-js.png ├── project-empat.png ├── project-enam.png ├── project-tujuh.png ├── project-sepuluh.png ├── project-sembilan.png ├── Screenshot 2024-06-12 085200.png ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg ├── next.svg ├── mysql-logo-svgrepo-com.svg └── nestJS.svg ├── app ├── favicon.ico ├── assets │ ├── foto-evan.png │ └── foto_evan.png ├── (components) │ ├── Container.tsx │ ├── ShootingStars.tsx │ ├── StarEffect.tsx │ ├── SplitReveal.tsx │ ├── LoadingScreenFirstOpen.tsx │ ├── HomePage.tsx │ ├── ContactPage.tsx │ ├── ParticleComponent.tsx │ ├── ProjectPage.tsx │ ├── Navbar.tsx │ ├── AboutPage.tsx │ └── DataWorks.tsx ├── context │ └── navContext.tsx ├── layout.tsx ├── globals.css └── page.tsx ├── postcss.config.mjs ├── next.config.ts ├── eslint.config.mjs ├── .gitignore ├── README.md ├── tsconfig.json ├── package.json └── tailwind.config.ts /public/css.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/css.png -------------------------------------------------------------------------------- /public/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/js.png -------------------------------------------------------------------------------- /public/php.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/php.png -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/gear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/gear.png -------------------------------------------------------------------------------- /public/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/html.png -------------------------------------------------------------------------------- /public/java.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/java.png -------------------------------------------------------------------------------- /public/mysql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/mysql.png -------------------------------------------------------------------------------- /public/nnode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/nnode.png -------------------------------------------------------------------------------- /public/laravel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/laravel.png -------------------------------------------------------------------------------- /public/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/python.png -------------------------------------------------------------------------------- /public/three js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/three js.png -------------------------------------------------------------------------------- /public/bootstrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/bootstrap.png -------------------------------------------------------------------------------- /public/download.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/download.jpeg -------------------------------------------------------------------------------- /public/express-js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/express-js.png -------------------------------------------------------------------------------- /app/assets/foto-evan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/app/assets/foto-evan.png -------------------------------------------------------------------------------- /app/assets/foto_evan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/app/assets/foto_evan.png -------------------------------------------------------------------------------- /public/project-empat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/project-empat.png -------------------------------------------------------------------------------- /public/project-enam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/project-enam.png -------------------------------------------------------------------------------- /public/project-tujuh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/project-tujuh.png -------------------------------------------------------------------------------- /public/project-sepuluh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/project-sepuluh.png -------------------------------------------------------------------------------- /public/project-sembilan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/project-sembilan.png -------------------------------------------------------------------------------- /public/Screenshot 2024-06-12 085200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evanstef/Portofolio-Evan/HEAD/public/Screenshot 2024-06-12 085200.png -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | images : { 6 | remotePatterns : [ 7 | { 8 | protocol : 'https', 9 | hostname : 'tenor.com' 10 | } 11 | ] 12 | } 13 | }; 14 | 15 | export default nextConfig; 16 | -------------------------------------------------------------------------------- /app/(components)/Container.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Container = ({children, className}: {children: React.ReactNode, className?: string} ) => { 4 | return ( 5 |
8 | {children} 9 |
10 | ) 11 | } 12 | 13 | export default Container 14 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Hai guys selamat datang ini merupakan portfolio saya yang saya buat dengan menggunakan NextJS salah satu fitur menarik di website portfolio ini adalah background yang interaktif dengan menggunakan perpaduan library particleJS dan juga GSAP untuk membuat animasi lebih menarik.untuk stack apa saja yang saya gunakan dalam membuat website ini adalah sebagai berikut : 2 | 3 | 1. NextJS 4 | 2. GSAP 5 | 3. TailwindCSS 6 | 4. Lenis 7 | 5. ParticleJs 8 | 9 | ## Jangan lupa like dan saya juga butuh masukan dari temen temen sekalian apa yang harus di perbaiki di website portfolio ini Sekian terimakasih 10 | 11 |
12 |

Get In Touch

13 |
14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /app/context/navContext.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | "use client" 3 | 4 | import { useContext, createContext, useEffect, useState } from "react"; 5 | 6 | interface NavContextProps { 7 | openNav: boolean; 8 | isLoading: boolean; 9 | setIsLoading: React.Dispatch>; 10 | setOpenNav: React.Dispatch>; 11 | } 12 | 13 | const NavContext = createContext({} as NavContextProps); 14 | 15 | const NavProvider = ({children}: {children: React.ReactNode}) => { 16 | const [openNav, setOpenNav] = useState(false); 17 | const [isLoading, setIsLoading] = useState(true); 18 | 19 | return ( 20 | 21 | {children} 22 | 23 | ) 24 | } 25 | 26 | export const useNavContext = () => useContext(NavContext); 27 | 28 | export default NavProvider -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@gsap/react": "^2.1.2", 13 | "@tsparticles/all": "^3.7.1", 14 | "@tsparticles/react": "^3.0.0", 15 | "gif.js": "^0.2.0", 16 | "gsap": "^3.12.5", 17 | "lenis": "^1.1.19", 18 | "next": "15.1.4", 19 | "react": "^18.3.1", 20 | "react-dom": "^18.3.1", 21 | "react-tsparticles": "^2.12.2", 22 | "tailwind-scrollbar": "^3.1.0", 23 | "tsparticles": "^3.7.1", 24 | "typewriter-effect": "^2.21.0" 25 | }, 26 | "devDependencies": { 27 | "@eslint/eslintrc": "^3", 28 | "@types/node": "^20", 29 | "@types/react": "^19", 30 | "@types/react-dom": "^19", 31 | "eslint": "^9", 32 | "eslint-config-next": "15.1.4", 33 | "postcss": "^8", 34 | "tailwindcss": "^3.4.1", 35 | "typescript": "^5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Geist } from "next/font/google"; 3 | import "./globals.css"; 4 | import NavProvider from "./context/navContext"; 5 | import Navbar from "./(components)/Navbar"; 6 | import LoadingScreenFirstOpen from "./(components)/LoadingScreenFirstOpen"; 7 | 8 | const geistSans = Geist({ 9 | variable: "--font-geist-sans", 10 | subsets: ["latin"], 11 | }); 12 | 13 | export const metadata: Metadata = { 14 | title: "Evan Web", 15 | description: "Generated by create next app", 16 | }; 17 | 18 | export default function RootLayout({ 19 | children, 20 | }: Readonly<{ 21 | children: React.ReactNode; 22 | }>) { 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | {children} 30 | 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /app/(components)/ShootingStars.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const ShootingStars = () => { 4 | // State untuk posisi awal dan arah bintang 5 | const [stars] = useState( 6 | Array.from({ length: 100 }).map(() => { 7 | const direction = Math.random() < 0.5 ? 'top' : 'right-top'; // Random arah 8 | return { 9 | top: direction === 'top' ? Math.random() * 100 : 0, // Jika dari atas 10 | left: direction === 'top' ? Math.random() * 100 : 100, // Jika dari kanan atas 11 | direction, 12 | }; 13 | }) 14 | ); 15 | 16 | return ( 17 |
18 | {stars.map((star, index) => { 19 | const delay = Math.random() * 5; // Random delay (0-5 detik) 20 | return ( 21 |
31 | ); 32 | })} 33 |
34 | ); 35 | }; 36 | 37 | export default ShootingStars; 38 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-require-imports */ 2 | import type { Config } from "tailwindcss"; 3 | 4 | export default { 5 | content: [ 6 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 9 | ], 10 | theme: { 11 | extend: { 12 | keyframes: { 13 | bounceHigh: { 14 | "0%, 100%": { 15 | transform: "translateY(-100%)", // Pantulan lebih tinggi 16 | animationTimingFunction: "cubic-bezier(0.8, 0, 1, 1)", 17 | }, 18 | "50%": { 19 | transform: "translateY(0)", // Kembali ke posisi awal 20 | animationTimingFunction: "cubic-bezier(0, 0, 0.2, 1)", 21 | }, 22 | }, 23 | }, 24 | colors: { 25 | background: "var(--background)", 26 | foreground: "var(--foreground)", 27 | secondary: "#0f0f0f", 28 | primary: "#0A0A0A", 29 | variatif: "#171717", 30 | variatif_secondary : "#161616", 31 | }, 32 | animation: { 33 | "bounce-high": "bounceHigh 1s infinite", // Animasi custom 34 | } 35 | }, 36 | }, 37 | plugins: [ 38 | require("tailwind-scrollbar") 39 | ], 40 | } satisfies Config; 41 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/(components)/StarEffect.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import React from 'react' 3 | import { useState } from 'react' 4 | 5 | const StarEffect = () => { 6 | // State untuk posisi awal bintang 7 | const [stars, setStars] = useState( 8 | Array.from({ length: 120 }).map(() => ({ 9 | top: Math.random() * 100, 10 | left: Math.random() * 100, 11 | })) 12 | ); 13 | 14 | // Fungsi untuk mengubah posisi saat dihover 15 | const handleHover = (index : any) => { 16 | setStars((prevStars) => 17 | prevStars.map((star, i) => 18 | i === index 19 | ? { 20 | top: Math.random() * 100, // Posisi baru acak 21 | left: Math.random() * 100, 22 | } 23 | : star 24 | ) 25 | ); 26 | }; 27 | 28 | 29 | return ( 30 |
31 | {stars.map((star, index) => { 32 | const size = Math.random() * 4 + 1; // Ukuran acak 33 | const delay = Math.random() * 1; // Delay acak 34 | return ( 35 |
handleHover(index)} // Trigger saat hover 46 | >
47 | ); 48 | })} 49 |
50 | ) 51 | } 52 | 53 | export default StarEffect 54 | -------------------------------------------------------------------------------- /app/(components)/SplitReveal.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useEffect } from "react"; 4 | import { gsap } from "gsap"; 5 | 6 | const SplitText = () => { 7 | useEffect(() => { 8 | // Buat GSAP Timeline dengan repeat dan yoyo 9 | const tl = gsap.timeline({ repeat: -1, delay: 0.5 }); // repeat: -1 untuk animasi tanpa batas 10 | 11 | // Animasi garis reveal 12 | tl.fromTo( 13 | ".reveal-line", 14 | { width: "0%", opacity: 0 }, 15 | { 16 | width: "100%", 17 | opacity: 1, 18 | duration: 1, 19 | ease: "power4.out", 20 | } 21 | ) 22 | .fromTo( 23 | ".split-text span", 24 | { y: "100%", opacity: 0 }, // Muncul dari bawah 25 | { 26 | y: "0%", 27 | opacity: 1, 28 | duration: 1, 29 | stagger: 0.2, 30 | ease: "power4.out", 31 | }, 32 | "-=0.8" // Mulai teks bersamaan dengan garis 33 | ) 34 | .to( 35 | ".split-text span", 36 | { 37 | y: "-100%", // Hilang ke arah atas 38 | opacity: 0, 39 | duration: 1, 40 | stagger: 0.2, 41 | ease: "power4.in", 42 | }, 43 | "+=0.5" // Delay sebelum animasi hilang 44 | ) 45 | .to( 46 | ".reveal-line", 47 | { 48 | width: "0%", 49 | opacity: 0, 50 | duration: 1, 51 | ease: "power4.in", 52 | }, 53 | "-=0.8" // Garis mulai menyusut saat teks mulai hilang 54 | ); 55 | }, []); 56 | 57 | return ( 58 |
59 |
60 |
61 | {Array.from("Contact Me").map((letter, index) => ( 62 | 66 | {letter === " " ? "\u00A0" : letter} 67 | 68 | ))} 69 |
70 | {/* Reveal Lines */} 71 |
72 |
73 |
74 | ); 75 | }; 76 | 77 | export default SplitText; 78 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .custom-scrollbar { 6 | scrollbar-width: thin; 7 | scrollbar-color: #4f46e5 #1e293b; 8 | } 9 | 10 | .custom-scrollbar::-webkit-scrollbar { 11 | width: 8px; 12 | } 13 | 14 | .custom-scrollbar::-webkit-scrollbar-track { 15 | background: #1e293b; 16 | } 17 | 18 | .custom-scrollbar::-webkit-scrollbar-thumb { 19 | background-color: #4f46e5; 20 | border-radius: 9999px; 21 | } 22 | 23 | 24 | 25 | 26 | html { 27 | scroll-behavior: smooth; 28 | } 29 | 30 | * { 31 | box-sizing: border-box; 32 | margin: 0; 33 | padding: 0; 34 | } 35 | 36 | body { 37 | height: 100%; 38 | overflow-x: hidden; 39 | overflow-y: scroll; 40 | } 41 | 42 | .gsap_animation { 43 | position: relative; 44 | z-index: auto; 45 | box-sizing: border-box; 46 | } 47 | 48 | .parralax-wrap { 49 | position: relative; 50 | z-index: 10; 51 | } 52 | 53 | /* efek */ 54 | 55 | .starfield { 56 | position: absolute; 57 | top: 0; 58 | left: 0; 59 | width: 100%; 60 | height: 100%; 61 | overflow: hidden; 62 | z-index: -1; 63 | } 64 | 65 | .star { 66 | position: absolute; 67 | background: white; 68 | border-radius: 50%; 69 | box-shadow: 0 0 6px rgba(255, 255, 255, 1); 70 | animation: twinkle 2s infinite ease-in-out; 71 | } 72 | 73 | @keyframes twinkle { 74 | 0%, 100% { 75 | opacity: 0.5; 76 | transform: scale(1); 77 | } 78 | 50% { 79 | opacity: 1; 80 | transform: scale(2); 81 | } 82 | } 83 | 84 | 85 | .shooting-star-container { 86 | position: absolute; 87 | width: 100vw; 88 | height: 100vh; 89 | z-index: -1; 90 | overflow: hidden; 91 | } 92 | 93 | .shooting-star { 94 | position: absolute; 95 | top: 0; 96 | width: 2px; 97 | height: 120px; 98 | z-index: -1; 99 | background: linear-gradient(to bottom, white, transparent); 100 | transform: rotate(-35deg); 101 | animation: shoot 4s linear infinite, fade-in 0.5s ease-in-out; 102 | } 103 | 104 | @keyframes shoot { 105 | 0% { 106 | transform: translate(0, 0) rotate(-35deg); 107 | opacity: 1; 108 | } 109 | 100% { 110 | transform: translate(200px, 200px) rotate(-45deg); 111 | opacity: 0; 112 | } 113 | } 114 | 115 | @keyframes fade-in { 116 | from { 117 | opacity: 0; 118 | } 119 | to { 120 | opacity: 1; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/(components)/LoadingScreenFirstOpen.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-const */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | /* eslint-disable @typescript-eslint/no-explicit-any */ 4 | "use client"; 5 | 6 | import React, { useEffect, useRef } from "react"; 7 | import { gsap } from "gsap"; 8 | import { useNavContext } from "../context/navContext"; 9 | 10 | const LoadingScreenFirstOpen = () => { 11 | const { isLoading, setIsLoading } = useNavContext(); 12 | 13 | // setting angka pada counter 14 | useEffect(() => { 15 | // counter value 16 | function counterValue() { 17 | let counter : any = document.querySelector(".counter"); 18 | let currentValue = 0 19 | 20 | function updateCounter() { 21 | if(currentValue === 100) { 22 | return 23 | } 24 | 25 | currentValue += Math.floor(Math.random() * 10) + 1; 26 | 27 | if(currentValue > 100) { 28 | currentValue = 100 29 | } 30 | 31 | // mengisi counter element 32 | counter.textContent = currentValue; 33 | 34 | let delay = Math.floor(Math.random() * 200) + 50; 35 | 36 | setTimeout(updateCounter, delay) 37 | 38 | } 39 | 40 | updateCounter(); 41 | } 42 | 43 | counterValue(); 44 | 45 | gsap.to(".counter", { 46 | duration : 0.25, 47 | delay : 3.5, 48 | opacity : 0, 49 | }) 50 | 51 | // gsap.to(".overlay", { 52 | // duration 53 | // }) 54 | 55 | gsap.to(".bar", 1.5,{ 56 | delay : 3.5, 57 | height : 0, 58 | stagger: { 59 | amount : 1 60 | }, 61 | onComplete: () => { 62 | setIsLoading(false) 63 | } 64 | 65 | }) 66 | 67 | 68 | 69 | }, [setIsLoading]); 70 | 71 | 72 | 73 | return ( 74 |
75 |

76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | 90 | ); 91 | }; 92 | 93 | export default LoadingScreenFirstOpen; -------------------------------------------------------------------------------- /app/(components)/HomePage.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | "use client" 3 | 4 | import React, { useRef } from 'react' 5 | import Container from './Container'; 6 | import Typewriter from 'typewriter-effect'; 7 | import {gsap} from 'gsap'; 8 | import {useGSAP} from '@gsap/react' 9 | import { ScrollTrigger } from "gsap/ScrollTrigger" 10 | import ParticlesComponent from './ParticleComponent' 11 | 12 | 13 | const HomePage = () => { 14 | const containerHome = useRef(null); 15 | 16 | useGSAP(() => { 17 | gsap.registerPlugin(ScrollTrigger); 18 | 19 | const tl = gsap.timeline() 20 | // animasi pada tulisan dan foto 21 | tl.from(".nama-evan", { opacity : 0, duration : 2, delay : 7 }) 22 | .from(".crafting", { scale : 0, duration : 0.2 }) 23 | .from(".text1 p", { scale : 0, duration : 0.1, stagger : 0.1 }) 24 | .from(".text2 p", { scale : 0, duration : 0.1, stagger : 0.1 }) 25 | .from(".text3 p", { scale : 0, duration : 0.1, stagger : 0.1 }) 26 | .from(".languages", {scale : 0, opacity : 0, duration : 0.5 }) 27 | .from(".scroll-down", { y : 100, opacity : 0, duration : 0.1 }) 28 | 29 | }, {scope : containerHome}) 30 | 31 | return ( 32 |
33 | {/* efek background bintang */} 34 | 35 | 36 | {/* Keterangan dan foto */} 37 |
38 | 39 | {/* Keterangan */} 40 |
41 |

Hi, I'm Evan Stefanus Candra

42 |

Crafting, Design As

43 |
44 | 45 |
46 |

F

47 |

r

48 |

o

49 |

n

50 |

t

51 |

e

52 |

n

53 |

d

54 |
55 | 56 |
57 |

W

58 |

e

59 |

b

60 |
61 | 62 |
63 |

D

64 |

e

65 |

v

66 |
67 | 68 |
69 |
70 | 79 |
80 |
81 | {/* 82 | */} 83 | {/* scroll down logo */} 84 |
85 | 88 |

Scroll Down

89 |
90 | 91 |
92 | 93 |
94 | 95 |
96 |
97 |
98 | ) 99 | } 100 | 101 | export default HomePage 102 | -------------------------------------------------------------------------------- /public/mysql-logo-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/(components)/ContactPage.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import React, { useEffect } from 'react' 3 | import Container from './Container' 4 | import { useNavContext } from '../context/navContext' 5 | import gsap from 'gsap' 6 | import SplitText from './SplitReveal' 7 | 8 | const ContactPage = () => { 9 | const {isLoading} = useNavContext() 10 | 11 | // logik cursor cahaya 12 | useEffect(() => { 13 | const handleMouseMove = (e: MouseEvent) => { 14 | gsap.to(".light-cursor", { 15 | x: e.clientX, 16 | y: e.clientY, 17 | duration: 0.3, 18 | ease: "power2.out", 19 | }); 20 | }; 21 | 22 | window.addEventListener("mousemove", handleMouseMove); 23 | return () => window.removeEventListener("mousemove", handleMouseMove); 24 | }, []) 25 | 26 | return ( 27 |
28 | {/* Efek cursor cahaya */} 29 |
32 | 33 | {/* */} 34 |
35 | 36 |
37 | {/* Tulisan Interest */} 38 |

39 | Your Project, My Expertise 40 |

41 | 42 | {/* Sosmed */} 43 | 54 |
55 | 56 | 57 | 58 | {/* form pengisian pesan */} 59 |
60 |
61 | {/* email */} 62 |
63 | 64 | 65 |
66 | {/* pesan */} 67 |
68 | 69 | 70 |
71 | {/* button */} 72 |
73 | 74 |
75 |
76 |
77 |
78 | 79 | 80 |
81 |
82 | ) 83 | } 84 | 85 | export default ContactPage 86 | -------------------------------------------------------------------------------- /app/(components)/ParticleComponent.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | /* eslint-disable @typescript-eslint/no-explicit-any */ 4 | 5 | import Particles, { initParticlesEngine } from "@tsparticles/react"; 6 | import { useCallback, useEffect, useMemo, useState } from "react"; 7 | // import { loadAll } from "@/tsparticles/all"; // if you are going to use `loadAll`, install the "@tsparticles/all" package too. 8 | // import { loadFull } from "tsparticles"; // if you are going to use `loadFull`, install the "tsparticles" package too. 9 | import { loadSlim } from "@tsparticles/slim"; // if you are going to use `loadSlim`, install the "@tsparticles/slim" package too. 10 | import { log } from "node:console"; 11 | // import { loadBasic } from "@tsparticles/basic"; // if you are going to use `loadBasic`, install the "@tsparticles/basic" package too. 12 | 13 | 14 | 15 | const ParticlesComponent = ({className, isActive = false, maxClicks = 10 }: {className? : string, isActive : boolean, maxClicks? : number}) => { 16 | 17 | 18 | const [init, setInit] = useState(false); 19 | const [clickCount, setClickCount] = useState(0); 20 | // this should be run only once per application lifetime 21 | useEffect(() => { 22 | initParticlesEngine(async (engine) => { 23 | // you can initiate the tsParticles instance (engine) here, adding custom shapes or presets 24 | // this loads the tsparticles package bundle, it's the easiest method for getting everything ready 25 | // starting from v2 you can add only the features you need reducing the bundle size 26 | //await loadAll(engine); 27 | //await loadFull(engine); 28 | await loadSlim(engine); 29 | //await loadBasic(engine); 30 | }).then(() => { 31 | setInit(true); 32 | }); 33 | }, []); 34 | 35 | const handleParticleClick = () => { 36 | if (clickCount < maxClicks) { 37 | setClickCount((prev) => prev + 1); 38 | } else { 39 | console.log("Batas klik tercapai!"); 40 | } 41 | }; 42 | 43 | const particlesLoaded = useCallback((container: any) => { 44 | if (container && container.interactivity) { 45 | container.interactivity.element.addEventListener("click", handleParticleClick); 46 | } 47 | }, [handleParticleClick]); 48 | 49 | 50 | 51 | const options : any = useMemo( 52 | () => ({ 53 | // background: { 54 | // color: { 55 | // value: "#1b1b1b", 56 | // }, 57 | // }, 58 | fpsLimit: 120, 59 | interactivity: { 60 | events: { 61 | onHover: { 62 | enable: true, 63 | mode: ["repulse", "grab"], 64 | }, 65 | }, 66 | modes: { 67 | bubble: { 68 | distance: 100, // Radius gelembung 69 | duration: 2, // Durasi efek gelembung 70 | opacity: 0.8, // Opacity gelembung 71 | size: 15, // Ukuran gelembung 72 | }, 73 | repulse: { 74 | distance: 110, // Radius dorongan 75 | duration: 1, // Durasi efek dorongan 76 | }, 77 | grab: { 78 | distance: 170, // Radius tarikan 79 | lineLinked: { 80 | opacity: 1, // Opacity garis 81 | }, 82 | } 83 | }, 84 | }, 85 | particles: { 86 | // color: { 87 | // value: ["#FF5733", "#33FF57", "#3357FF", "#FF33A1", "#FFFF33"], // Warna variatif 88 | // }, 89 | links: { 90 | color: ["#f0f0f0"], 91 | distance: 240, 92 | enable: true, 93 | opacity: 0.4, 94 | width: 1.1, 95 | }, 96 | move: { 97 | direction: "none", 98 | enable: true, 99 | outModes: { 100 | default: "bounce", 101 | }, 102 | random: true, 103 | speed: { min: 1, max: 3 }, 104 | straight: false, 105 | }, 106 | number: { 107 | density: { 108 | enable: true, 109 | }, 110 | value: 100, 111 | }, 112 | opacity: { 113 | value: 1.0, 114 | }, 115 | shape: { 116 | type: "image", 117 | options: { 118 | image : [ 119 | { src: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/JavaScript-logo.png/640px-JavaScript-logo.png", 120 | opacity: 0.7 121 | }, 122 | { src: "https://cdn.iconscout.com/icon/free/png-256/free-typescript-3521774-2945272.png?f=webp", 123 | opacity: 0.7 124 | }, 125 | { src: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/PHP-logo.svg/1280px-PHP-logo.svg.png", 126 | opacity: 0.7 127 | }, 128 | { src: "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Python-logo-notext.svg/1200px-Python-logo-notext.svg.png", 129 | opacity: 0.5 130 | }, 131 | { src: "https://upload.wikimedia.org/wikipedia/id/a/a9/MySQL.png", 132 | opacity: 0.5 133 | }, 134 | { src: "https://w7.pngwing.com/pngs/956/695/png-transparent-mongodb-original-wordmark-logo-icon-thumbnail.png", 135 | opacity: 0.5 136 | }, 137 | { src: "https://miro.medium.com/v2/resize:fit:1200/1*WA_9JsyqFkge2HwYKcdJQw.png", 138 | opacity: 0.5 139 | }, 140 | { src: "https://img.icons8.com/fluent-systems-filled/512/FFFFFF/nextjs.png", 141 | opacity: 0.5 142 | }, 143 | { src: "https://blogs.powercode.id/wp-content/uploads/2022/06/java-logo-vector.png", 144 | opacity: 0.5 145 | }, 146 | { src: "https://static-00.iconduck.com/assets.00/nestjs-icon-2048x2040-3rrvcej8.png", 147 | opacity: 0.5 148 | }, 149 | { src: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Laravel.svg/1200px-Laravel.svg.png", 150 | opacity: 0.5 151 | } 152 | ] 153 | } 154 | }, 155 | size: { 156 | value: { min: 5, max: 10 }, 157 | }, 158 | }, 159 | detectRetina: true, 160 | }), 161 | [maxClicks, clickCount], 162 | ); 163 | 164 | const handleClick = () => { 165 | if (clickCount < maxClicks) { 166 | setClickCount((prev) => prev + 1); 167 | } else { 168 | console.log("Max clicks reached"); 169 | } 170 | }; 171 | 172 | 173 | if (!isActive) return null; 174 | 175 | return ( 176 |
177 | 178 |
179 | 180 | 181 | ) 182 | 183 | 184 | 185 | }; 186 | 187 | export default ParticlesComponent; -------------------------------------------------------------------------------- /public/nestJS.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/(components)/ProjectPage.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | "use client" 3 | 4 | import React, { useEffect, useRef } from 'react' 5 | import Container from './Container' 6 | import StarEffect from './StarEffect' 7 | import Image from 'next/image' 8 | import { useNavContext } from '../context/navContext' 9 | import { data } from './DataWorks' 10 | import gsap from 'gsap' 11 | 12 | 13 | 14 | const ProjectPage = () => { 15 | const {isLoading} = useNavContext() 16 | const hoverRefs : any = useRef([]); 17 | 18 | 19 | useEffect(() => { 20 | // Set initial state untuk setiap detail card 21 | hoverRefs.current.forEach((card : any) => { 22 | gsap.set(card, { scale: 0, zIndex: -10, rotateY: 360 }); 23 | }); 24 | }, []); 25 | 26 | // Handle hover untuk animasi 27 | const handleMouseEnter = (index : any) => { 28 | gsap.to(hoverRefs.current[index], { 29 | scale: 1, 30 | zIndex: 50, 31 | duration: 0.5, 32 | rotateY: 0, // Untuk flip card 33 | ease: "power4.out", 34 | }); 35 | }; 36 | 37 | const handleMouseLeave = (index : any) => { 38 | gsap.to(hoverRefs.current[index], { 39 | scale: 0, 40 | zIndex: -10, 41 | duration: 0.5, 42 | rotateY: 360, // Balik ke posisi awal 43 | ease: "power4.in", 44 | }); 45 | }; 46 | return ( 47 |
48 | {/* */} 49 | 50 | 51 |

The Project

52 |
53 | {data.map((item, index) => ( 54 |
55 |
56 | handleMouseEnter(index)} 57 | onMouseLeave={() => handleMouseLeave(index)} 58 | src={item.image} alt={item.judul} className='w-full h-full project-image cursor-pointer ' /> 59 |
60 | 61 | 62 | {/* Detail Project */} 63 |
(hoverRefs.current[index] = el)} 65 | className={`cursor-default absolute -top-4 -right-2 sm:right-0 xl:-right-7 w-32 sm:w-40 md:w-52 xl:w-72 h-auto p-2 sm:p-3 bg-black bg-opacity-85 border rounded-lg space-y-3 scale-0 -z-10 transform rotate-y-360`} 66 | onMouseEnter={() => handleMouseEnter(index)} 67 | onMouseLeave={() => handleMouseLeave(index)}> 68 |

{item.judul}

69 |

{item.desc}

70 | {/* icon tech */} 71 |
72 | {item.icontech.map((item, index) => ( 73 |
{item}
74 | ))} 75 |
76 |
77 | {!item.link ? 78 | 79 | GitHub 80 |

Documentation

81 |
: 82 | 83 | GitHub 84 |

Github

85 |
86 | } 87 | 88 | 89 | {item.judul === "Diary App" ? ( 90 | 96 | ) : ( 97 | item.link && 98 | 99 | {/* icon link */} 100 | 101 | 102 | 103 |

Demo

104 | 105 |
106 | )} 107 |
108 |
109 |
110 | ))} 111 |
112 |
113 |
114 | ) 115 | } 116 | 117 | export default ProjectPage 118 | -------------------------------------------------------------------------------- /app/(components)/Navbar.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | /* eslint-disable @typescript-eslint/no-explicit-any */ 4 | 5 | "use client" 6 | 7 | import React, { useEffect, useRef, useState } from 'react' 8 | import { gsap } from 'gsap'; 9 | import { useNavContext } from '../context/navContext'; 10 | import { usePathname } from 'next/navigation'; 11 | 12 | const Navbar = () => { 13 | const {openNav, setOpenNav} = useNavContext(); 14 | const navRef = useRef(null); 15 | const [scroll, setScroll] = useState(false); 16 | const hideTimeoutRef = useRef(null); 17 | const pathname = usePathname(); 18 | const [activeSection, setActiveSection] = useState('home-page'); 19 | 20 | // intersection observer untuk mengatahui saat ini berada di page mana dengan // Scroll ke elemen yang sesuai 21 | const scrollToSection = (sectionId: string) => { 22 | const section = document.getElementById(sectionId); 23 | if (section) { 24 | section.scrollIntoView({ behavior: 'smooth', block: 'start' }); 25 | } 26 | setOpenNav(false); // Tutup navbar setelah klik 27 | }; 28 | 29 | // IntersectionObserver untuk mengetahui posisi aktif 30 | useEffect(() => { 31 | const sections = document.querySelectorAll('section'); 32 | const observer = new IntersectionObserver( 33 | (entries) => { 34 | entries.forEach((entry) => { 35 | if (entry.isIntersecting) { 36 | setActiveSection(entry.target.id); // Set id section yang aktif 37 | } 38 | }); 39 | }, 40 | { 41 | threshold: 0.5, // Adjust agar posisi dianggap aktif lebih responsif 42 | } 43 | ); 44 | 45 | sections.forEach((section) => observer.observe(section)); 46 | 47 | return () => observer.disconnect(); 48 | }, []); 49 | 50 | 51 | 52 | // handle open nav 53 | const handleOpenNav = () => { 54 | setOpenNav(!openNav); 55 | } 56 | 57 | // default navbar hidden 58 | useEffect(() => { 59 | if (navRef.current) { 60 | // Pastikan navbar tersembunyi secara default 61 | gsap.set(navRef.current, { x: "100%", visibility: "hidden" }); 62 | } 63 | }, []); 64 | 65 | // handle close nav 66 | useEffect(() => { 67 | const body = document.body; 68 | if (openNav) { 69 | // Menambahkan overflow-hidden agar halaman tidak bisa di-scroll 70 | body.style.overflow = "hidden"; 71 | 72 | // Animasi GSAP untuk menampilkan navbar 73 | gsap.to(navRef.current, { 74 | x: 0, // Geser ke posisi asli (dari kanan) 75 | visibility: "visible", 76 | duration: 1, // Durasi animasi 77 | ease: "power4.inOut", // Jenis easing 78 | }); 79 | 80 | // Animasi GSAP untuk memunculkan tombol "close" 81 | gsap.from(".close-button", { 82 | scale: 0, 83 | delay: 0.7, 84 | duration: 0.7, 85 | ease: "elastic.inOut", 86 | }) 87 | 88 | // Tampilkan overlay 89 | gsap.to(".overlay", { 90 | opacity: 0.5, // Transparansi overlay 91 | visibility: "visible", 92 | duration: 0.5, 93 | }); 94 | 95 | } else { 96 | // mengaktifkan scroll kembali pada body 97 | body.style.overflow = "auto"; 98 | 99 | 100 | // Animasi GSAP untuk menyembunyikan navbar 101 | gsap.to(navRef.current, { 102 | x: "100%", // Geser keluar ke kanan 103 | duration: 1, 104 | ease: "power4.inOut", 105 | onComplete: () => { 106 | gsap.set(navRef.current, { visibility: "hidden" }); 107 | } 108 | }); 109 | 110 | // Sembunyikan overlay 111 | gsap.to(".overlay", { 112 | opacity: 0, 113 | visibility: "hidden", 114 | duration: 1, 115 | }); 116 | } 117 | }, [openNav]); 118 | 119 | // Menyembunyikan navbar 120 | const hideNavbar = () => { 121 | if(!openNav) { 122 | gsap.to(".navbar", { 123 | y: "-100%", // Geser navbar ke atas 124 | duration: 0.4, 125 | ease: "power2.out", 126 | }); 127 | } 128 | 129 | }; 130 | 131 | // Menampilkan navbar 132 | const showNavbar = () => { 133 | gsap.to(".navbar", { 134 | y: "0%", // Geser navbar ke posisi semula 135 | duration: 0.4, 136 | ease: "power2.out", 137 | }); 138 | }; 139 | 140 | // menyembunyikan navbar jika tidak ada action scroll 141 | // Logika untuk menyembunyikan navbar jika tidak ada aktivitas 142 | useEffect(() => { 143 | if (openNav) { 144 | // Jika menu navbar terbuka, hentikan semua timer dan event listener 145 | if (hideTimeoutRef.current) { 146 | clearTimeout(hideTimeoutRef.current); 147 | } 148 | return; 149 | } 150 | 151 | // Timer awal untuk menyembunyikan navbar setelah 5 detik tanpa aktivitas 152 | hideTimeoutRef.current = setTimeout(() => { 153 | hideNavbar(); 154 | }, 4000); 155 | 156 | const resetHideTimeout = () => { 157 | // Tampilkan navbar saat ada aktivitas 158 | showNavbar(); 159 | 160 | // Reset timer jika ada aktivitas 161 | if (hideTimeoutRef.current) { 162 | clearTimeout(hideTimeoutRef.current); 163 | } 164 | 165 | // Mulai ulang timer untuk menyembunyikan navbar 166 | hideTimeoutRef.current = setTimeout(() => { 167 | hideNavbar(); 168 | }, 3000); 169 | }; 170 | 171 | // Daftar event untuk mendeteksi aktivitas 172 | const activityEvents = ["scroll", "keypress"]; 173 | activityEvents.forEach((event) => { 174 | window.addEventListener(event, resetHideTimeout); 175 | }); 176 | 177 | // Bersihkan event listener dan timer saat komponen di-unmount 178 | return () => { 179 | activityEvents.forEach((event) => { 180 | window.removeEventListener(event, resetHideTimeout); 181 | }); 182 | if (hideTimeoutRef.current) { 183 | clearTimeout(hideTimeoutRef.current); 184 | } 185 | }; 186 | }, [openNav]); // Gunakan openNav sebagai dependensi 187 | 188 | return ( 189 |
190 |
191 | {/* Judul Navbar */} 192 |
193 |

Evan Stefanus Candra

194 |
195 | 196 | {/* Button Navbar */} 197 |
handleOpenNav()} className='w-8 h-8 rounded-md cursor-pointer xl:w-10 xl:h-10 border xl:rounded-lg flex flex-col justify-evenly items-center'> 198 | 199 | 200 | 201 |
202 | 203 | {/* Menu Navbar */} 204 |
205 | {/* tombol close */} 206 |
handleOpenNav()} className='absolute cursor-pointer top-4 right-4 w-8 h-8 rounded-md xl:w-10 xl:h-10 border xl:rounded-lg flex justify-center items-center font-normal close-button'>X
207 | 208 | {/* menu dan content navbar */} 209 | 233 |
234 |
235 | 236 | {/* Background Overlay */} 237 |
handleOpenNav()} // Klik overlay juga nutup navbar 241 | >
242 |
243 | ) 244 | } 245 | 246 | export default Navbar 247 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | "use client" 3 | import Lenis from "lenis"; 4 | import HomePage from "./(components)/HomePage"; 5 | import AboutPage from "./(components)/AboutPage"; 6 | import ProjectPage from "./(components)/ProjectPage"; 7 | import ContactPage from "./(components)/ContactPage"; 8 | import {gsap} from "gsap"; 9 | import {ScrollTrigger} from "gsap/ScrollTrigger"; 10 | import { useEffect, useRef } from "react"; 11 | import { useNavContext } from "./context/navContext"; 12 | // import ParticlesComponent from "./(components)/ParticleComponent"; 13 | 14 | 15 | 16 | export default function Home() { 17 | const {openNav,isLoading} = useNavContext(); 18 | const lenisRef = useRef(null); 19 | 20 | // Initialize Lenis 21 | // Initialize Lenis once 22 | useEffect(() => { 23 | const lenis = new Lenis({ 24 | duration: 1.2, 25 | // infinite: true, 26 | }); 27 | 28 | lenisRef.current = lenis; 29 | 30 | // Use requestAnimationFrame to continuously update the scroll 31 | const raf = (time: number) => { 32 | lenis.raf(time); 33 | requestAnimationFrame(raf); 34 | } 35 | 36 | requestAnimationFrame(raf); 37 | 38 | // Link Lenis with ScrollTrigger 39 | lenis.on("scroll", ScrollTrigger.update); 40 | gsap.ticker.add((time) => { 41 | lenis.raf(time * 1000); 42 | }); 43 | 44 | return () => { 45 | // Cleanup Lenis when the component is unmounted 46 | lenis.destroy(); 47 | }; 48 | }, []); 49 | 50 | // mengatur agar lenis berhenti ketika open nav bernilai true 51 | useEffect(() => { 52 | const lenis = lenisRef.current; 53 | 54 | if (lenis) { 55 | if (openNav || isLoading) { 56 | lenis.stop(); // Pause Lenis when the navbar is open 57 | } else { 58 | lenis.start(); // Resume Lenis when the navbar is closed 59 | } 60 | } 61 | }, [openNav, isLoading]); // Re-run when openNav changes 62 | 63 | 64 | 65 | useEffect(() => { 66 | if(!isLoading) { 67 | // Animation on scroll 68 | gsap.registerPlugin(ScrollTrigger); 69 | 70 | // Ambil elemen dengan kelas '.gsap_animation' 71 | const gsapAnim = gsap.utils.toArray(".gsap_animation"); 72 | 73 | 74 | // Animasi untuk setiap elemen 75 | gsapAnim.forEach((section : any) => { 76 | gsap.to(section, { 77 | scrollTrigger: { 78 | trigger: section, 79 | start: "bottom bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 80 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 81 | scrub: true, // Buat animasi sinkron dengan scroll 82 | }, 83 | yPercent: 100, // Geser elemen ke bawah sebesar 100% 84 | ease: "none", 85 | }); 86 | }); 87 | 88 | // Ambil elemen dengan kelas '.gsap_animation' 89 | const parralaxAnim = gsap.utils.toArray(".parralax-wrap"); 90 | 91 | // Animasi untuk setiap elemen 92 | parralaxAnim.forEach((parralax : any) => { 93 | gsap.to(parralax, { 94 | scrollTrigger: { 95 | trigger: parralax, 96 | start: "top top", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 97 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 98 | scrub: true 99 | }, 100 | yPercent: -30, // Geser elemen ke keatas sebesar 40% 101 | ease: "none", 102 | }); 103 | }); 104 | 105 | ScrollTrigger.refresh() 106 | 107 | // scrolling effect tiap page 108 | // home-page 109 | gsap.to(".nama-evan", { 110 | scrollTrigger: { 111 | trigger: ".about-page", 112 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 113 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 114 | scrub: true 115 | }, 116 | x : -100, 117 | filter: "blur(7px)", 118 | ease: "power2.out", 119 | }) 120 | 121 | // kata kata creting 122 | gsap.to(".crafting", { 123 | scrollTrigger: { 124 | trigger: ".about-page", 125 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 126 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 127 | scrub: true 128 | }, 129 | x : -200, 130 | filter: "blur(7px)", 131 | ease: "power2.out", 132 | }) 133 | 134 | // kata kata frontend 135 | gsap.to(".front-end", { 136 | scrollTrigger: { 137 | trigger: ".about-page", 138 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 139 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 140 | scrub: true 141 | }, 142 | x : 300, 143 | filter: "blur(7px)", 144 | ease: "power2.out", 145 | }) 146 | 147 | // languages 148 | gsap.to(".languages", { 149 | scrollTrigger: { 150 | trigger: ".about-page", 151 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 152 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 153 | scrub: true 154 | }, 155 | x : 400, 156 | filter: "blur(7px)", 157 | ease: "power2.out", 158 | }) 159 | 160 | // scroll-down 161 | gsap.to(".scroll-down", { 162 | scrollTrigger: { 163 | trigger: ".about-page", 164 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 165 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 166 | scrub: true 167 | }, 168 | x : 500, 169 | filter: "blur(7px)", 170 | ease: "power2.out", 171 | }) 172 | 173 | // rotate the gear 174 | gsap.to(".gear", { 175 | scrollTrigger: { 176 | trigger: ".about-page", 177 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 178 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 179 | scrub: true 180 | }, 181 | rotate : 360, 182 | }) 183 | 184 | // Akhir Home Page 185 | 186 | // About PAge 187 | gsap.from(".about-page", { 188 | scrollTrigger: { 189 | trigger: ".about-page", 190 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 191 | end: "top top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 192 | scrub: true 193 | }, 194 | borderRadius : "40% 40% 0 0", 195 | duration : 1 196 | }) 197 | 198 | gsap.to(".about-title", { 199 | scrollTrigger: { 200 | trigger: ".project-page", 201 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 202 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 203 | scrub: true 204 | }, 205 | x : -200, 206 | filter: "blur(7px)", 207 | ease: "power2.out", 208 | }) 209 | 210 | // gear scroll 211 | gsap.to(".gear", { 212 | scrollTrigger: { 213 | trigger: ".project-page", 214 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 215 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 216 | scrub: true 217 | }, 218 | rotate : 360, 219 | }) 220 | 221 | // Line light 222 | gsap.to(".line-efek", { 223 | scrollTrigger: { 224 | trigger: ".project-page", 225 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 226 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 227 | scrub: true 228 | }, 229 | x : 400, 230 | ease: "power2.out", 231 | }) 232 | 233 | // Description 234 | gsap.to(".desc-about", { 235 | scrollTrigger: { 236 | trigger: ".project-page", 237 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 238 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 239 | scrub: true 240 | }, 241 | x : -300, 242 | filter: "blur(7px)", 243 | ease: "power2.out", 244 | }) 245 | 246 | // Skill and logo 247 | gsap.to(".skills-title", { 248 | scrollTrigger: { 249 | trigger: ".project-page", 250 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 251 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 252 | scrub: true 253 | }, 254 | x : 500, 255 | filter: "blur(7px)", 256 | ease: "power2.out", 257 | }) 258 | 259 | gsap.to(".logo-skills .logo", { 260 | scrollTrigger: { 261 | trigger: ".project-page", 262 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 263 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 264 | scrub: true, 265 | }, 266 | x: (index) => 600 + index * 20, // Geser lebih jauh berdasarkan indeks elemen 267 | filter: "blur(7px)", 268 | ease: "power2.out", 269 | }); 270 | 271 | // Akhir About Page 272 | 273 | 274 | // Project Page 275 | gsap.from(".project-page", { 276 | scrollTrigger: { 277 | trigger: ".project-page", 278 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 279 | end: "top top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 280 | scrub: true 281 | }, 282 | borderRadius : "40% 40% 0 0", 283 | duration : 1 284 | }) 285 | gsap.to(".project-title", { 286 | scrollTrigger: { 287 | trigger: ".contact-page", 288 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 289 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 290 | scrub: true 291 | }, 292 | x : -200, 293 | filter: "blur(7px)", 294 | ease: "power2.out", 295 | }) 296 | 297 | gsap.to(".project-image", { 298 | scrollTrigger: { 299 | trigger: ".contact-page", 300 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 301 | end: "bottom top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 302 | scrub: true 303 | }, 304 | scale : 2, 305 | filter: "blur(7px)", 306 | ease: "power2.out", 307 | }) 308 | // Akhir Project Page 309 | 310 | 311 | // Contact Page 312 | gsap.from(".contact-page", { 313 | scrollTrigger: { 314 | trigger: ".contact-page", 315 | start: "top bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 316 | end: "top top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 317 | scrub: true 318 | }, 319 | borderRadius : "40% 40% 0 0", 320 | duration : 1 321 | }) 322 | gsap.from(".text1-contact", { 323 | scrollTrigger: { 324 | trigger: ".contact-page", 325 | start: "center bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 326 | end: "top top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 327 | scrub: true 328 | }, 329 | xPercent : -100, 330 | filter: "blur(7px)", 331 | }) 332 | 333 | gsap.from(".text2-contact", { 334 | scrollTrigger: { 335 | trigger: ".contact-page", 336 | start: "center bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 337 | end: "top top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 338 | scrub: true 339 | }, 340 | xPercent : -200, 341 | filter: "blur(7px)", 342 | }) 343 | 344 | // form pesan 345 | gsap.from(".form-pesan-contact", { 346 | scrollTrigger: { 347 | trigger: ".contact-page", 348 | start: "center bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 349 | end: "top top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 350 | scrub: true 351 | }, 352 | xPercent : 200, 353 | filter: "blur(7px)", 354 | duration : 1 355 | }) 356 | 357 | // sosmed 358 | gsap.from(".sosmed-contact", { 359 | scrollTrigger: { 360 | trigger: ".contact-page", 361 | start: "center bottom", // Mulai animasi saat bagian bawah elemen mencapai bagian bawah viewport 362 | end: "top top", // Selesai animasi saat bagian bawah elemen mencapai bagian atas viewport 363 | scrub: true 364 | }, 365 | xPercent : -400, 366 | filter: "blur(7px)", 367 | duration : 1 368 | }) 369 | 370 | // Akhir Contact Page 371 | } 372 | 373 | }, [isLoading]); 374 | 375 | return ( 376 |
377 | 378 | 379 | 380 | 381 |
382 | ) 383 | } 384 | -------------------------------------------------------------------------------- /app/(components)/AboutPage.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React, { useRef } from 'react' 4 | import Container from './Container' 5 | import Image from 'next/image' 6 | import php from '../../public/php.png'; 7 | import python from '../../public/python.png'; 8 | import java from '../../public/java.png'; 9 | import js from '../../public/js.png'; 10 | import html from '../../public/html.png'; 11 | import css from '../../public/css.png'; 12 | import express from '../../public/express-js.png'; 13 | import nodejs from '../../public/nnode.png'; 14 | import laravel from '../../public/laravel.png'; 15 | import bootstrap from '../../public/bootstrap.png'; 16 | import mysql from '../../public/mysql.png'; 17 | import { useGSAP } from '@gsap/react'; 18 | import { gsap } from 'gsap'; 19 | import { useNavContext } from '../context/navContext'; 20 | import gear from '../../public/gear.png'; 21 | 22 | const AboutPage = () => { 23 | const aboutRef = useRef(null) 24 | const {isLoading} = useNavContext() 25 | 26 | useGSAP(() => { 27 | gsap.fromTo( 28 | ".light", 29 | { left: '-50%' }, 30 | { 31 | left: '100%', 32 | duration: 2.2, 33 | repeat: -1, 34 | yoyo: true, 35 | ease: 'linear', 36 | } 37 | ); 38 | 39 | // GSAP animasi untuk gelombang 40 | gsap.fromTo( 41 | ".wave-text span", 42 | { 43 | y: (index) => (index % 2 === 0 ? -10 : 10), // Huruf genap ke atas, ganjil ke bawah 44 | }, 45 | { 46 | y: 0, // Kembali ke posisi awal 47 | repeat: -1, // Ulangi terus-menerus 48 | yoyo: true, // Kembali ke posisi awal setelah animasi selesai 49 | stagger: 0.1, // Jeda antar huruf 50 | duration: 0.7, // Durasi animasi per huruf 51 | ease: "power1.inOut", // Animasi yang halus 52 | } 53 | ); 54 | 55 | }, {scope : aboutRef}) 56 | 57 | return ( 58 |
59 | 60 |
61 |
62 |
63 | gear 64 |
65 |

66 | {"About Me".split("").map((letter, index) => ( 67 | 72 | {letter === " " ? "\u00A0" : letter} 73 | 74 | ))} 75 |

76 |
77 | gear 78 |
79 |
80 | 81 | {/* efek line */} 82 |
83 |
93 |
94 | 95 |
96 |

I enjoy showcasing my skills in a more visual and engaging way. My journey in web development has allowed me to create responsive and modern designs that prioritize both creativity and user experience. I constantly push my limits to craft intuitive and aesthetically pleasing interfaces.On the back-end, I specialize in JavaScript and PHP, working with frameworks like ExpressJS, NestJS, NodeJS, and Laravel. My focus is on building scalable, efficient, and secure applications while continuously improving my expertise in these technologies.Beyond coding, I love exploring new ideas and playing FPS games to relax, stay sharp, and find inspiration. With every project, I strive to learn, grow, and deliver innovative solutions that make an impact.

97 |
98 | 99 |
100 |

Skills

101 | 102 | {/* Logo Skills */} 103 |
104 | {/* JavaScript */} 105 |
106 |
107 | js 108 | JavaScript 109 |
110 |
111 | 112 | {/* TypeScript */} 113 |
114 |
115 |
116 | 117 |
118 | TypeScript 119 |
120 |
121 | 122 | {/* PHP */} 123 |
124 |
125 | 126 | PHP 127 |
128 |
129 | 130 | {/* Pyhton */} 131 |
132 |
133 | 134 | Python 135 |
136 |
137 | 138 | {/* Java */} 139 |
140 |
141 | 142 | Java 143 |
144 |
145 | 146 | {/* HTML */} 147 |
148 |
149 | 150 | HTML 151 |
152 |
153 | 154 | {/* CSS */} 155 |
156 |
157 | 158 | CSS 159 |
160 |
161 | 162 | {/* NextJS */} 163 |
164 |
165 | 166 | NextJS 167 |
168 |
169 | 170 | {/* ReactJs */} 171 |
172 |
173 | 174 | ReactJS 175 |
176 |
177 | 178 | {/* NodeJS */} 179 |
180 |
181 | node 182 | NodeJS 183 |
184 |
185 | 186 | {/* ExpressJS */} 187 |
188 |
189 | express 190 | ExpressJS 191 |
192 |
193 | 194 | {/* NestJS */} 195 |
196 |
197 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | NestJS 208 |
209 |
210 | 211 | {/* Laravel */} 212 |
213 |
214 | laravel 215 | Laravel 216 |
217 |
218 | 219 | {/* TailwindCSS */} 220 |
221 |
222 | 223 | TailwindCSS 224 |
225 |
226 | 227 | {/* Bootstrap */} 228 |
229 |
230 | bootstrap 231 | Bootstrap 232 |
233 |
234 | 235 | {/* Prisma */} 236 |
237 |
238 | 239 | 240 | 241 | Prisma 242 |
243 |
244 | 245 | {/* MySQl */} 246 |
247 |
248 | mysql 249 | MySQL 250 |
251 |
252 | 253 | {/* MongoDB */} 254 |
255 |
256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | MongoDB 268 |
269 |
270 | 271 | {/* Supabase */} 272 |
273 |
274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | Supabase 290 |
291 |
292 |
293 | 294 |
295 |
296 | 297 |
298 | ) 299 | } 300 | 301 | export default AboutPage 302 | -------------------------------------------------------------------------------- /app/(components)/DataWorks.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image" 2 | import satu from "../../public/project-sembilan.png" 3 | import dua from "../../public/Screenshot 2024-06-12 085200.png" 4 | import tiga from "../../public/project-tujuh.png" 5 | import empat from "../../public/project-enam.png" 6 | import lima from "../../public/project-empat.png" 7 | import gsap from "../../public/download.jpeg" 8 | import three from "../../public/three js.png" 9 | import enam from "../../public/project-sepuluh.png" 10 | import nodejs from "../../public/nnode.png" 11 | 12 | 13 | export const data = [ 14 | { 15 | id : 9, 16 | judul : "Animation 3D Model Web", 17 | image : satu, 18 | desc : "mengimplementasikan desain web 3D yang sudah saya pelajari hanya untuk sekedar latihan saja cara membuat web 3D jadi mungkin kedepannya saya akan membuat website yang ada unsur 3D saya menggunakkan webstie skectfab.com untuk mencari asset 3D model yang saya mau kendala saat membuat website ini adalah setup webgi yang agak rumit, namun saya ada kendala di bagian preview mode sehingga user bisa merotate 3d model tersebut pada saat di development masih berjalan lancar namun setelah naik ke production user tidak bisa merotate 3d model tersebut jadi, untuk sementara saya masih menonaktifkan fitur tersebut. dan mencoba debugging terlebih dahulu", 19 | link : "https://vortex-gt-8.vercel.app/", 20 | source : "https://github.com/evanstef/Animation-Web-3D", 21 | tech : [ 22 | "NextJs", 23 | "TailwindCSS", 24 | "GSAP", 25 | "Web GI(Three.js)", 26 | "Typescript" 27 | ], 28 | icontech : [ 29 | // NextJS 30 | , 31 | 32 | // TailwindCSS 33 | , 34 | 35 | // GSAP 36 | gsap, 37 | 38 | // WebGI 39 | webgi, 40 | 41 | // Typescript 42 | 43 | ] 44 | }, 45 | { 46 | id : 8, 47 | judul : "Netflix Clone", 48 | image : dua, 49 | desc : "Website ini merupakan website netflix clone seperti hal nya netflix namun bedanya website ini hanya untuk mencari film - film bukan buat streaming filmnya dan juga semua film yang anda cari bisa anda tambahkan ke wishlist anda namun dengan catatan jika ingin membuka website ini harus login dulu", 50 | link : "https://netflix-clone-evan.vercel.app/", 51 | source : "https://github.com/evanstef/Netflix-Clone", 52 | tech : [ 53 | "NextJS", 54 | "TailwindCSS", 55 | "ShadcnUI", 56 | "Framer Motion", 57 | "Axios", 58 | "NextAuth", 59 | "Typescript", 60 | "Supabase", 61 | "Prisma", 62 | ], 63 | icontech : [ 64 | // NextJS 65 | , 66 | 67 | // TailwindCSS 68 | , 69 | 70 | // Shadcn Ui 71 | , 72 | 73 | // Framer Motion 74 | , 75 | 76 | // Axios 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | , 85 | 86 | // TypeScript 87 | , 88 | 89 | // Supabase 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | , 105 | 106 | // Prisma 107 | 108 | 109 | 110 | ] 111 | 112 | 113 | }, 114 | { 115 | id : 7, 116 | judul : "Sekaben Camp", 117 | image : tiga, 118 | desc : "Sekaben Camp merupakan jasa sewa alat-alat camping yang ada di Bangka Belitung ini merupakan website company profile dari perusahaan Sekaben Camp, Sekaben camp merupakan salah satu bisa di bilang startup di bidang penyewaan jasa alat camping dengan memanfaatkan wisata alam yang banyak di daerah Bangka Belitung sehingga memungkinkan untuk membuka usaha sewa alat camping karena Bangka terkenal akan pantai,bukit,hutan dan wisata alam lainnya jadi buat temen-temen yang hobi camping tidak perlu bingung jika tidak punya alat camping Sekaben Camp bisa menjadi pilihan dan yang pasti dengan harga yang terjangkau. tantangan dalam mengelola website ini adalah soal SEO nya awalnya saya ingin menggunakkan domain bawaan vercel dengan menggunakkan DNS provider cloudflare namun sangat sulit untuk mengkonekan si cloudflare ke vercel jadi saya memutuskan untuk membeli domain .my.id karena harganya murah sehingga website bisa di daftarkan di google search console dan SEO nya berjalan", 119 | link : "https://sekabencampid.my.id/", 120 | source : "https://github.com/evanstef/Sekaben-camp", 121 | tech : [ 122 | "NextJS", 123 | "DaisyUI", 124 | "Framer Motion", 125 | "TailwindCSS", 126 | "TypeScript" 127 | ], 128 | icontech : [ 129 | // Next Js 130 | , 131 | 132 | // Framer 133 | , 134 | 135 | // DaisyUi 136 | 137 | 138 | 139 | 140 | 141 | , 142 | 143 | 144 | // Tailwind 145 | , 146 | 147 | // TS 148 | 149 | 150 | 151 | 152 | ] 153 | }, 154 | 155 | { 156 | id : 6, 157 | judul : "Diary App", 158 | image : empat, 159 | desc : "Web App Untuk menulis semua diary dan saling sharing kepada semua user yang ada, dengan menggunakkan supabase sebagai database untuk menampung semua data usernya tantangan dalam membuat website ini adalah saya masih mengalami error setelah kita mengcreate diary dan kemudian di redirect ke halaman my diary terjadi erro page not found padahal saat saya jalankan di mode development tidak terjadi kesalahan sama sekali dan ini juga hasil pembelajaran saya mengikuti kursus dari Bang Dea Afrizal Special thanks To Bang Dea Afrizal", 160 | link : "https://diary-app-lec.vercel.app/", 161 | source : "https://github.com/evanstef/Diary-App", 162 | tech : [ 163 | "NextJS", 164 | "Supabase", 165 | "DaisyUI", 166 | "Framer Motion", 167 | "TailwindCSS", 168 | "TypeScript" 169 | ], 170 | icontech : [ 171 | // Next Js 172 | , 173 | 174 | // Supabase 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | , 190 | 191 | // Framer 192 | , 193 | 194 | // DaisyUi 195 | 196 | 197 | 198 | 199 | 200 | , 201 | 202 | 203 | // Tailwind 204 | , 205 | 206 | // TS 207 | 208 | 209 | ] 210 | }, 211 | 212 | { 213 | id : 5, 214 | judul : "API Books", 215 | image : enam, 216 | desc : "API buku yang saya buat sendiri yang mana datanya lebih dari 500+ judul buku untuk sekarang hanya ada novel saja semoga kedepannya bisa makin bertambah bersumber dari salah satu website buku yaitu https://www.goodreads.com/", 217 | // link : "https://evan-movie-search.vercel.app/", 218 | source : "https://github.com/evanstef/nest-books-api", 219 | tech : [ 220 | "NestJS", 221 | "NodeJs", 222 | "MySql", 223 | "Prisma", 224 | "Supabase", 225 | ], 226 | icontech : [ 227 | // NestJs 228 | , 229 | // NodeJs 230 | nest, 231 | 232 | // MY SQL 233 | , 234 | 235 | // Prisma 236 | 237 | 238 | , 239 | 240 | // Supabase 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | ] 259 | }, 260 | 261 | { 262 | id : 4, 263 | judul : "Rawg clone", 264 | image : lima, 265 | desc : "website pencarian semua game game baik dari yang lama maupun game yang baru keluar serta mendapatkan detail dari game tersebut saya menggunakkan metode consume API yang saya ambil API nya dari rawg io dan website ini dibuat dengan menggunakkan framework nextJs, tantangan dalam membuat website ini saya tidak bisa mengambil data trailer data masing masing data game yang tersedia sehingga saya hardcode di bagian trailer game jadi data yang ada di bagian trailer game tidak terupdate harus di update secara manual", 266 | link : "https://rawg-io-clone-peach.vercel.app/", 267 | source : "https://github.com/evanstef/rawg-io-clone", 268 | tech : [ 269 | "NextJS", 270 | "TailwindCSS", 271 | "TypeScript", 272 | "RAWG API", 273 | "Framer Motion" 274 | ], 275 | icontech : [ 276 | // Next 277 | , 278 | 279 | // Axios 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | , 288 | 289 | // Framer 290 | , 291 | 292 | // Tailwind 293 | , 294 | 295 | // TS 296 | 297 | 298 | 299 | 300 | ] 301 | 302 | }, 303 | 304 | // { 305 | // id : 3, 306 | // judul : "Next Auth Course", 307 | // image : lima, 308 | // desc : "hasil course selama 8 jam memahami penggunaan next auth, mulai dari two factor, reset password, update role, resend email confirmation, saya menggunakkan PostgreSQL melalui neon tech sebagai database untuk menghubungkan ke Postgre saya menggunakkan ORM Prisma tantangan dalam membuat website ini saya tidak bisa mengirimkan resend email ke semua email dikarenakan persayaratan harus memiliki domain yang berbayar agar bisa mengirimkan resend email ke semua email bukan hanya pada email yang terdaftar di dashboard resend dengan code with antonio channel", 309 | // link : "https://next-auth-course.vercel.app/", 310 | // source : "https://github.com/evanstef/next-auth-course", 311 | // tech : [ 312 | // "NextJS", 313 | // "TailwindCSS", 314 | // "ShadcnUI", 315 | // "NextAuth", 316 | // "TypeScript", 317 | // "Resend", 318 | // "Prisma", 319 | // "Neon Tech" 320 | // ], 321 | // icontech : [ 322 | // // Next 323 | // , 324 | 325 | // // ShadcnUI 326 | // , 327 | 328 | 329 | // // Prisma 330 | // 331 | // 332 | // , 333 | 334 | // // Neon 335 | // 336 | // 344 | // 345 | // 346 | // 347 | // 348 | // 349 | // 350 | // 351 | // 352 | // 353 | // 354 | // , 355 | 356 | // // Tailwind 357 | // , 358 | 359 | // // TS 360 | // 361 | 362 | 363 | 364 | // ] 365 | // }, 366 | 367 | // { 368 | // id : 2, 369 | // judul : "Tailwind CSS Website", 370 | // image : dua, 371 | // desc : "website yang sudah ada sebelumnya dari pak sandhika galih web programming unpas yang saya modifikasi lagi dengan UI yang lebih menarik terima kasih pak dhika", 372 | // link : "https://tailwind-css-website-kappa.vercel.app/", 373 | // source : "https://github.com/evanstef/tailwind-css-website", 374 | // tech : [ 375 | // "HTML", 376 | // "TailwindCSS", 377 | // "Javascript", 378 | // "JQuery" 379 | // ], 380 | // icontech : [ 381 | // // HTML 382 | // 383 | // 384 | // , 385 | 386 | // // CSS 387 | // 388 | // 389 | // , 390 | 391 | // // JS 392 | // 393 | // 394 | // , 395 | 396 | // // JQuery 397 | // 398 | // 399 | // 400 | // ] 401 | // }, 402 | 403 | // { 404 | // id : 1, 405 | // judul : "Parallax Animated Website (Desktop Only)", 406 | // image : tiga, 407 | // desc : "membuat web perpindahan 4 musim dengan konsep parallax dengan menggunakan animasi sederhana dibuat dari vanilla javascript dan juga vanilla css", 408 | // link : "https://parallax-website-tan-one.vercel.app/", 409 | // source : "https://github.com/evanstef/parallax-website", 410 | // tech : [ 411 | // "HTML", 412 | // "CSS", 413 | // "Javascript", 414 | // ], 415 | // icontech : [ 416 | // // HTML 417 | // 418 | // 419 | // , 420 | 421 | // // CSS 422 | // 423 | // 424 | // , 425 | 426 | // // JS 427 | // 428 | // 429 | // 430 | // ] 431 | // }, 432 | ] --------------------------------------------------------------------------------