├── .gitignore ├── LICENSE ├── README.md ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── public ├── draco │ ├── a.txt │ ├── draco_decoder.js │ └── draco_decoder.wasm ├── images │ ├── express.webp │ ├── javascript.webp │ ├── mongo.webp │ ├── mysql.webp │ ├── next.webp │ ├── next1.webp │ ├── next2.webp │ ├── nextBL.webp │ ├── node.webp │ ├── node2.webp │ ├── placeholder.webp │ ├── react.webp │ ├── react2.webp │ └── typescript.webp └── models │ ├── b.txt │ ├── char_enviorment.hdr │ ├── character.enc │ ├── character.glb │ └── encrypt.cjs ├── src ├── App.css ├── App.tsx ├── assets │ └── react.svg ├── components │ ├── About.tsx │ ├── Career.tsx │ ├── Character │ │ ├── Scene.tsx │ │ ├── exports.ts │ │ ├── index.tsx │ │ └── utils │ │ │ ├── animationUtils.ts │ │ │ ├── character.ts │ │ │ ├── decrypt.ts │ │ │ ├── lighting.ts │ │ │ ├── mouseUtils.ts │ │ │ └── resizeUtils.ts │ ├── Contact.tsx │ ├── Cursor.tsx │ ├── HoverLinks.tsx │ ├── Landing.tsx │ ├── Loading.tsx │ ├── MainContainer.tsx │ ├── Navbar.tsx │ ├── SocialIcons.tsx │ ├── TechStack.tsx │ ├── WhatIDo.tsx │ ├── Work.tsx │ ├── WorkImage.tsx │ ├── styles │ │ ├── About.css │ │ ├── Career.css │ │ ├── Contact.css │ │ ├── Cursor.css │ │ ├── Landing.css │ │ ├── Loading.css │ │ ├── Navbar.css │ │ ├── SocialIcons.css │ │ ├── WhatIDo.css │ │ ├── Work.css │ │ └── style.css │ └── utils │ │ ├── GsapScroll.ts │ │ ├── initialFX.ts │ │ └── splitText.ts ├── context │ └── LoadingProvider.tsx ├── data │ └── boneData.ts ├── index.css ├── main.tsx └── vite-env.d.ts ├── test.js ├── tsconfig.app.json ├── tsconfig.app.tsbuildinfo ├── tsconfig.json ├── tsconfig.node.json ├── tsconfig.node.tsbuildinfo └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .npmrc 26 | 27 | backup/ 28 | .env -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Kabir Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # My Portfolio Website - Overview 🚀 2 | 3 | This repository contains the open source version of my 3D portfolio website. Do check it out! 4 | 5 | ## Instructions 🛠️ 6 | 7 | I have modified the GSAP club plugins with the trial plugins, but with the trial plugin, you cannot host it⛔️. For Club plugins, check out here: [GSAP Installation](https://gsap.com/docs/v3/Installation/). 8 | 9 | **Techstack** - React, TypeScript, GSAP, ThreeJS, WebGL, HTML, CSS, JavaScript 10 | 11 | ### Installation 12 | 13 | 1. Clone the repository to your local machine: 14 | 15 | ```bash 16 | git clone 17 | cd 18 | ``` 19 | 20 | 2. Install the required packages: 21 | 22 | ```bash 23 | npm install 24 | ``` 25 | 26 | 3. Install React: 27 | 28 | ```bash 29 | npm i react 30 | ``` 31 | 32 | 4. Install GSAP for React: 33 | 34 | You can choose to install GSAP specifically for React or the general GSAP package: 35 | 36 | For React: 37 | ```bash 38 | npm i gsap/react 39 | ``` 40 | 41 | Or the general GSAP package: 42 | ```bash 43 | npm i gsap 44 | ``` 45 | 46 | 5. Run the development server: 47 | 48 | ```bash 49 | npm run dev 50 | ``` 51 | 52 | Open your browser and navigate to `http://localhost:3000` to view the website. 53 | 54 | ## Features 55 | 56 | - 3D interactive UI built with ThreeJS and WebGL 57 | - Smooth animations using GSAP 58 | - Responsive design for various screen sizes 59 | 60 | ## Contributing 61 | 62 | Contributions are welcome! Please follow these steps: 63 | 64 | 1. Fork the repository. 65 | 2. Create a new branch (`git checkout -b feature-branch`). 66 | 3. Commit your changes (`git commit -m 'Add new feature'`). 67 | 4. Push to the branch (`git push origin feature-branch`). 68 | 5. Open a pull request. 69 | 70 | ## License 71 | 72 | This project is licensed under the MIT License. See the LICENSE file for details. 73 | 74 | 75 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | KABIR SINGH - Creative Developer | Designer 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kabir-portfolio", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@gsap/react": "^2.1.2", 14 | "@react-three/cannon": "^6.6.0", 15 | "@react-three/drei": "^9.120.4", 16 | "@react-three/fiber": "^8.17.10", 17 | "@react-three/postprocessing": "^2.16.3", 18 | "@react-three/rapier": "^1.5.0", 19 | "@types/three": "^0.168.0", 20 | "@vercel/analytics": "^1.4.1", 21 | "gsap": "^3.12.7", 22 | "gsap-trial": "^3.12.7", 23 | "react": "^18.3.1", 24 | "react-dom": "^18.3.1", 25 | "react-fast-marquee": "^1.6.5", 26 | "react-icons": "^5.3.0", 27 | "three": "^0.168.0", 28 | "three-stdlib": "^2.33.0" 29 | }, 30 | "devDependencies": { 31 | "@eslint/js": "^9.9.0", 32 | "@types/react": "^18.3.3", 33 | "@types/react-dom": "^18.3.0", 34 | "@vitejs/plugin-react": "^4.3.1", 35 | "eslint": "^9.9.0", 36 | "eslint-plugin-react-hooks": "^5.1.0-rc.0", 37 | "eslint-plugin-react-refresh": "^0.4.9", 38 | "globals": "^15.9.0", 39 | "typescript": "^5.5.3", 40 | "typescript-eslint": "^8.0.1", 41 | "vite": "^5.4.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/draco/a.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/draco/a.txt -------------------------------------------------------------------------------- /public/draco/draco_decoder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/draco/draco_decoder.wasm -------------------------------------------------------------------------------- /public/images/express.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/express.webp -------------------------------------------------------------------------------- /public/images/javascript.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/javascript.webp -------------------------------------------------------------------------------- /public/images/mongo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/mongo.webp -------------------------------------------------------------------------------- /public/images/mysql.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/mysql.webp -------------------------------------------------------------------------------- /public/images/next.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/next.webp -------------------------------------------------------------------------------- /public/images/next1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/next1.webp -------------------------------------------------------------------------------- /public/images/next2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/next2.webp -------------------------------------------------------------------------------- /public/images/nextBL.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/nextBL.webp -------------------------------------------------------------------------------- /public/images/node.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/node.webp -------------------------------------------------------------------------------- /public/images/node2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/node2.webp -------------------------------------------------------------------------------- /public/images/placeholder.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/placeholder.webp -------------------------------------------------------------------------------- /public/images/react.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/react.webp -------------------------------------------------------------------------------- /public/images/react2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/react2.webp -------------------------------------------------------------------------------- /public/images/typescript.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/images/typescript.webp -------------------------------------------------------------------------------- /public/models/b.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/models/b.txt -------------------------------------------------------------------------------- /public/models/char_enviorment.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/models/char_enviorment.hdr -------------------------------------------------------------------------------- /public/models/character.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/public/models/character.enc -------------------------------------------------------------------------------- /public/models/character.glb: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c287a4d10bbbbc4c779f7b0a68b666b530ef9754142843aa048b4d5976c0d052 3 | size 1537316 4 | -------------------------------------------------------------------------------- /public/models/encrypt.cjs: -------------------------------------------------------------------------------- 1 | const crypto = require("crypto"); 2 | const fs = require("fs"); 3 | 4 | const encryptFile = (inputFile, outputFile, password) => { 5 | const key = crypto.createHash("sha256").update(password).digest(); 6 | const iv = crypto.randomBytes(16); 7 | 8 | const cipher = crypto.createCipheriv("aes-256-cbc", key, iv); 9 | const input = fs.createReadStream(inputFile); 10 | const output = fs.createWriteStream(outputFile); 11 | 12 | output.write(iv); 13 | input.pipe(cipher).pipe(output); 14 | }; 15 | 16 | encryptFile("character.glb", "character.enc", "Character3D#@"); 17 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .section-container { 2 | width: 1300px; 3 | } 4 | .title, 5 | .para { 6 | font-kerning: none; 7 | -webkit-text-rendering: optimizeSpeed; 8 | text-rendering: optimizeSpeed; 9 | -webkit-transform: translateZ(0); 10 | transform: translateZ(0); 11 | } 12 | @media only screen and (max-width: 1600px) { 13 | .section-container { 14 | width: 1200px; 15 | max-width: calc(100% - 160px); 16 | } 17 | } 18 | @media only screen and (max-width: 1400px) { 19 | .section-container { 20 | width: 900px; 21 | } 22 | } 23 | @media only screen and (max-width: 900px) { 24 | .section-container { 25 | width: 500px; 26 | max-width: var(--cWidth); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { lazy, Suspense } from "react"; 2 | import "./App.css"; 3 | 4 | const CharacterModel = lazy(() => import("./components/Character")); 5 | const MainContainer = lazy(() => import("./components/MainContainer")); 6 | import { LoadingProvider } from "./context/LoadingProvider"; 7 | 8 | const App = () => { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default App; 25 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/About.tsx: -------------------------------------------------------------------------------- 1 | import "./styles/About.css"; 2 | 3 | const About = () => { 4 | return ( 5 |
6 |
7 |

About Me

8 |

9 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic quis 10 | dolores numquam iusto Ratione earum ducimus autem id iure pariatur 11 | dolorum quae maiores. 12 |

13 |
14 |
15 | ); 16 | }; 17 | 18 | export default About; 19 | -------------------------------------------------------------------------------- /src/components/Career.tsx: -------------------------------------------------------------------------------- 1 | import "./styles/Career.css"; 2 | 3 | const Career = () => { 4 | return ( 5 |
6 |
7 |

8 | My career & 9 |
experience 10 |

11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |

Position In Company

19 |
Company Name
20 |
21 |

20XX

22 |
23 |

24 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim 25 | labore sit non ipsum temporibus quidem, deserunt eaque officiis 26 | mollitia ratione suscipit repellat. 27 |

28 |
29 |
30 |
31 |
32 |

Position In Company

33 |
Company Name
34 |
35 |

20XX

36 |
37 |

38 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim 39 | labore sit non ipsum temporibus quidem, deserunt eaque officiis 40 | mollitia ratione suscipit repellat. 41 |

42 |
43 |
44 |
45 |
46 |

Position In Company

47 |
Company Name
48 |
49 |

NOW

50 |
51 |

52 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim 53 | labore sit non ipsum temporibus quidem, deserunt eaque officiis 54 | mollitia ratione suscipit repellat. 55 |

56 |
57 |
58 |
59 |
60 | ); 61 | }; 62 | 63 | export default Career; 64 | -------------------------------------------------------------------------------- /src/components/Character/Scene.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | import * as THREE from "three"; 3 | import setCharacter from "./utils/character"; 4 | import setLighting from "./utils/lighting"; 5 | import { useLoading } from "../../context/LoadingProvider"; 6 | import handleResize from "./utils/resizeUtils"; 7 | import { 8 | handleMouseMove, 9 | handleTouchEnd, 10 | handleHeadRotation, 11 | handleTouchMove, 12 | } from "./utils/mouseUtils"; 13 | import setAnimations from "./utils/animationUtils"; 14 | import { setProgress } from "../Loading"; 15 | 16 | const Scene = () => { 17 | const canvasDiv = useRef(null); 18 | const hoverDivRef = useRef(null); 19 | const sceneRef = useRef(new THREE.Scene()); 20 | const { setLoading } = useLoading(); 21 | 22 | const [character, setChar] = useState(null); 23 | useEffect(() => { 24 | if (canvasDiv.current) { 25 | let rect = canvasDiv.current.getBoundingClientRect(); 26 | let container = { width: rect.width, height: rect.height }; 27 | const aspect = container.width / container.height; 28 | const scene = sceneRef.current; 29 | 30 | const renderer = new THREE.WebGLRenderer({ 31 | alpha: true, 32 | antialias: true, 33 | }); 34 | renderer.setSize(container.width, container.height); 35 | renderer.setPixelRatio(window.devicePixelRatio); 36 | renderer.toneMapping = THREE.ACESFilmicToneMapping; 37 | renderer.toneMappingExposure = 1; 38 | canvasDiv.current.appendChild(renderer.domElement); 39 | 40 | const camera = new THREE.PerspectiveCamera(14.5, aspect, 0.1, 1000); 41 | camera.position.z = 10; 42 | camera.position.set(0, 13.1, 24.7); 43 | camera.zoom = 1.1; 44 | camera.updateProjectionMatrix(); 45 | 46 | let headBone: THREE.Object3D | null = null; 47 | let screenLight: any | null = null; 48 | let mixer: THREE.AnimationMixer; 49 | 50 | const clock = new THREE.Clock(); 51 | 52 | const light = setLighting(scene); 53 | let progress = setProgress((value) => setLoading(value)); 54 | const { loadCharacter } = setCharacter(renderer, scene, camera); 55 | 56 | loadCharacter().then((gltf) => { 57 | if (gltf) { 58 | const animations = setAnimations(gltf); 59 | hoverDivRef.current && animations.hover(gltf, hoverDivRef.current); 60 | mixer = animations.mixer; 61 | let character = gltf.scene; 62 | setChar(character); 63 | scene.add(character); 64 | headBone = character.getObjectByName("spine006") || null; 65 | screenLight = character.getObjectByName("screenlight") || null; 66 | progress.loaded().then(() => { 67 | setTimeout(() => { 68 | light.turnOnLights(); 69 | animations.startIntro(); 70 | }, 2500); 71 | }); 72 | window.addEventListener("resize", () => 73 | handleResize(renderer, camera, canvasDiv, character) 74 | ); 75 | } 76 | }); 77 | 78 | let mouse = { x: 0, y: 0 }, 79 | interpolation = { x: 0.1, y: 0.2 }; 80 | 81 | const onMouseMove = (event: MouseEvent) => { 82 | handleMouseMove(event, (x, y) => (mouse = { x, y })); 83 | }; 84 | let debounce: number | undefined; 85 | const onTouchStart = (event: TouchEvent) => { 86 | const element = event.target as HTMLElement; 87 | debounce = setTimeout(() => { 88 | element?.addEventListener("touchmove", (e: TouchEvent) => 89 | handleTouchMove(e, (x, y) => (mouse = { x, y })) 90 | ); 91 | }, 200); 92 | }; 93 | 94 | const onTouchEnd = () => { 95 | handleTouchEnd((x, y, interpolationX, interpolationY) => { 96 | mouse = { x, y }; 97 | interpolation = { x: interpolationX, y: interpolationY }; 98 | }); 99 | }; 100 | 101 | document.addEventListener("mousemove", (event) => { 102 | onMouseMove(event); 103 | }); 104 | const landingDiv = document.getElementById("landingDiv"); 105 | if (landingDiv) { 106 | landingDiv.addEventListener("touchstart", onTouchStart); 107 | landingDiv.addEventListener("touchend", onTouchEnd); 108 | } 109 | const animate = () => { 110 | requestAnimationFrame(animate); 111 | if (headBone) { 112 | handleHeadRotation( 113 | headBone, 114 | mouse.x, 115 | mouse.y, 116 | interpolation.x, 117 | interpolation.y, 118 | THREE.MathUtils.lerp 119 | ); 120 | light.setPointLight(screenLight); 121 | } 122 | const delta = clock.getDelta(); 123 | if (mixer) { 124 | mixer.update(delta); 125 | } 126 | renderer.render(scene, camera); 127 | }; 128 | animate(); 129 | return () => { 130 | clearTimeout(debounce); 131 | scene.clear(); 132 | renderer.dispose(); 133 | window.removeEventListener("resize", () => 134 | handleResize(renderer, camera, canvasDiv, character!) 135 | ); 136 | if (canvasDiv.current) { 137 | canvasDiv.current.removeChild(renderer.domElement); 138 | } 139 | if (landingDiv) { 140 | document.removeEventListener("mousemove", onMouseMove); 141 | landingDiv.removeEventListener("touchstart", onTouchStart); 142 | landingDiv.removeEventListener("touchend", onTouchEnd); 143 | } 144 | }; 145 | } 146 | }, []); 147 | 148 | return ( 149 | <> 150 |
151 |
152 |
153 |
154 |
155 |
156 | 157 | ); 158 | }; 159 | 160 | export default Scene; 161 | -------------------------------------------------------------------------------- /src/components/Character/exports.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankitpathak62/3D_Portfolio/a5224aa1e32f4627e5648d00472903bb220a049d/src/components/Character/exports.ts -------------------------------------------------------------------------------- /src/components/Character/index.tsx: -------------------------------------------------------------------------------- 1 | import Scene from "./Scene"; 2 | 3 | const CharacterModel = () => { 4 | return ; 5 | }; 6 | 7 | export default CharacterModel; 8 | -------------------------------------------------------------------------------- /src/components/Character/utils/animationUtils.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { GLTF } from "three-stdlib"; 3 | import { eyebrowBoneNames, typingBoneNames } from "../../../data/boneData"; 4 | 5 | const setAnimations = (gltf: GLTF) => { 6 | let character = gltf.scene; 7 | let mixer = new THREE.AnimationMixer(character); 8 | if (gltf.animations) { 9 | const introClip = gltf.animations.find( 10 | (clip) => clip.name === "introAnimation" 11 | ); 12 | const introAction = mixer.clipAction(introClip!); 13 | introAction.setLoop(THREE.LoopOnce, 1); 14 | introAction.clampWhenFinished = true; 15 | introAction.play(); 16 | const clipNames = ["key1", "key2", "key5", "key6"]; 17 | clipNames.forEach((name) => { 18 | const clip = THREE.AnimationClip.findByName(gltf.animations, name); 19 | if (clip) { 20 | const action = mixer?.clipAction(clip); 21 | action!.play(); 22 | action!.timeScale = 1.2; 23 | } else { 24 | console.error(`Animation "${name}" not found`); 25 | } 26 | }); 27 | let typingAction: THREE.AnimationAction | null = null; 28 | typingAction = createBoneAction(gltf, mixer, "typing", typingBoneNames); 29 | if (typingAction) { 30 | typingAction.enabled = true; 31 | typingAction.play(); 32 | typingAction.timeScale = 1.2; 33 | } 34 | } 35 | function startIntro() { 36 | const introClip = gltf.animations.find( 37 | (clip) => clip.name === "introAnimation" 38 | ); 39 | const introAction = mixer.clipAction(introClip!); 40 | introAction.clampWhenFinished = true; 41 | introAction.reset().play(); 42 | setTimeout(() => { 43 | const blink = gltf.animations.find((clip) => clip.name === "Blink"); 44 | mixer.clipAction(blink!).play().fadeIn(0.5); 45 | }, 2500); 46 | } 47 | function hover(gltf: GLTF, hoverDiv: HTMLDivElement) { 48 | let eyeBrowUpAction = createBoneAction( 49 | gltf, 50 | mixer, 51 | "browup", 52 | eyebrowBoneNames 53 | ); 54 | let isHovering = false; 55 | if (eyeBrowUpAction) { 56 | eyeBrowUpAction.setLoop(THREE.LoopOnce, 1); 57 | eyeBrowUpAction.clampWhenFinished = true; 58 | eyeBrowUpAction.enabled = true; 59 | } 60 | const onHoverFace = () => { 61 | if (eyeBrowUpAction && !isHovering) { 62 | isHovering = true; 63 | eyeBrowUpAction.reset(); 64 | eyeBrowUpAction.enabled = true; 65 | eyeBrowUpAction.setEffectiveWeight(4); 66 | eyeBrowUpAction.fadeIn(0.5).play(); 67 | } 68 | }; 69 | const onLeaveFace = () => { 70 | if (eyeBrowUpAction && isHovering) { 71 | isHovering = false; 72 | eyeBrowUpAction.fadeOut(0.6); 73 | } 74 | }; 75 | if (!hoverDiv) return; 76 | hoverDiv.addEventListener("mouseenter", onHoverFace); 77 | hoverDiv.addEventListener("mouseleave", onLeaveFace); 78 | return () => { 79 | hoverDiv.removeEventListener("mouseenter", onHoverFace); 80 | hoverDiv.removeEventListener("mouseleave", onLeaveFace); 81 | }; 82 | } 83 | return { mixer, startIntro, hover }; 84 | }; 85 | 86 | const createBoneAction = ( 87 | gltf: GLTF, 88 | mixer: THREE.AnimationMixer, 89 | clip: string, 90 | boneNames: string[] 91 | ): THREE.AnimationAction | null => { 92 | const AnimationClip = THREE.AnimationClip.findByName(gltf.animations, clip); 93 | if (!AnimationClip) { 94 | console.error(`Animation "${clip}" not found in GLTF file.`); 95 | return null; 96 | } 97 | 98 | const filteredClip = filterAnimationTracks(AnimationClip, boneNames); 99 | 100 | return mixer.clipAction(filteredClip); 101 | }; 102 | 103 | const filterAnimationTracks = ( 104 | clip: THREE.AnimationClip, 105 | boneNames: string[] 106 | ): THREE.AnimationClip => { 107 | const filteredTracks = clip.tracks.filter((track) => 108 | boneNames.some((boneName) => track.name.includes(boneName)) 109 | ); 110 | 111 | return new THREE.AnimationClip( 112 | clip.name + "_filtered", 113 | clip.duration, 114 | filteredTracks 115 | ); 116 | }; 117 | 118 | export default setAnimations; 119 | -------------------------------------------------------------------------------- /src/components/Character/utils/character.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { DRACOLoader, GLTF, GLTFLoader } from "three-stdlib"; 3 | import { setCharTimeline, setAllTimeline } from "../../utils/GsapScroll"; 4 | import { decryptFile } from "./decrypt"; 5 | 6 | const setCharacter = ( 7 | renderer: THREE.WebGLRenderer, 8 | scene: THREE.Scene, 9 | camera: THREE.PerspectiveCamera 10 | ) => { 11 | const loader = new GLTFLoader(); 12 | const dracoLoader = new DRACOLoader(); 13 | dracoLoader.setDecoderPath("/draco/"); 14 | loader.setDRACOLoader(dracoLoader); 15 | 16 | const loadCharacter = () => { 17 | return new Promise(async (resolve, reject) => { 18 | try { 19 | const encryptedBlob = await decryptFile( 20 | "/models/character.enc", 21 | "Character3D#@" 22 | ); 23 | const blobUrl = URL.createObjectURL(new Blob([encryptedBlob])); 24 | 25 | let character: THREE.Object3D; 26 | loader.load( 27 | blobUrl, 28 | async (gltf) => { 29 | character = gltf.scene; 30 | await renderer.compileAsync(character, camera, scene); 31 | character.traverse((child: any) => { 32 | if (child.isMesh) { 33 | const mesh = child as THREE.Mesh; 34 | child.castShadow = true; 35 | child.receiveShadow = true; 36 | mesh.frustumCulled = true; 37 | } 38 | }); 39 | resolve(gltf); 40 | setCharTimeline(character, camera); 41 | setAllTimeline(); 42 | character!.getObjectByName("footR")!.position.y = 3.36; 43 | character!.getObjectByName("footL")!.position.y = 3.36; 44 | dracoLoader.dispose(); 45 | }, 46 | undefined, 47 | (error) => { 48 | console.error("Error loading GLTF model:", error); 49 | reject(error); 50 | } 51 | ); 52 | } catch (err) { 53 | reject(err); 54 | console.error(err); 55 | } 56 | }); 57 | }; 58 | 59 | return { loadCharacter }; 60 | }; 61 | 62 | export default setCharacter; 63 | -------------------------------------------------------------------------------- /src/components/Character/utils/decrypt.ts: -------------------------------------------------------------------------------- 1 | async function generateAESKey(password: string): Promise { 2 | const passwordBuffer = new TextEncoder().encode(password); 3 | const hashedPassword = await crypto.subtle.digest("SHA-256", passwordBuffer); 4 | return crypto.subtle.importKey( 5 | "raw", 6 | hashedPassword.slice(0, 32), 7 | { name: "AES-CBC" }, 8 | false, 9 | ["encrypt", "decrypt"] 10 | ); 11 | } 12 | 13 | export const decryptFile = async ( 14 | url: string, 15 | password: string 16 | ): Promise => { 17 | const response = await fetch(url); 18 | const encryptedData = await response.arrayBuffer(); 19 | const iv = new Uint8Array(encryptedData.slice(0, 16)); 20 | const data = encryptedData.slice(16); 21 | const key = await generateAESKey(password); 22 | return crypto.subtle.decrypt({ name: "AES-CBC", iv }, key, data); 23 | }; 24 | -------------------------------------------------------------------------------- /src/components/Character/utils/lighting.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { RGBELoader } from "three-stdlib"; 3 | import { gsap } from "gsap"; 4 | 5 | const setLighting = (scene: THREE.Scene) => { 6 | const directionalLight = new THREE.DirectionalLight(0xc7a9ff, 0); 7 | directionalLight.intensity = 0; 8 | directionalLight.position.set(-0.47, -0.32, -1); 9 | directionalLight.castShadow = true; 10 | directionalLight.shadow.mapSize.width = 1024; 11 | directionalLight.shadow.mapSize.height = 1024; 12 | directionalLight.shadow.camera.near = 0.5; 13 | directionalLight.shadow.camera.far = 50; 14 | scene.add(directionalLight); 15 | 16 | const pointLight = new THREE.PointLight(0xc2a4ff, 0, 100, 3); 17 | pointLight.position.set(3, 12, 4); 18 | pointLight.castShadow = true; 19 | scene.add(pointLight); 20 | 21 | new RGBELoader() 22 | .setPath("/models/") 23 | .load("char_enviorment.hdr", function (texture) { 24 | texture.mapping = THREE.EquirectangularReflectionMapping; 25 | scene.environment = texture; 26 | scene.environmentIntensity = 0; 27 | scene.environmentRotation.set(5.76, 85.85, 1); 28 | }); 29 | 30 | function setPointLight(screenLight: any) { 31 | if (screenLight.material.opacity > 0.9) { 32 | pointLight.intensity = screenLight.material.emissiveIntensity * 20; 33 | } else { 34 | pointLight.intensity = 0; 35 | } 36 | } 37 | const duration = 2; 38 | const ease = "power2.inOut"; 39 | function turnOnLights() { 40 | gsap.to(scene, { 41 | environmentIntensity: 0.64, 42 | duration: duration, 43 | ease: ease, 44 | }); 45 | gsap.to(directionalLight, { 46 | intensity: 1, 47 | duration: duration, 48 | ease: ease, 49 | }); 50 | gsap.to(".character-rim", { 51 | y: "55%", 52 | opacity: 1, 53 | delay: 0.2, 54 | duration: 2, 55 | }); 56 | } 57 | 58 | return { setPointLight, turnOnLights }; 59 | }; 60 | 61 | export default setLighting; 62 | -------------------------------------------------------------------------------- /src/components/Character/utils/mouseUtils.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | 3 | export const handleMouseMove = ( 4 | event: MouseEvent, 5 | setMousePosition: (x: number, y: number) => void 6 | ) => { 7 | const mouseX = (event.clientX / window.innerWidth) * 2 - 1; 8 | const mouseY = -(event.clientY / window.innerHeight) * 2 + 1; 9 | setMousePosition(mouseX, mouseY); 10 | }; 11 | 12 | export const handleTouchMove = ( 13 | event: TouchEvent, 14 | setMousePosition: (x: number, y: number) => void 15 | ) => { 16 | const mouseX = (event.touches[0].clientX / window.innerWidth) * 2 - 1; 17 | const mouseY = -(event.touches[0].clientY / window.innerHeight) * 2 + 1; 18 | setMousePosition(mouseX, mouseY); 19 | }; 20 | 21 | export const handleTouchEnd = ( 22 | setMousePosition: ( 23 | x: number, 24 | y: number, 25 | interpolationX: number, 26 | interpolationY: number 27 | ) => void 28 | ) => { 29 | setTimeout(() => { 30 | setMousePosition(0, 0, 0.03, 0.03); 31 | setTimeout(() => { 32 | setMousePosition(0, 0, 0.1, 0.2); 33 | }, 1000); 34 | }, 2000); 35 | }; 36 | 37 | export const handleHeadRotation = ( 38 | headBone: THREE.Object3D, 39 | mouseX: number, 40 | mouseY: number, 41 | interpolationX: number, 42 | interpolationY: number, 43 | lerp: (x: number, y: number, t: number) => number 44 | ) => { 45 | if (!headBone) return; 46 | if (window.scrollY < 200) { 47 | const maxRotation = Math.PI / 6; 48 | headBone.rotation.y = lerp( 49 | headBone.rotation.y, 50 | mouseX * maxRotation, 51 | interpolationY 52 | ); 53 | let minRotationX = -0.3; 54 | let maxRotationX = 0.4; 55 | if (mouseY > minRotationX) { 56 | if (mouseY < maxRotationX) { 57 | headBone.rotation.x = lerp( 58 | headBone.rotation.x, 59 | -mouseY - 0.5 * maxRotation, 60 | interpolationX 61 | ); 62 | } else { 63 | headBone.rotation.x = lerp( 64 | headBone.rotation.x, 65 | -maxRotation - 0.5 * maxRotation, 66 | interpolationX 67 | ); 68 | } 69 | } else { 70 | headBone.rotation.x = lerp( 71 | headBone.rotation.x, 72 | -minRotationX - 0.5 * maxRotation, 73 | interpolationX 74 | ); 75 | } 76 | } else { 77 | if (window.innerWidth > 1024) { 78 | headBone.rotation.x = lerp(headBone.rotation.x, -0.4, 0.03); 79 | headBone.rotation.y = lerp(headBone.rotation.y, -0.3, 0.03); 80 | } 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /src/components/Character/utils/resizeUtils.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { ScrollTrigger } from "gsap/ScrollTrigger"; 3 | import { setCharTimeline, setAllTimeline } from "../../utils/GsapScroll"; 4 | 5 | export default function handleResize( 6 | renderer: THREE.WebGLRenderer, 7 | camera: THREE.PerspectiveCamera, 8 | canvasDiv: React.RefObject, 9 | character: THREE.Object3D 10 | ) { 11 | if (!canvasDiv.current) return; 12 | let canvas3d = canvasDiv.current.getBoundingClientRect(); 13 | const width = canvas3d.width; 14 | const height = canvas3d.height; 15 | renderer.setSize(width, height); 16 | camera.aspect = width / height; 17 | camera.updateProjectionMatrix(); 18 | const workTrigger = ScrollTrigger.getById("work"); 19 | ScrollTrigger.getAll().forEach((trigger) => { 20 | if (trigger != workTrigger) { 21 | trigger.kill(); 22 | } 23 | }); 24 | setCharTimeline(character, camera); 25 | setAllTimeline(); 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Contact.tsx: -------------------------------------------------------------------------------- 1 | import { MdArrowOutward, MdCopyright } from "react-icons/md"; 2 | import "./styles/Contact.css"; 3 | 4 | const Contact = () => { 5 | return ( 6 |
7 |
8 |

Contact

9 |
10 |
11 |

Email

12 |

13 | 14 | abc@gmail.com 15 | 16 |

17 |

Phone

18 |

19 | 20 | +91 9988776655 21 | 22 |

23 |
24 | 59 |
60 |

61 | Designed and Developed
by Kabir Singh 62 |

63 |
64 | 2025 65 |
66 |
67 |
68 |
69 |
70 | ); 71 | }; 72 | 73 | export default Contact; 74 | -------------------------------------------------------------------------------- /src/components/Cursor.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | import "./styles/Cursor.css"; 3 | import gsap from "gsap"; 4 | 5 | const Cursor = () => { 6 | const cursorRef = useRef(null); 7 | useEffect(() => { 8 | let hover = false; 9 | const cursor = cursorRef.current!; 10 | const mousePos = { x: 0, y: 0 }; 11 | const cursorPos = { x: 0, y: 0 }; 12 | document.addEventListener("mousemove", (e) => { 13 | mousePos.x = e.clientX; 14 | mousePos.y = e.clientY; 15 | }); 16 | requestAnimationFrame(function loop() { 17 | if (!hover) { 18 | const delay = 6; 19 | cursorPos.x += (mousePos.x - cursorPos.x) / delay; 20 | cursorPos.y += (mousePos.y - cursorPos.y) / delay; 21 | gsap.to(cursor, { x: cursorPos.x, y: cursorPos.y, duration: 0.1 }); 22 | // cursor.style.transform = `translate(${cursorPos.x}px, ${cursorPos.y}px)`; 23 | } 24 | requestAnimationFrame(loop); 25 | }); 26 | document.querySelectorAll("[data-cursor]").forEach((item) => { 27 | const element = item as HTMLElement; 28 | element.addEventListener("mouseover", (e: MouseEvent) => { 29 | const target = e.currentTarget as HTMLElement; 30 | const rect = target.getBoundingClientRect(); 31 | 32 | if (element.dataset.cursor === "icons") { 33 | cursor.classList.add("cursor-icons"); 34 | 35 | gsap.to(cursor, { x: rect.left, y: rect.top, duration: 0.1 }); 36 | // cursor.style.transform = `translate(${rect.left}px,${rect.top}px)`; 37 | cursor.style.setProperty("--cursorH", `${rect.height}px`); 38 | hover = true; 39 | } 40 | if (element.dataset.cursor === "disable") { 41 | cursor.classList.add("cursor-disable"); 42 | } 43 | }); 44 | element.addEventListener("mouseout", () => { 45 | cursor.classList.remove("cursor-disable", "cursor-icons"); 46 | hover = false; 47 | }); 48 | }); 49 | }, []); 50 | 51 | return
; 52 | }; 53 | 54 | export default Cursor; 55 | -------------------------------------------------------------------------------- /src/components/HoverLinks.tsx: -------------------------------------------------------------------------------- 1 | import "./styles/style.css"; 2 | 3 | const HoverLinks = ({ text, cursor }: { text: string; cursor?: boolean }) => { 4 | return ( 5 |
6 |
7 | {text}
{text}
8 |
9 |
10 | ); 11 | }; 12 | 13 | export default HoverLinks; 14 | -------------------------------------------------------------------------------- /src/components/Landing.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from "react"; 2 | import "./styles/Landing.css"; 3 | 4 | const Landing = ({ children }: PropsWithChildren) => { 5 | return ( 6 | <> 7 |
8 |
9 |
10 |

Hello! I'm

11 |

12 | KABIR 13 |
14 | SINGH 15 |

16 |
17 |
18 |

A Creative

19 |

20 |
Designer
21 |
Developer
22 |

23 |

24 |
Developer
25 |
Designer
26 |

27 |
28 |
29 | {children} 30 |
31 | 32 | ); 33 | }; 34 | 35 | export default Landing; 36 | -------------------------------------------------------------------------------- /src/components/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import "./styles/Loading.css"; 3 | import { useLoading } from "../context/LoadingProvider"; 4 | 5 | import Marquee from "react-fast-marquee"; 6 | 7 | const Loading = ({ percent }: { percent: number }) => { 8 | const { setIsLoading } = useLoading(); 9 | const [loaded, setLoaded] = useState(false); 10 | const [isLoaded, setIsLoaded] = useState(false); 11 | const [clicked, setClicked] = useState(false); 12 | 13 | if (percent >= 100) { 14 | setTimeout(() => { 15 | setLoaded(true); 16 | setTimeout(() => { 17 | setIsLoaded(true); 18 | }, 1000); 19 | }, 600); 20 | } 21 | 22 | useEffect(() => { 23 | import("./utils/initialFX").then((module) => { 24 | if (isLoaded) { 25 | setClicked(true); 26 | setTimeout(() => { 27 | if (module.initialFX) { 28 | module.initialFX(); 29 | } 30 | setIsLoading(false); 31 | }, 900); 32 | } 33 | }); 34 | }, [isLoaded]); 35 | 36 | function handleMouseMove(e: React.MouseEvent) { 37 | const { currentTarget: target } = e; 38 | const rect = target.getBoundingClientRect(); 39 | const x = e.clientX - rect.left; 40 | const y = e.clientY - rect.top; 41 | target.style.setProperty("--mouse-x", `${x}px`); 42 | target.style.setProperty("--mouse-y", `${y}px`); 43 | } 44 | 45 | return ( 46 | <> 47 |
48 | 49 | Logo 50 | 51 |
52 |
53 |
54 | {[...Array(27)].map((_, index) => ( 55 |
56 | ))} 57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | 65 | A Creative Developer A Creative Designer 66 | A Creative Developer A Creative Designer 67 | 68 |
69 |
handleMouseMove(e)} 72 | > 73 |
74 |
75 |
76 |
77 |
78 | Loading {percent}% 79 |
80 |
81 |
82 |
83 |
84 | Welcome 85 |
86 |
87 |
88 |
89 | 90 | ); 91 | }; 92 | 93 | export default Loading; 94 | 95 | export const setProgress = (setLoading: (value: number) => void) => { 96 | let percent: number = 0; 97 | 98 | let interval = setInterval(() => { 99 | if (percent <= 50) { 100 | let rand = Math.round(Math.random() * 5); 101 | percent = percent + rand; 102 | setLoading(percent); 103 | } else { 104 | clearInterval(interval); 105 | interval = setInterval(() => { 106 | percent = percent + Math.round(Math.random()); 107 | setLoading(percent); 108 | if (percent > 91) { 109 | clearInterval(interval); 110 | } 111 | }, 2000); 112 | } 113 | }, 100); 114 | 115 | function clear() { 116 | clearInterval(interval); 117 | setLoading(100); 118 | } 119 | 120 | function loaded() { 121 | return new Promise((resolve) => { 122 | clearInterval(interval); 123 | interval = setInterval(() => { 124 | if (percent < 100) { 125 | percent++; 126 | setLoading(percent); 127 | } else { 128 | resolve(percent); 129 | clearInterval(interval); 130 | } 131 | }, 2); 132 | }); 133 | } 134 | return { loaded, percent, clear }; 135 | }; 136 | -------------------------------------------------------------------------------- /src/components/MainContainer.tsx: -------------------------------------------------------------------------------- 1 | import { lazy, PropsWithChildren, Suspense, useEffect, useState } from "react"; 2 | import About from "./About"; 3 | import Career from "./Career"; 4 | import Contact from "./Contact"; 5 | import Cursor from "./Cursor"; 6 | import Landing from "./Landing"; 7 | import Navbar from "./Navbar"; 8 | import SocialIcons from "./SocialIcons"; 9 | import WhatIDo from "./WhatIDo"; 10 | import Work from "./Work"; 11 | import setSplitText from "./utils/splitText"; 12 | 13 | const TechStack = lazy(() => import("./TechStack")); 14 | 15 | const MainContainer = ({ children }: PropsWithChildren) => { 16 | const [isDesktopView, setIsDesktopView] = useState( 17 | window.innerWidth > 1024 18 | ); 19 | 20 | useEffect(() => { 21 | const resizeHandler = () => { 22 | setSplitText(); 23 | setIsDesktopView(window.innerWidth > 1024); 24 | }; 25 | resizeHandler(); 26 | window.addEventListener("resize", resizeHandler); 27 | return () => { 28 | window.removeEventListener("resize", resizeHandler); 29 | }; 30 | }, [isDesktopView]); 31 | 32 | return ( 33 |
34 | 35 | 36 | 37 | {isDesktopView && children} 38 |
39 |
40 |
41 | {!isDesktopView && children} 42 | 43 | 44 | 45 | 46 | {isDesktopView && ( 47 | Loading....
}> 48 | 49 | 50 | )} 51 | 52 |
53 |
54 |
55 | 56 | ); 57 | }; 58 | 59 | export default MainContainer; 60 | -------------------------------------------------------------------------------- /src/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { ScrollTrigger } from "gsap/ScrollTrigger"; 3 | import HoverLinks from "./HoverLinks"; 4 | import { gsap } from "gsap"; 5 | import { ScrollSmoother } from "gsap-trial/ScrollSmoother"; 6 | import "./styles/Navbar.css"; 7 | 8 | gsap.registerPlugin(ScrollSmoother, ScrollTrigger); 9 | export let smoother: ScrollSmoother; 10 | 11 | const Navbar = () => { 12 | useEffect(() => { 13 | smoother = ScrollSmoother.create({ 14 | wrapper: "#smooth-wrapper", 15 | content: "#smooth-content", 16 | smooth: 1.7, 17 | speed: 1.7, 18 | effects: true, 19 | autoResize: true, 20 | ignoreMobileResize: true, 21 | }); 22 | 23 | smoother.scrollTop(0); 24 | smoother.paused(true); 25 | 26 | let links = document.querySelectorAll(".header ul a"); 27 | links.forEach((elem) => { 28 | let element = elem as HTMLAnchorElement; 29 | element.addEventListener("click", (e) => { 30 | if (window.innerWidth > 1024) { 31 | e.preventDefault(); 32 | let elem = e.currentTarget as HTMLAnchorElement; 33 | let section = elem.getAttribute("data-href"); 34 | smoother.scrollTo(section, true, "top top"); 35 | } 36 | }); 37 | }); 38 | window.addEventListener("resize", () => { 39 | ScrollSmoother.refresh(true); 40 | }); 41 | }, []); 42 | return ( 43 | <> 44 |
45 | 46 | Logo 47 | 48 | 53 | example@mail.com 54 | 55 | 72 |
73 | 74 |
75 |
76 |
77 | 78 | ); 79 | }; 80 | 81 | export default Navbar; 82 | -------------------------------------------------------------------------------- /src/components/SocialIcons.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | FaGithub, 3 | FaInstagram, 4 | FaLinkedinIn, 5 | FaXTwitter, 6 | } from "react-icons/fa6"; 7 | import "./styles/SocialIcons.css"; 8 | import { TbNotes } from "react-icons/tb"; 9 | import { useEffect } from "react"; 10 | import HoverLinks from "./HoverLinks"; 11 | 12 | const SocialIcons = () => { 13 | useEffect(() => { 14 | const social = document.getElementById("social") as HTMLElement; 15 | 16 | social.querySelectorAll("span").forEach((item) => { 17 | const elem = item as HTMLElement; 18 | const link = elem.querySelector("a") as HTMLElement; 19 | 20 | const rect = elem.getBoundingClientRect(); 21 | let mouseX = rect.width / 2; 22 | let mouseY = rect.height / 2; 23 | let currentX = 0; 24 | let currentY = 0; 25 | 26 | const updatePosition = () => { 27 | currentX += (mouseX - currentX) * 0.1; 28 | currentY += (mouseY - currentY) * 0.1; 29 | 30 | link.style.setProperty("--siLeft", `${currentX}px`); 31 | link.style.setProperty("--siTop", `${currentY}px`); 32 | 33 | requestAnimationFrame(updatePosition); 34 | }; 35 | 36 | const onMouseMove = (e: MouseEvent) => { 37 | const x = e.clientX - rect.left; 38 | const y = e.clientY - rect.top; 39 | 40 | if (x < 40 && x > 10 && y < 40 && y > 5) { 41 | mouseX = x; 42 | mouseY = y; 43 | } else { 44 | mouseX = rect.width / 2; 45 | mouseY = rect.height / 2; 46 | } 47 | }; 48 | 49 | document.addEventListener("mousemove", onMouseMove); 50 | 51 | updatePosition(); 52 | 53 | return () => { 54 | elem.removeEventListener("mousemove", onMouseMove); 55 | }; 56 | }); 57 | }, []); 58 | 59 | return ( 60 |
61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
83 | 84 | 85 | 86 | 87 | 88 | 89 |
90 | ); 91 | }; 92 | 93 | export default SocialIcons; 94 | -------------------------------------------------------------------------------- /src/components/TechStack.tsx: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { useRef, useMemo, useState, useEffect } from "react"; 3 | import { Canvas, useFrame } from "@react-three/fiber"; 4 | import { Environment } from "@react-three/drei"; 5 | import { EffectComposer, N8AO } from "@react-three/postprocessing"; 6 | import { 7 | BallCollider, 8 | Physics, 9 | RigidBody, 10 | CylinderCollider, 11 | RapierRigidBody, 12 | } from "@react-three/rapier"; 13 | 14 | const textureLoader = new THREE.TextureLoader(); 15 | const imageUrls = [ 16 | "/images/react2.webp", 17 | "/images/next2.webp", 18 | "/images/node2.webp", 19 | "/images/express.webp", 20 | "/images/mongo.webp", 21 | "/images/mysql.webp", 22 | "/images/typescript.webp", 23 | "/images/javascript.webp", 24 | ]; 25 | const textures = imageUrls.map((url) => textureLoader.load(url)); 26 | 27 | const sphereGeometry = new THREE.SphereGeometry(1, 28, 28); 28 | 29 | const spheres = [...Array(30)].map(() => ({ 30 | scale: [0.7, 1, 0.8, 1, 1][Math.floor(Math.random() * 5)], 31 | })); 32 | 33 | type SphereProps = { 34 | vec?: THREE.Vector3; 35 | scale: number; 36 | r?: typeof THREE.MathUtils.randFloatSpread; 37 | material: THREE.MeshPhysicalMaterial; 38 | isActive: boolean; 39 | }; 40 | 41 | function SphereGeo({ 42 | vec = new THREE.Vector3(), 43 | scale, 44 | r = THREE.MathUtils.randFloatSpread, 45 | material, 46 | isActive, 47 | }: SphereProps) { 48 | const api = useRef(null); 49 | 50 | useFrame((_state, delta) => { 51 | if (!isActive) return; 52 | delta = Math.min(0.1, delta); 53 | const impulse = vec 54 | .copy(api.current!.translation()) 55 | .normalize() 56 | .multiply( 57 | new THREE.Vector3( 58 | -50 * delta * scale, 59 | -150 * delta * scale, 60 | -50 * delta * scale 61 | ) 62 | ); 63 | 64 | api.current?.applyImpulse(impulse, true); 65 | }); 66 | 67 | return ( 68 | 76 | 77 | 82 | 90 | 91 | ); 92 | } 93 | 94 | type PointerProps = { 95 | vec?: THREE.Vector3; 96 | isActive: boolean; 97 | }; 98 | 99 | function Pointer({ vec = new THREE.Vector3(), isActive }: PointerProps) { 100 | const ref = useRef(null); 101 | 102 | useFrame(({ pointer, viewport }) => { 103 | if (!isActive) return; 104 | const targetVec = vec.lerp( 105 | new THREE.Vector3( 106 | (pointer.x * viewport.width) / 2, 107 | (pointer.y * viewport.height) / 2, 108 | 0 109 | ), 110 | 0.2 111 | ); 112 | ref.current?.setNextKinematicTranslation(targetVec); 113 | }); 114 | 115 | return ( 116 | 122 | 123 | 124 | ); 125 | } 126 | 127 | const TechStack = () => { 128 | const [isActive, setIsActive] = useState(false); 129 | 130 | useEffect(() => { 131 | const handleScroll = () => { 132 | const scrollY = window.scrollY || document.documentElement.scrollTop; 133 | const threshold = document 134 | .getElementById("work")! 135 | .getBoundingClientRect().top; 136 | setIsActive(scrollY > threshold); 137 | }; 138 | document.querySelectorAll(".header a").forEach((elem) => { 139 | const element = elem as HTMLAnchorElement; 140 | element.addEventListener("click", () => { 141 | const interval = setInterval(() => { 142 | handleScroll(); 143 | }, 10); 144 | setTimeout(() => { 145 | clearInterval(interval); 146 | }, 1000); 147 | }); 148 | }); 149 | window.addEventListener("scroll", handleScroll); 150 | return () => { 151 | window.removeEventListener("scroll", handleScroll); 152 | }; 153 | }, []); 154 | const materials = useMemo(() => { 155 | return textures.map( 156 | (texture) => 157 | new THREE.MeshPhysicalMaterial({ 158 | map: texture, 159 | emissive: "#ffffff", 160 | emissiveMap: texture, 161 | emissiveIntensity: 0.3, 162 | metalness: 0.5, 163 | roughness: 1, 164 | clearcoat: 0.1, 165 | }) 166 | ); 167 | }, []); 168 | 169 | return ( 170 |
171 |

My Techstack

172 | 173 | (state.gl.toneMappingExposure = 1.5)} 178 | className="tech-canvas" 179 | > 180 | 181 | 189 | 190 | 191 | 192 | {spheres.map((props, i) => ( 193 | 199 | ))} 200 | 201 | 206 | 207 | 208 | 209 | 210 |
211 | ); 212 | }; 213 | 214 | export default TechStack; 215 | -------------------------------------------------------------------------------- /src/components/WhatIDo.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | import "./styles/WhatIDo.css"; 3 | import { ScrollTrigger } from "gsap/ScrollTrigger"; 4 | 5 | const WhatIDo = () => { 6 | const containerRef = useRef<(HTMLDivElement | null)[]>([]); 7 | const setRef = (el: HTMLDivElement | null, index: number) => { 8 | containerRef.current[index] = el; 9 | }; 10 | useEffect(() => { 11 | if (ScrollTrigger.isTouch) { 12 | containerRef.current.forEach((container) => { 13 | if (container) { 14 | container.classList.remove("what-noTouch"); 15 | container.addEventListener("click", () => handleClick(container)); 16 | } 17 | }); 18 | } 19 | return () => { 20 | containerRef.current.forEach((container) => { 21 | if (container) { 22 | container.removeEventListener("click", () => handleClick(container)); 23 | } 24 | }); 25 | }; 26 | }, []); 27 | return ( 28 |
29 |
30 |

31 | WHAT 32 |
33 | I DO 34 |
35 |

36 |
37 |
38 |
39 |
40 | 41 | 50 | 59 | 60 |
61 |
setRef(el, 0)} 64 | > 65 |
66 | 67 | 76 | 85 | 86 |
87 |
88 | 89 |
90 |

DEVELOP

91 |

Description

92 |

93 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Quas 94 | quia aliquid laboriosam ducimus sit molestiae. 95 |

96 |
Skillset & tools
97 |
98 |
JavaScript
99 |
TypeScript
100 |
Three.js
101 |
React
102 |
Css
103 |
Node.js
104 |
Next.js
105 |
Express.js
106 |
PHP
107 |
MySql
108 |
109 |
110 |
111 |
112 |
setRef(el, 1)} 115 | > 116 |
117 | 118 | 127 | 128 |
129 |
130 |
131 |

DESIGN

132 |

Description

133 |

134 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Quas 135 | quia aliquid laboriosam ducimus sit molestiae 136 |

137 |
Skillset & tools
138 |
139 |
Blender
140 |
Zbrush
141 |
UI Design
142 |
Motion
143 |
Rigging
144 |
3D Animation
145 |
Character Design
146 |
Modelling
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | ); 155 | }; 156 | 157 | export default WhatIDo; 158 | 159 | function handleClick(container: HTMLDivElement) { 160 | container.classList.toggle("what-content-active"); 161 | container.classList.remove("what-sibling"); 162 | if (container.parentElement) { 163 | const siblings = Array.from(container.parentElement.children); 164 | 165 | siblings.forEach((sibling) => { 166 | if (sibling !== container) { 167 | sibling.classList.remove("what-content-active"); 168 | sibling.classList.toggle("what-sibling"); 169 | } 170 | }); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/components/Work.tsx: -------------------------------------------------------------------------------- 1 | import "./styles/Work.css"; 2 | import WorkImage from "./WorkImage"; 3 | import gsap from "gsap"; 4 | import { ScrollTrigger } from "gsap/ScrollTrigger"; 5 | import { useGSAP } from "@gsap/react"; 6 | 7 | gsap.registerPlugin(useGSAP); 8 | 9 | const Work = () => { 10 | useGSAP(() => { 11 | let translateX: number = 0; 12 | function setTranslateX() { 13 | const box = document.getElementsByClassName("work-box"); 14 | const rectLeft = document 15 | .querySelector(".work-container")! 16 | .getBoundingClientRect().left; 17 | const rect = box[0].getBoundingClientRect(); 18 | const parentWidth = box[0].parentElement!.getBoundingClientRect().width; 19 | let padding: number = 20 | parseInt(window.getComputedStyle(box[0]).padding) / 2; 21 | translateX = rect.width * box.length - (rectLeft + parentWidth) + padding; 22 | } 23 | 24 | setTranslateX(); 25 | 26 | let timeline = gsap.timeline({ 27 | scrollTrigger: { 28 | trigger: ".work-section", 29 | start: "top top", 30 | end: "bottom top", 31 | scrub: true, 32 | pin: true, 33 | pinType: !ScrollTrigger.isTouch ? "transform" : "fixed", 34 | id: "work", 35 | }, 36 | }); 37 | 38 | timeline.to(".work-flex", { 39 | x: -translateX, 40 | duration: 40, 41 | delay: 0.2, 42 | }); 43 | }, []); 44 | return ( 45 |
46 |
47 |

48 | My Work 49 |

50 |
51 | {[...Array(6)].map((_value, index) => ( 52 |
53 |
54 |
55 |

0{index + 1}

56 | 57 |
58 |

Project Name

59 |

Category

60 |
61 |
62 |

Tools and features

63 |

Javascript, TypeScript, React, Threejs

64 |
65 | 66 |
67 | ))} 68 |
69 |
70 |
71 | ); 72 | }; 73 | 74 | export default Work; 75 | -------------------------------------------------------------------------------- /src/components/WorkImage.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { MdArrowOutward } from "react-icons/md"; 3 | 4 | interface Props { 5 | image: string; 6 | alt?: string; 7 | video?: string; 8 | link?: string; 9 | } 10 | 11 | const WorkImage = (props: Props) => { 12 | const [isVideo, setIsVideo] = useState(false); 13 | const [video, setVideo] = useState(""); 14 | const handleMouseEnter = async () => { 15 | if (props.video) { 16 | setIsVideo(true); 17 | const response = await fetch(`src/assets/${props.video}`); 18 | const blob = await response.blob(); 19 | const blobUrl = URL.createObjectURL(blob); 20 | setVideo(blobUrl); 21 | } 22 | }; 23 | 24 | return ( 25 | 43 | ); 44 | }; 45 | 46 | export default WorkImage; 47 | -------------------------------------------------------------------------------- /src/components/styles/About.css: -------------------------------------------------------------------------------- 1 | .about-section { 2 | display: flex; 3 | align-items: center; 4 | justify-content: left; 5 | place-items: center; 6 | position: relative; 7 | opacity: 1; 8 | height: auto; 9 | width: var(--cWidth); 10 | margin: auto; 11 | } 12 | .about-me { 13 | padding: 50px 0px; 14 | padding-bottom: 0; 15 | width: 500px; 16 | max-width: calc(100% - 15px); 17 | } 18 | .about-me h3 { 19 | font-size: 25px; 20 | text-transform: uppercase; 21 | letter-spacing: 7px; 22 | font-weight: 400; 23 | color: var(--accentColor); 24 | } 25 | 26 | .about-me p { 27 | font-size: 33px; 28 | font-weight: 600; 29 | line-height: 36px; 30 | letter-spacing: 1px; 31 | } 32 | 33 | @media only screen and (min-width: 600px) { 34 | .about-section { 35 | justify-content: center; 36 | } 37 | } 38 | @media only screen and (min-width: 768px) { 39 | .about-me { 40 | width: 500px; 41 | max-width: calc(100% - 70px); 42 | transform: translateY(0%); 43 | } 44 | .about-section { 45 | opacity: 1; 46 | } 47 | } 48 | @media only screen and (min-width: 1025px) { 49 | .about-section { 50 | width: var(--cWidth); 51 | justify-content: right; 52 | max-width: 1920px; 53 | height: var(--vh); 54 | padding: 0px; 55 | opacity: 1; 56 | } 57 | .about-me { 58 | padding: 0px; 59 | width: 50%; 60 | } 61 | .about-me p { 62 | font-size: 1.9vw; 63 | line-height: 2.3vw; 64 | } 65 | } 66 | @media only screen and (min-width: 1950px) { 67 | .about-me p { 68 | font-size: 2.5rem; 69 | line-height: 2.7rem; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/components/styles/Career.css: -------------------------------------------------------------------------------- 1 | .career-section { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | place-items: center; 6 | justify-content: center; 7 | position: relative; 8 | opacity: 1; 9 | height: auto; 10 | margin: auto; 11 | margin-bottom: 250px; 12 | padding: 120px 0px; 13 | } 14 | 15 | .career-section h2 { 16 | font-size: 70px; 17 | line-height: 70px; 18 | font-weight: 400; 19 | text-align: center; 20 | background: linear-gradient(0deg, #7f40ff, #ffffff); 21 | background-clip: text; 22 | -webkit-background-clip: text; 23 | -webkit-text-fill-color: transparent; 24 | color: transparent; 25 | margin-top: 50px; 26 | margin-bottom: 90px; 27 | } 28 | .career-section h2 > span { 29 | font-family: "Geist", sans-serif; 30 | font-weight: 300; 31 | } 32 | .career-info { 33 | position: relative; 34 | display: flex; 35 | flex-direction: column; 36 | margin: 0px auto; 37 | } 38 | .career-info-box { 39 | display: flex; 40 | justify-content: space-between; 41 | margin-bottom: 50px; 42 | } 43 | .career-info-box p { 44 | width: 40%; 45 | font-size: 18px; 46 | font-weight: 300; 47 | margin: 0; 48 | } 49 | .career-info-in { 50 | display: flex; 51 | width: 40%; 52 | justify-content: space-between; 53 | gap: 50px; 54 | } 55 | .career-info h3 { 56 | font-size: 48px; 57 | margin: 0; 58 | font-weight: 500; 59 | line-height: 45px; 60 | } 61 | 62 | .career-info h4 { 63 | font-size: 33px; 64 | line-height: 30px; 65 | letter-spacing: 0.8px; 66 | font-weight: 500; 67 | margin: 0; 68 | } 69 | .career-info h5 { 70 | font-weight: 400; 71 | letter-spacing: 0.7px; 72 | font-size: 20px; 73 | text-transform: capitalize; 74 | margin: 10px 0px; 75 | color: var(--accentColor); 76 | } 77 | .career-timeline { 78 | position: absolute; 79 | top: -50px; 80 | left: 50%; 81 | transform: translateX(-50%); 82 | width: 3px; 83 | height: 100%; 84 | background-image: linear-gradient( 85 | to top, 86 | #aa42ff 20%, 87 | var(--accentColor) 50%, 88 | transparent 95% 89 | ); 90 | max-height: 0%; 91 | } 92 | .career-dot { 93 | position: absolute; 94 | bottom: 0; 95 | left: 50%; 96 | transform: translate(-50%, 50%); 97 | background-color: #aa42ff; 98 | width: 10px; 99 | height: 10px; 100 | border-radius: 50px; 101 | box-shadow: 0px 0px 5px 2px #d29bff, 0px 0px 15px 8px #d097ff, 102 | 0px 0px 110px 20px #f2c0ff; 103 | animation: timeline 0.8s linear infinite forwards; 104 | } 105 | 106 | @keyframes timeline { 107 | 10%, 108 | 20%, 109 | 50%, 110 | 70%, 111 | 90% { 112 | box-shadow: 0px 0px 5px 2px #d29bff; 113 | } 114 | 10%, 115 | 30%, 116 | 0%, 117 | 100%, 118 | 64%, 119 | 80% { 120 | box-shadow: 0px 0px 5px 2px #d29bff, 0px 0px 15px 5px #d097ff, 121 | 0px 0px 110px 20px #f2c0ff; 122 | } 123 | } 124 | @keyframes timeline2 { 125 | 0% { 126 | box-shadow: 0px 0px 5px 2px #d29bff; 127 | } 128 | 100% { 129 | box-shadow: 0px 0px 5px 2px #d29bff, 0px 0px 15px 5px #d097ff, 130 | 0px 0px 110px 20px #f2c0ff; 131 | } 132 | } 133 | @media only screen and (max-width: 1400px) { 134 | .career-section h2 { 135 | font-size: 50px; 136 | line-height: 50px; 137 | } 138 | .career-info h4 { 139 | font-size: 22px; 140 | line-height: 24px; 141 | width: 180px; 142 | } 143 | .career-info h5 { 144 | font-size: 17px; 145 | } 146 | .career-info h3 { 147 | font-size: 40px; 148 | } 149 | .career-info-box p { 150 | font-size: 14px; 151 | } 152 | .career-info-in { 153 | width: 45%; 154 | gap: 20px; 155 | } 156 | 157 | .career-info-box p { 158 | width: 45%; 159 | } 160 | } 161 | @media only screen and (max-width: 1025px) { 162 | .career-section { 163 | padding: 70px 0px; 164 | padding-top: 220px; 165 | margin-top: -200px; 166 | margin-bottom: 0; 167 | } 168 | } 169 | @media only screen and (max-width: 900px) { 170 | .career-info-box { 171 | flex-direction: column; 172 | gap: 10px; 173 | margin-bottom: 70px; 174 | } 175 | .career-info-in, 176 | .career-info-box p { 177 | width: 100%; 178 | padding-left: 10%; 179 | box-sizing: border-box; 180 | } 181 | .career-timeline { 182 | left: 0%; 183 | } 184 | .career-container { 185 | width: calc(100% - 25px); 186 | } 187 | } 188 | @media only screen and (max-width: 600px) { 189 | .career-info { 190 | margin: 0; 191 | } 192 | .career-section h2 { 193 | width: 100%; 194 | font-size: 45px; 195 | line-height: 45px; 196 | margin-top: 0px; 197 | } 198 | .career-info-in { 199 | gap: 0px; 200 | } 201 | .career-info h3 { 202 | font-size: 33px; 203 | } 204 | .career-info-in, 205 | .career-info-box p { 206 | padding-left: 5%; 207 | } 208 | .career-section { 209 | padding-top: 90px; 210 | margin-top: -70px; 211 | 212 | align-items: start; 213 | place-items: inherit; 214 | justify-content: left; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/components/styles/Contact.css: -------------------------------------------------------------------------------- 1 | .contact-section { 2 | margin: auto; 3 | padding-bottom: 100px; 4 | margin-top: 100px; 5 | } 6 | .contact-section h3 { 7 | font-size: 60px; 8 | font-weight: 400; 9 | text-transform: uppercase; 10 | margin: 0; 11 | } 12 | .contact-flex { 13 | display: flex; 14 | justify-content: space-between; 15 | } 16 | .contact-flex h4 { 17 | font-weight: 500; 18 | margin: 0; 19 | opacity: 0.6; 20 | } 21 | .contact-box { 22 | display: flex; 23 | flex-direction: column; 24 | } 25 | .contact-flex p { 26 | margin-top: 10px; 27 | margin-bottom: 20px; 28 | } 29 | a.contact-social { 30 | font-size: 25px; 31 | border-bottom: 1px solid #ccc; 32 | } 33 | .contact-box h2 { 34 | font-weight: 400; 35 | font-size: 23px; 36 | margin: 0; 37 | } 38 | .contact-box h2 > span { 39 | color: var(--accentColor); 40 | } 41 | .contact-box h5 { 42 | font-size: 20px; 43 | font-weight: 500; 44 | line-height: 20px; 45 | display: flex; 46 | gap: 10px; 47 | opacity: 0.5; 48 | } 49 | @media only screen and (max-width: 1600px) { 50 | .contact-section h3 { 51 | font-size: 50px; 52 | } 53 | .contact-box h2 { 54 | font-size: 20px; 55 | } 56 | a.contact-social { 57 | font-size: 22px; 58 | } 59 | } 60 | @media only screen and (max-width: 1300px) { 61 | .contact-section h3 { 62 | font-size: 40px; 63 | } 64 | .contact-box h2 { 65 | font-size: 18px; 66 | } 67 | a.contact-social { 68 | font-size: 20px; 69 | } 70 | .contact-flex p { 71 | margin-top: 0px; 72 | } 73 | } 74 | @media only screen and (max-width: 900px) { 75 | .contact-flex { 76 | flex-direction: column; 77 | gap: 40px; 78 | } 79 | .contact-flex p { 80 | margin-bottom: 0px; 81 | } 82 | .contact-flex h4 { 83 | margin-top: 20px; 84 | } 85 | .contact-section { 86 | margin-top: 50px; 87 | padding-bottom: 50px; 88 | } 89 | .contact-container { 90 | width: calc(100% - 25px); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/components/styles/Cursor.css: -------------------------------------------------------------------------------- 1 | .cursor-main { 2 | --size: 0px; 3 | position: fixed; 4 | top: calc(var(--size) / -2); 5 | left: calc(var(--size) / -2); 6 | width: var(--size); 7 | height: var(--size); 8 | border-radius: 50px; 9 | pointer-events: none; 10 | z-index: 99; 11 | background-color: #e6c3ff; 12 | box-shadow: 0px 0px 30px 0px rgb(175, 131, 255); 13 | mix-blend-mode: difference; 14 | transition: top 0.3s ease-out, left 0.3s ease-out, width 0.3s ease-out, 15 | height 0.3s ease-out; 16 | } 17 | .cursor-icons { 18 | top: 10px; 19 | left: 10px; 20 | height: calc(var(--cursorH) - 20px); 21 | transition: all 0.5s ease-out, height 0.5s ease-in-out; 22 | } 23 | .cursor-disable { 24 | --size: 0px; 25 | } 26 | @media only screen and (min-width: 600px) { 27 | .cursor-main { 28 | --size: 50px; 29 | } 30 | .cursor-disable { 31 | --size: 0px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/styles/Landing.css: -------------------------------------------------------------------------------- 1 | .landing-section { 2 | width: 100%; 3 | max-width: var(--cMaxWidth); 4 | margin: auto; 5 | position: relative; 6 | height: var(--vh); 7 | } 8 | .landing-container { 9 | width: var(--cWidth); 10 | margin: auto; 11 | height: 100%; 12 | position: relative; 13 | max-width: var(--cMaxWidth); 14 | } 15 | .landing-circle1 { 16 | top: 0%; 17 | left: 0%; 18 | z-index: 15; 19 | position: fixed; 20 | width: 300px; 21 | height: 300px; 22 | background-color: #fb8dff; 23 | box-shadow: inset -50px 40px 50px rgba(84, 0, 255, 0.6); 24 | filter: blur(60px); 25 | border-radius: 50%; 26 | animation: loadingCircle 5s linear infinite; 27 | } 28 | .nav-fade { 29 | position: fixed; 30 | top: 0; 31 | width: 100%; 32 | height: 130px; 33 | background-image: linear-gradient( 34 | 0deg, 35 | transparent, 36 | var(--backgroundColor) 70% 37 | ); 38 | pointer-events: none; 39 | z-index: 12; 40 | opacity: 0; 41 | left: 0; 42 | } 43 | @keyframes loadingCircle { 44 | 0% { 45 | transform: translate(-95%, -75%) rotateZ(0deg); 46 | } 47 | 100% { 48 | transform: translate(-95%, -75%) rotateZ(360deg); 49 | } 50 | } 51 | 52 | .landing-circle2 { 53 | top: 50%; 54 | right: 0%; 55 | transform: translate(calc(100% - 2px), -50%); 56 | z-index: 9; 57 | position: fixed; 58 | display: none; 59 | width: 300px; 60 | height: 300px; 61 | background-color: #fb8dff; 62 | box-shadow: inset -50px 40px 50px rgba(84, 0, 255, 0.6); 63 | filter: blur(50px); 64 | border-radius: 50%; 65 | animation: loadingCircle2 5s linear infinite; 66 | } 67 | @keyframes loadingCircle2 { 68 | 100% { 69 | transform: translate(calc(100% - 2px), -50%) rotate(360deg); 70 | } 71 | } 72 | .landing-video, 73 | .landing-image { 74 | position: absolute; 75 | bottom: 0; 76 | height: 95%; 77 | left: 50%; 78 | transform: translateX(-50%); 79 | } 80 | .landing-image img { 81 | height: 100%; 82 | z-index: 2; 83 | position: relative; 84 | } 85 | .character-rim { 86 | position: absolute; 87 | width: 400px; 88 | height: 400px; 89 | z-index: 1; 90 | background-color: #f59bf8; 91 | transform: translate(-50%, 36%) scaleX(1.4); 92 | box-shadow: inset 66px 35px 85px 0px rgba(85, 0, 255, 0.65); 93 | filter: blur(50px); 94 | border-radius: 50%; 95 | top: 50%; 96 | left: 50%; 97 | transform: translate(-50%, 100%) scale(1.4); 98 | opacity: 0; 99 | } 100 | .character-model { 101 | height: 80%; 102 | height: 80vh; 103 | position: absolute; 104 | max-width: 1920px; 105 | max-height: 1080px; 106 | transform: translateX(-50%); 107 | width: 100%; 108 | left: 50%; 109 | z-index: 0; 110 | bottom: 50px; 111 | pointer-events: inherit; 112 | } 113 | .character-model::after { 114 | content: ""; 115 | width: 100vw; 116 | height: 250px; 117 | background-image: linear-gradient( 118 | to bottom, 119 | transparent, 120 | var(--backgroundColor) 70% 121 | ); 122 | bottom: -50px; 123 | left: 50%; 124 | transform: translateX(-50%); 125 | z-index: 9; 126 | position: absolute; 127 | } 128 | .character-model::before { 129 | content: ""; 130 | width: 100vw; 131 | height: 700px; 132 | background-color: var(--backgroundColor); 133 | top: 100%; 134 | left: 50%; 135 | transform: translateX(-50%); 136 | z-index: 9; 137 | position: absolute; 138 | } 139 | .character-loaded .character-rim { 140 | animation: backlight 3s forwards; 141 | animation-delay: 0.3s; 142 | opacity: 0; 143 | } 144 | .character-model canvas { 145 | position: relative; 146 | pointer-events: none; 147 | z-index: 2; 148 | } 149 | .character-hover { 150 | position: absolute; 151 | width: 280px; 152 | height: 280px; 153 | top: 50%; 154 | left: 50%; 155 | z-index: 3; 156 | transform: translate(-50%, -50%); 157 | border-radius: 50%; 158 | } 159 | .landing-intro { 160 | position: absolute; 161 | z-index: 9; 162 | top: 12%; 163 | left: 0; 164 | } 165 | .landing-intro h2 { 166 | margin: 0; 167 | color: var(--accentColor); 168 | font-size: 22px; 169 | font-weight: 300; 170 | letter-spacing: 2px; 171 | } 172 | 173 | .landing-intro h1 { 174 | margin: 0; 175 | letter-spacing: 2px; 176 | font-size: 28px; 177 | line-height: 28px; 178 | font-weight: 500; 179 | font-family: "Geist", sans-serif; 180 | } 181 | 182 | /* .landing-intro h1 span { 183 | font-weight: 200; 184 | } */ 185 | 186 | .landing-info { 187 | position: absolute; 188 | right: 50%; 189 | transform: translateX(50%); 190 | bottom: 40px; 191 | top: inherit; 192 | z-index: 9; 193 | } 194 | .landing-info h3 { 195 | font-size: 22px; 196 | letter-spacing: 2px; 197 | font-weight: 300; 198 | color: var(--accentColor); 199 | margin: 0; 200 | } 201 | .landing-info h2 { 202 | margin: 0; 203 | margin-top: -20px; 204 | margin-left: 20px; 205 | font-family: "Geist", sans-serif; 206 | font-weight: 600; 207 | font-size: 32px; 208 | line-height: 40px; 209 | position: relative; 210 | display: flex; 211 | flex-wrap: nowrap; 212 | text-transform: uppercase; 213 | letter-spacing: 2px; 214 | } 215 | .landing-h2-info-1 { 216 | position: absolute; 217 | top: 0; 218 | } 219 | h2.landing-info-h2 { 220 | color: #c481ff; 221 | font-size: 42px; 222 | width: 120%; 223 | margin: 0; 224 | font-family: "Geist", sans-serif; 225 | font-weight: 600; 226 | position: relative; 227 | margin-left: -5px; 228 | } 229 | .landing-h2-2 { 230 | position: absolute; 231 | top: 0; 232 | } 233 | .landing-info-h2::after { 234 | content: ""; 235 | position: absolute; 236 | width: 100%; 237 | height: 120%; 238 | z-index: 3; 239 | background-image: linear-gradient( 240 | 0deg, 241 | var(--backgroundColor) 40%, 242 | rgba(0, 0, 0, 0) 110% 243 | ); 244 | top: 0; 245 | left: 0; 246 | } 247 | @media screen and (min-width: 500px) { 248 | .landing-circle2 { 249 | display: block; 250 | } 251 | .character-model { 252 | z-index: 0; 253 | } 254 | .landing-info h3 { 255 | font-size: 18px; 256 | } 257 | .landing-intro h2 { 258 | font-size: 18px; 259 | } 260 | .landing-intro h1 { 261 | font-size: 30px; 262 | line-height: 30px; 263 | } 264 | .landing-info h2 { 265 | font-size: 35px; 266 | line-height: 40px; 267 | } 268 | h2.landing-info-h2 { 269 | font-size: 38px; 270 | } 271 | } 272 | @media screen and (min-width: 768px) { 273 | .character-model { 274 | height: 80vh; 275 | } 276 | .landing-intro h2 { 277 | font-size: 25px; 278 | } 279 | .landing-intro h1 { 280 | font-size: 40px; 281 | line-height: 35px; 282 | } 283 | .landing-info h3 { 284 | font-size: 25px; 285 | } 286 | .landing-info h2 { 287 | font-size: 45px; 288 | line-height: 42px; 289 | } 290 | h2.landing-info-h2 { 291 | font-size: 55px; 292 | } 293 | } 294 | @media screen and (min-width: 1025px) { 295 | .character-model { 296 | height: 100vh; 297 | bottom: 0; 298 | z-index: 11; 299 | position: fixed; 300 | } 301 | .character-model::after, 302 | .character-model::before { 303 | display: none; 304 | } 305 | .landing-intro { 306 | top: 50%; 307 | left: auto; 308 | right: 66%; 309 | transform: translate(0%, -50%); 310 | } 311 | 312 | .landing-info { 313 | bottom: auto; 314 | top: 51%; 315 | z-index: inherit; 316 | text-align: left; 317 | transform: translate(0%, -50%); 318 | right: auto; 319 | left: 66%; 320 | } 321 | } 322 | @media screen and (min-width: 1200px) { 323 | .landing-intro { 324 | top: 50%; 325 | left: auto; 326 | right: 70%; 327 | transform: translate(0%, -50%); 328 | } 329 | 330 | .landing-info { 331 | bottom: auto; 332 | top: 51%; 333 | z-index: inherit; 334 | text-align: left; 335 | transform: translate(0%, -50%); 336 | right: auto; 337 | left: 70%; 338 | } 339 | } 340 | @media screen and (min-width: 1600px) { 341 | .landing-intro h2 { 342 | font-size: 35px; 343 | } 344 | .landing-intro h1 { 345 | font-size: 60px; 346 | line-height: 55px; 347 | } 348 | .landing-info h3 { 349 | font-size: 35px; 350 | } 351 | .landing-info h2 { 352 | font-size: 65px; 353 | line-height: 62px; 354 | } 355 | h2.landing-info-h2 { 356 | font-size: 75px; 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/components/styles/Loading.css: -------------------------------------------------------------------------------- 1 | .loading-screen { 2 | position: fixed; 3 | width: 100vw; 4 | height: var(--vh); 5 | /* background-image: linear-gradient(#cbb1ff, #d8c4ff); */ 6 | background-color: #eae5ec; 7 | z-index: 999999999; 8 | display: flex; 9 | place-items: center; 10 | justify-content: center; 11 | } 12 | .loading-button { 13 | padding: 20px 50px; 14 | border-radius: 100px; 15 | background-color: #000; 16 | overflow: hidden; 17 | font-size: 18px; 18 | font-weight: 500; 19 | position: relative; 20 | z-index: 9; 21 | } 22 | .loading-button::before { 23 | content: ""; 24 | background-color: #ffffff; 25 | top: var(--mouse-y); 26 | left: var(--mouse-x); 27 | border-radius: 50%; 28 | width: 60px; 29 | height: 60px; 30 | opacity: 1; 31 | position: absolute; 32 | z-index: 99; 33 | filter: blur(60px); 34 | opacity: 0; 35 | transform: translate(-50%, -50%); 36 | } 37 | .loading-button:hover::before { 38 | opacity: 1; 39 | } 40 | .loading-clicked .loading-button::before { 41 | opacity: 0; 42 | } 43 | 44 | .loading-wrap { 45 | --Lsize: 145px; 46 | padding: 6px; 47 | position: relative; 48 | min-width: 0px; 49 | min-height: 0px; 50 | border-radius: 100px; 51 | background-color: #000; 52 | overflow: hidden; 53 | transition: 0.8s ease-in-out; 54 | transition-delay: 0.2s; 55 | box-shadow: 0px 15px 15px 0px rgba(0, 0, 0, 0.2); 56 | display: flex; 57 | justify-content: center; 58 | align-items: center; 59 | } 60 | .loading-clicked { 61 | transition-delay: 0ms; 62 | transition-timing-function: cubic-bezier(0.33, 0.11, 1, 0.72); 63 | transform: scale(1); 64 | min-width: calc(100vw + 5000px); 65 | border-radius: 5000px; 66 | min-height: calc(100vh + 500px); 67 | box-shadow: none; 68 | } 69 | .loading-clicked .loading-button { 70 | overflow: visible; 71 | } 72 | .loading-hover { 73 | background-color: #a87cff; 74 | width: 250px; 75 | height: 120px; 76 | position: absolute; 77 | top: var(--mouse-y); 78 | left: var(--mouse-x); 79 | border-radius: 50%; 80 | transform: translate(-50%, -50%); 81 | filter: blur(30px); 82 | opacity: 1; 83 | transition: opacity 500ms; 84 | } 85 | 86 | .loading-wrap:hover .loading-hover { 87 | opacity: 1; 88 | } 89 | .loading-clicked:hover .loading-hover, 90 | .loading-clicked .loading-hover { 91 | opacity: 0; 92 | } 93 | .loading-content { 94 | position: relative; 95 | background-color: #000; 96 | width: 100%; 97 | overflow: hidden; 98 | transition: 0.6s; 99 | text-transform: uppercase; 100 | } 101 | .loading-content-in { 102 | position: relative; 103 | width: var(--Lsize); 104 | overflow: hidden; 105 | } 106 | .loading-content2 { 107 | position: relative; 108 | letter-spacing: 2px; 109 | text-transform: uppercase; 110 | width: var(--Lsize); 111 | overflow: hidden; 112 | display: flex; 113 | justify-content: center; 114 | align-items: center; 115 | column-gap: 10px; 116 | text-align: center; 117 | transition: 1s; 118 | max-width: var(--Lsize); 119 | } 120 | .loading-clicked .loading-content2 { 121 | opacity: 0; 122 | transition: 0.5s; 123 | } 124 | 125 | .loading-content span { 126 | font-weight: 300; 127 | position: absolute; 128 | top: 50%; 129 | right: 0; 130 | transform: translateY(-50%); 131 | opacity: 0.7; 132 | } 133 | .loading-box { 134 | position: absolute; 135 | right: 0px; 136 | top: 50%; 137 | transform: translate(100%, -50%); 138 | width: 15px; 139 | height: 25px; 140 | background-color: white; 141 | animation: blink 1s linear infinite; 142 | } 143 | .loading-icon { 144 | transform: scale(0); 145 | opacity: 0; 146 | transition: 0.5s; 147 | transition-delay: 0.5s; 148 | } 149 | 150 | .loading-complete .loading-icon { 151 | transform: scale(1); 152 | opacity: 1; 153 | } 154 | .loading-clicked .loading-icon { 155 | transition-delay: 0s; 156 | transition: 1s; 157 | transform: translateX(200px); 158 | } 159 | .loading-clicked .loading-content2 { 160 | overflow: visible; 161 | } 162 | .loading-clicked .loading-content2 span { 163 | transition: 1s; 164 | transform: translateY(100px); 165 | opacity: 0; 166 | } 167 | .loading-container { 168 | position: absolute; 169 | width: 100%; 170 | max-width: var(--Lsize); 171 | /* height: 45px; */ 172 | top: 50%; 173 | transition: 1s; 174 | left: 50px; 175 | z-index: 9; 176 | transform: translateY(-50%); 177 | } 178 | 179 | .loading-complete .loading-container { 180 | max-width: 0px; 181 | } 182 | .loading-header { 183 | width: var(--cWidth); 184 | max-width: var(--cMaxWidth); 185 | position: fixed; 186 | z-index: 9999999999; 187 | display: flex; 188 | justify-content: space-between; 189 | box-sizing: border-box; 190 | padding: 20px 0px; 191 | left: 50%; 192 | transform: translateX(-50%); 193 | top: 0; 194 | color: var(--backgroundColor); 195 | } 196 | .loader-title { 197 | font-weight: 700; 198 | font-size: 14px; 199 | letter-spacing: 0.2px; 200 | } 201 | @keyframes blink { 202 | 0% { 203 | opacity: 0; 204 | } 205 | 25% { 206 | opacity: 1; 207 | } 208 | 75% { 209 | opacity: 1; 210 | } 211 | 100% { 212 | opacity: 0; 213 | } 214 | } 215 | .loading-complete .loading-box { 216 | animation: blinkDone 0.3s forwards; 217 | animation-delay: 1s; 218 | opacity: 1; 219 | } 220 | @keyframes blinkDone { 221 | to { 222 | opacity: 0; 223 | } 224 | } 225 | 226 | .loaderGame-container { 227 | width: 200px; 228 | transition: 0.3s; 229 | height: 100px; 230 | overflow: hidden; 231 | position: relative; 232 | transform: scale(0.4); 233 | transform-origin: top right; 234 | } 235 | .loader-out .loaderGame-container { 236 | opacity: 0; 237 | } 238 | .loaderGame-in { 239 | width: 1200px; 240 | position: absolute; 241 | overflow: hidden; 242 | left: 0; 243 | animation: loaderGame 7s linear infinite; 244 | } 245 | @keyframes loaderGame { 246 | 0% { 247 | transform: translateX(0px); 248 | } 249 | 100% { 250 | transform: translateX(-300px); 251 | } 252 | } 253 | .loaderGame-line { 254 | float: left; 255 | margin: 0px 20px; 256 | margin-bottom: 40px; 257 | position: relative; 258 | width: 10px; 259 | height: 60px; 260 | background-color: #000; 261 | display: block; 262 | } 263 | .loaderGame-line:nth-child(2n) { 264 | margin-top: 40px; 265 | margin-bottom: 0px; 266 | } 267 | 268 | .loaderGame-ball { 269 | position: absolute; 270 | left: 20%; 271 | top: 0%; 272 | width: 15px; 273 | height: 15px; 274 | border-radius: 50%; 275 | background-color: #a87cff; 276 | animation: ball25 7s infinite; 277 | transform: translateY(10px); 278 | animation-timing-function: cubic-bezier(0.3, 1.18, 0.63, 1.28); 279 | } 280 | .loading-marquee { 281 | position: absolute; 282 | top: 50%; 283 | left: 0; 284 | width: 100%; 285 | transform: translateY(-50%); 286 | color: var(--backgroundColor); 287 | font-size: 60px; 288 | font-weight: 600; 289 | text-transform: uppercase; 290 | } 291 | .loading-marquee span { 292 | padding: 0px 50px; 293 | position: relative; 294 | } 295 | .loading-marquee span::before { 296 | content: ""; 297 | width: 20px; 298 | height: 20px; 299 | background-color: var(--backgroundColor); 300 | position: absolute; 301 | top: 50%; 302 | border-radius: 50px; 303 | left: 0px; 304 | transform: translate(-50%, -50%); 305 | } 306 | @keyframes ball25 { 307 | 0% { 308 | transform: translateY(70px); 309 | } 310 | 15% { 311 | transform: translateY(10px); 312 | } 313 | 30% { 314 | transform: translateY(70px); 315 | } 316 | 45% { 317 | transform: translateY(10px); 318 | } 319 | 67% { 320 | transform: translateY(70px); 321 | } 322 | 80% { 323 | transform: translateY(10px); 324 | } 325 | 90% { 326 | transform: translateY(70px); 327 | } 328 | 100% { 329 | transform: translateY(70px); 330 | } 331 | } 332 | 333 | @media only screen and (min-width: 1400px) { 334 | .loading-wrap { 335 | --Lsize: 210px; 336 | } 337 | .loading-button { 338 | padding: 30px 70px; 339 | font-size: 25px; 340 | } 341 | .loading-container { 342 | left: 70px; 343 | } 344 | .loading-marquee { 345 | font-size: 100px; 346 | } 347 | } 348 | @media only screen and (min-width: 500px) { 349 | .loading-header { 350 | padding: 20px 0px; 351 | } 352 | .loader-title { 353 | font-size: 16px; 354 | } 355 | } 356 | @media only screen and (min-width: 1200px) { 357 | .loading-header { 358 | padding: 35px 0px; 359 | } 360 | .loader-title { 361 | font-size: 18px; 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /src/components/styles/Navbar.css: -------------------------------------------------------------------------------- 1 | .header { 2 | display: flex; 3 | max-width: var(--cMaxWidth); 4 | width: var(--cWidth); 5 | justify-content: space-between; 6 | padding: 20px 0px; 7 | margin-bottom: -100px; 8 | box-sizing: border-box; 9 | position: fixed; 10 | left: 50%; 11 | transform: translateX(-50%); 12 | top: 0; 13 | z-index: 9999; 14 | } 15 | .header ul { 16 | font-size: 12px; 17 | display: flex; 18 | flex-direction: column; 19 | margin: 0; 20 | padding: 0; 21 | list-style: none; 22 | column-gap: 40px; 23 | row-gap: 8px; 24 | align-items: end; 25 | } 26 | .header ul li { 27 | margin-left: 0px; 28 | letter-spacing: 1px; 29 | color: #ccc; 30 | font-weight: 600; 31 | cursor: pointer; 32 | } 33 | .navbar-connect { 34 | position: absolute; 35 | display: none; 36 | left: 50%; 37 | top: 50%; 38 | transform: translate(-50%, -50%); 39 | font-size: 15px; 40 | letter-spacing: 1px; 41 | font-weight: 500; 42 | } 43 | .navbar-title { 44 | font-weight: 700; 45 | font-size: 14px; 46 | letter-spacing: 0.2px; 47 | } 48 | @media only screen and (min-width: 500px) { 49 | .header { 50 | padding: 20px 0px; 51 | } 52 | .header ul { 53 | flex-direction: row; 54 | align-items: center; 55 | font-size: 14px; 56 | } 57 | .header ul li { 58 | color: #eae5ec; 59 | } 60 | .navbar-title { 61 | font-size: 16px; 62 | } 63 | } 64 | @media only screen and (min-width: 900px) { 65 | .navbar-connect { 66 | display: block; 67 | } 68 | } 69 | @media only screen and (min-width: 1200px) { 70 | .header { 71 | padding: 35px 0px; 72 | } 73 | .header ul { 74 | column-gap: 80px; 75 | font-size: 16px; 76 | } 77 | .navbar-connect { 78 | font-size: 16px; 79 | } 80 | .navbar-title { 81 | font-size: 18px; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/components/styles/SocialIcons.css: -------------------------------------------------------------------------------- 1 | .icons-section { 2 | position: fixed; 3 | max-width: var(--cMaxWidth); 4 | width: var(--cWidth); 5 | bottom: 0; 6 | z-index: 99; 7 | left: 50%; 8 | transform: translateX(-50%); 9 | } 10 | .social-icons { 11 | position: absolute; 12 | left: -20px; 13 | bottom: 20px; 14 | display: none; 15 | flex-direction: column; 16 | gap: 8px; 17 | z-index: 999; 18 | padding: 10px; 19 | } 20 | .social-icons:hover { 21 | transition: 0.3s; 22 | color: var(--backgroundColor); 23 | } 24 | .social-icons a:hover { 25 | color: var(--backgroundColor); 26 | /* transform: scale(1.2); */ 27 | } 28 | .social-icons span { 29 | width: 50px; 30 | height: 50px; 31 | position: relative; 32 | display: flex; 33 | } 34 | .social-icons a { 35 | --siLeft: 50%; 36 | --siTop: 50%; 37 | position: absolute; 38 | left: var(--siLeft, 50%); 39 | top: var(--siTop, 50%); 40 | transform: translate(-50%, -50%); 41 | display: flex; 42 | font-size: 23px; 43 | will-change: left, top; 44 | transition: transform 0.3s ease-out; 45 | } 46 | .resume-button { 47 | position: absolute; 48 | z-index: 99; 49 | display: flex; 50 | gap: 5px; 51 | bottom: 40px; 52 | right: 0; 53 | width: auto; 54 | text-wrap: nowrap; 55 | letter-spacing: 4px; 56 | font-size: 15px; 57 | line-height: 15px; 58 | font-weight: 600; 59 | color: #5e5e5e; 60 | cursor: pointer; 61 | transition: 0.5s; 62 | transform-origin: left bottom; 63 | transform: translateX(100%) rotateZ(-90deg); 64 | } 65 | .resume-button:hover { 66 | color: #fff; 67 | } 68 | div.resume-button span { 69 | color: #fff; 70 | font-size: 17px; 71 | margin-top: -1px; 72 | display: flex; 73 | align-items: center; 74 | } 75 | .check-line { 76 | position: fixed; 77 | top: 655px; 78 | left: 0; 79 | height: 1px; 80 | background-color: #ffffff; 81 | width: 100%; 82 | z-index: 99999; 83 | } 84 | @media only screen and (min-width: 900px) { 85 | .social-icons { 86 | display: flex; 87 | gap: 20px; 88 | } 89 | .social-icons a { 90 | font-size: 28px; 91 | } 92 | } 93 | @media only screen and (min-width: 768px) { 94 | .resume-button { 95 | transform: none; 96 | font-size: 20px; 97 | line-height: 20px; 98 | } 99 | div.resume-button span { 100 | font-size: 23px; 101 | margin-top: -1.5px; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/components/styles/WhatIDo.css: -------------------------------------------------------------------------------- 1 | .whatIDO { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | place-items: center; 6 | position: relative; 7 | opacity: 1; 8 | height: 100vh; 9 | width: var(--cWidth); 10 | max-width: 1920px; 11 | margin: auto; 12 | z-index: 9; 13 | } 14 | .what-box { 15 | width: 50%; 16 | display: flex; 17 | justify-content: center; 18 | position: relative; 19 | z-index: 9; 20 | } 21 | .what-box h2 { 22 | font-size: calc(4vw + 25px); 23 | line-height: calc(4vw + 20px); 24 | font-weight: 600; 25 | margin-right: 10%; 26 | margin-bottom: 100px; 27 | } 28 | 29 | .hat-h2 { 30 | font-style: italic; 31 | } 32 | .do-h2 { 33 | color: var(--accentColor); 34 | } 35 | .what-box-in { 36 | flex-direction: column; 37 | height: 500px; 38 | margin-left: 200px; 39 | position: relative; 40 | display: none; 41 | } 42 | .what-content { 43 | width: 450px; 44 | height: 33%; 45 | min-height: 50%; 46 | transition: 0.5s; 47 | /* border: 0.5px dashed rgba(255, 255, 255, 0.3); */ 48 | position: relative; 49 | padding: 50px; 50 | box-sizing: border-box; 51 | } 52 | 53 | .what-noTouch:hover, 54 | .what-content-active { 55 | min-height: 67%; 56 | padding: 40px 50px; 57 | } 58 | 59 | .what-noTouch:hover ~ .what-content, 60 | .what-box-in:hover .what-noTouch:not(:hover), 61 | .what-content.what-sibling { 62 | min-height: 33%; 63 | padding: 10px 50px; 64 | } 65 | .what-content h3 { 66 | font-size: 35px; 67 | letter-spacing: 1px; 68 | margin: 0; 69 | } 70 | .what-content p { 71 | font-size: 14px; 72 | line-height: 18px; 73 | font-weight: 200; 74 | letter-spacing: 0.7px; 75 | } 76 | .what-content h4 { 77 | font-weight: 300; 78 | letter-spacing: 1px; 79 | margin: 0px; 80 | font-size: 14px; 81 | opacity: 0.3; 82 | } 83 | .what-content-in { 84 | opacity: 0; 85 | animation: whatFlicker 0.5s 1 forwards; 86 | animation-delay: 1s; 87 | } 88 | @keyframes whatFlicker { 89 | 0%, 90 | 25%, 91 | 35%, 92 | 60% { 93 | opacity: 0; 94 | } 95 | 96 | 30%, 97 | 50%, 98 | 40%, 99 | 100% { 100 | opacity: 1; 101 | } 102 | } 103 | .what-content::before, 104 | .what-corner::before, 105 | .what-content::after, 106 | .what-corner::after { 107 | content: ""; 108 | width: 10px; 109 | height: 10px; 110 | position: absolute; 111 | border: 4px solid #fff; 112 | opacity: 0; 113 | animation: whatCorners 0.2s 1 forwards; 114 | animation-delay: 0.5s; 115 | } 116 | @keyframes whatCorners { 117 | 100% { 118 | opacity: 1; 119 | } 120 | } 121 | .what-content::before { 122 | top: -2px; 123 | left: -2px; 124 | border-right: none; 125 | border-bottom: none; 126 | } 127 | .what-corner::before { 128 | top: -2px; 129 | right: -2px; 130 | border-left: none; 131 | border-bottom: none; 132 | } 133 | .what-content::after { 134 | bottom: -2px; 135 | left: -2px; 136 | border-top: none; 137 | border-right: none; 138 | } 139 | .what-corner::after { 140 | bottom: -2px; 141 | right: -2px; 142 | border-left: none; 143 | border-top: none; 144 | } 145 | .what-arrow { 146 | position: absolute; 147 | bottom: 20px; 148 | right: 20px; 149 | width: 25px; 150 | height: 25px; 151 | border: 1px solid #fff; 152 | } 153 | .what-arrow::before { 154 | content: ""; 155 | position: absolute; 156 | top: 40%; 157 | left: 50%; 158 | transform: translate(-50%, -50%) rotate(-45deg); 159 | border-left: 1px solid #fff; 160 | border-bottom: 1px solid #fff; 161 | transition: 0.5s; 162 | width: 10px; 163 | height: 10px; 164 | } 165 | .what-noTouch:hover .what-arrow::before, 166 | .what-content-active .what-arrow::before { 167 | transform: translate(-50%, -20%) rotate(-225deg); 168 | } 169 | .what-border1 { 170 | position: absolute; 171 | top: 0; 172 | width: 100%; 173 | left: 50%; 174 | transform: translateX(-50%); 175 | height: 100%; 176 | transition: 0.5s; 177 | max-width: 0%; 178 | overflow: hidden; 179 | opacity: 0.8; 180 | animation: whatBorders 1.2s 1 forwards; 181 | } 182 | .what-border1 svg { 183 | position: absolute; 184 | left: 50%; 185 | transform: translateX(-50%); 186 | width: 450px; 187 | } 188 | .what-border2 { 189 | position: absolute; 190 | top: 50%; 191 | width: 100%; 192 | left: 0; 193 | transform: translateY(-50%); 194 | height: 100%; 195 | max-height: 0%; 196 | overflow: hidden; 197 | transition: 0.5s; 198 | opacity: 0.8; 199 | animation: whatBorders 1.2s 1 forwards; 200 | } 201 | .what-border2 svg { 202 | height: 500px; 203 | top: 50%; 204 | transform: translateY(-50%); 205 | position: absolute; 206 | } 207 | .what-content-in { 208 | height: 100%; 209 | overflow: hidden; 210 | } 211 | .what-content-in h5 { 212 | font-weight: 300; 213 | opacity: 0.5; 214 | font-size: 12px; 215 | letter-spacing: 1px; 216 | font-family: "Geist", sans-serif; 217 | margin-bottom: 5px; 218 | } 219 | @keyframes whatBorders { 220 | 80% { 221 | opacity: 0.8; 222 | } 223 | 100% { 224 | max-height: 100%; 225 | max-width: 100%; 226 | opacity: 0.2; 227 | } 228 | } 229 | .what-content-flex { 230 | display: flex; 231 | gap: 5px; 232 | flex-wrap: wrap; 233 | } 234 | .what-tags { 235 | font-size: 13px; 236 | font-weight: 400; 237 | padding: 2px 7px; 238 | background-color: rgba(255, 255, 255, 0.15); 239 | border: 1px solid #ffffff50; 240 | border-radius: 30px; 241 | } 242 | @media only screen and (max-width: 1600px) { 243 | .what-box h2 { 244 | margin-right: 18%; 245 | } 246 | } 247 | @media only screen and (max-width: 1400px) { 248 | .what-box h2 { 249 | margin-right: 20%; 250 | } 251 | .what-box-in { 252 | height: 400px; 253 | } 254 | .what-content h3 { 255 | font-size: 28px; 256 | } 257 | .what-content { 258 | padding: 30px; 259 | width: 400px; 260 | } 261 | .what-content p { 262 | font-size: 13px; 263 | } 264 | .what-noTouch:hover, 265 | .what-content-active { 266 | padding: 20px 30px; 267 | } 268 | 269 | .what-noTouch:hover ~ .what-content, 270 | .what-box-in:hover .what-noTouch:not(:hover), 271 | .what-content.what-sibling { 272 | padding: 10px 30px; 273 | } 274 | .what-tags { 275 | font-size: 12px; 276 | } 277 | } 278 | @media only screen and (max-width: 1400px) { 279 | .what-box-in { 280 | margin-left: 50px; 281 | } 282 | .what-content { 283 | width: 380px; 284 | } 285 | } 286 | @media only screen and (max-width: 1024px) { 287 | .whatIDO { 288 | height: auto; 289 | padding: 50px 0px; 290 | padding-bottom: 50px; 291 | } 292 | .what-box-in { 293 | height: 500px; 294 | margin-left: -50px; 295 | } 296 | .what-content { 297 | padding: 50px; 298 | width: 500px; 299 | } 300 | .what-content p { 301 | font-size: 14px; 302 | } 303 | .what-noTouch:hover, 304 | .what-content-active { 305 | min-height: 67%; 306 | padding: 50px; 307 | } 308 | 309 | .what-noTouch:hover ~ .what-content, 310 | .what-box-in:hover .what-noTouch:not(:hover), 311 | .what-content.what-sibling { 312 | min-height: 33%; 313 | padding: 10px 50px; 314 | } 315 | } 316 | @media only screen and (max-width: 900px) { 317 | .whatIDO { 318 | flex-direction: column; 319 | } 320 | .what-box h2 { 321 | margin: 50px 0; 322 | font-size: 55px; 323 | line-height: 53px; 324 | } 325 | .what-box:first-child { 326 | justify-content: left; 327 | } 328 | .what-box:last-child { 329 | height: 500px; 330 | } 331 | .what-box { 332 | width: 500px; 333 | max-width: calc(100% - 50px); 334 | margin: auto; 335 | } 336 | .what-content { 337 | width: 100%; 338 | } 339 | .what-box-in { 340 | margin-left: 0px; 341 | height: 450px; 342 | } 343 | .what-content h5, 344 | .what-content-flex { 345 | opacity: 0; 346 | transition: 0.3s; 347 | } 348 | .what-noTouch:hover h5, 349 | .what-content-active h5, 350 | .what-noTouch:hover .what-content-flex, 351 | .what-content-active .what-content-flex { 352 | opacity: 1; 353 | } 354 | .what-content { 355 | padding: 30px; 356 | } 357 | .what-content p { 358 | font-size: 11px; 359 | } 360 | .what-noTouch:hover, 361 | .what-content-active { 362 | padding: 10px 30px; 363 | } 364 | .what-tags { 365 | font-size: 11px; 366 | } 367 | 368 | .what-noTouch:hover ~ .what-content, 369 | .what-box-in:hover .what-noTouch:not(:hover), 370 | .what-content.what-sibling { 371 | padding: 5px 30px; 372 | } 373 | .what-content h3 { 374 | font-size: 25px; 375 | } 376 | } 377 | @media only screen and (max-width: 550px) { 378 | .whatIDO { 379 | place-items: inherit; 380 | align-items: start; 381 | justify-content: left; 382 | } 383 | .what-box { 384 | max-width: calc(100% - 25px); 385 | margin: 0; 386 | } 387 | } 388 | @media only screen and (min-width: 1950px) { 389 | .what-box h2 { 390 | font-size: 7rem; 391 | line-height: 6.8rem; 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /src/components/styles/Work.css: -------------------------------------------------------------------------------- 1 | .work-section h2 { 2 | margin-top: 100px; 3 | font-size: 70px; 4 | font-weight: 500; 5 | } 6 | .work-section h2 > span { 7 | color: var(--accentColor); 8 | } 9 | .work-section { 10 | transition: 0s; 11 | height: 100vh; 12 | box-sizing: border-box; 13 | will-change: transform; 14 | } 15 | .work-container { 16 | margin: auto; 17 | display: flex; 18 | flex-direction: column; 19 | height: 100%; 20 | align-content: stretch; 21 | } 22 | .work-flex { 23 | width: 100%; 24 | display: flex; 25 | height: 100%; 26 | margin-left: -80px; 27 | padding-right: 120px; 28 | position: relative; 29 | } 30 | .work-box { 31 | padding: 80px; 32 | display: flex; 33 | flex-direction: column; 34 | width: 600px; 35 | box-sizing: border-box; 36 | border-right: 1px solid #363636; 37 | flex-shrink: 0; 38 | gap: 50px; 39 | justify-content: start; 40 | } 41 | .work-flex .work-box:nth-child(even) { 42 | flex-direction: column-reverse; 43 | } 44 | .work-flex::before, 45 | .work-flex::after { 46 | content: ""; 47 | width: calc(50000vw); 48 | height: 1px; 49 | background-color: #363636; 50 | position: absolute; 51 | left: 50%; 52 | top: 0; 53 | transform: translateX(-50%); 54 | } 55 | .work-flex::after { 56 | top: 100%; 57 | } 58 | .work-title { 59 | justify-content: space-between; 60 | display: flex; 61 | width: 100%; 62 | margin-bottom: 20px; 63 | } 64 | .work-title > div { 65 | text-align: right; 66 | } 67 | .work-title h3 { 68 | font-size: 50px; 69 | line-height: 50px; 70 | margin: 0; 71 | font-weight: 600; 72 | } 73 | 74 | .work-info h4 { 75 | font-size: 18px; 76 | font-weight: 400; 77 | margin: 0; 78 | } 79 | .work-info p { 80 | font-weight: 200; 81 | color: #adacac; 82 | margin: 0; 83 | margin-top: 3px; 84 | } 85 | .work-info > p { 86 | width: 90%; 87 | } 88 | .work-image { 89 | display: flex; 90 | width: 100%; 91 | justify-content: center; 92 | } 93 | .work-image-in { 94 | position: relative; 95 | } 96 | .work-link { 97 | position: absolute; 98 | bottom: 10px; 99 | right: 10px; 100 | background-color: var(--backgroundColor); 101 | width: 50px; 102 | border-radius: 50px; 103 | height: 50px; 104 | display: flex; 105 | justify-content: center; 106 | align-items: center; 107 | font-size: 25px; 108 | box-shadow: 0px 0px 10px 0px rgba(255, 255, 255, 0.5), 109 | inset 0px 0px 10px 0px #393939; 110 | transition: 0.3s; 111 | opacity: 0; 112 | } 113 | .work-image a:hover { 114 | color: inherit; 115 | } 116 | .work-image a:hover .work-link { 117 | opacity: 1; 118 | } 119 | .work-image video { 120 | position: absolute; 121 | width: 100%; 122 | height: 100%; 123 | top: 0; 124 | left: 0; 125 | background-color: #000; 126 | object-fit: cover; 127 | } 128 | .work-image img { 129 | max-width: 100%; 130 | max-height: 350px; 131 | } 132 | @media only screen and (max-height: 900px) { 133 | .work-image img { 134 | max-height: 250px; 135 | } 136 | .work-box { 137 | padding-top: 40px; 138 | padding-bottom: 40px; 139 | } 140 | .work-section h2 { 141 | font-size: 60px; 142 | margin-bottom: 30px; 143 | margin-top: 70px; 144 | } 145 | } 146 | @media only screen and (max-width: 1400px) { 147 | .work-title h3 { 148 | font-size: 35px; 149 | } 150 | .work-info p { 151 | font-size: 13px; 152 | } 153 | .work-info h4 { 154 | font-size: 15px; 155 | } 156 | .work-box { 157 | width: 450px; 158 | padding: 50px; 159 | } 160 | .work-flex { 161 | margin-left: -50px; 162 | padding-right: 75px; 163 | } 164 | .work-section h2 { 165 | font-size: 50px; 166 | } 167 | } 168 | @media only screen and (max-width: 1400px) { 169 | .work-box { 170 | width: 350px; 171 | padding: 30px; 172 | } 173 | .work-flex { 174 | margin-left: -30px; 175 | padding-right: 45px; 176 | } 177 | } 178 | 179 | @media only screen and (max-height: 650px) { 180 | .work-image img { 181 | max-height: 200px; 182 | } 183 | .work-section h2 { 184 | font-size: 40px; 185 | margin-bottom: 20px; 186 | } 187 | .work-box { 188 | gap: 20px; 189 | } 190 | } 191 | /* @media only screen and (max-width: 900px) { 192 | .work-image img { 193 | max-height: 200px; 194 | } 195 | .work-section h2 { 196 | font-size: 40px; 197 | margin-bottom: 20px; 198 | } 199 | .work-box { 200 | gap: 20px; 201 | } 202 | } */ 203 | @media only screen and (max-width: 1025px) { 204 | .work-container { 205 | align-content: normal; 206 | } 207 | .work-flex { 208 | height: auto; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/components/styles/style.css: -------------------------------------------------------------------------------- 1 | .hover-link { 2 | position: relative; 3 | display: flex; 4 | text-wrap: nowrap; 5 | overflow: hidden; 6 | } 7 | .hover-in { 8 | position: relative; 9 | transition: 0.3s; 10 | } 11 | .hover-in div { 12 | display: flex; 13 | position: absolute; 14 | top: 100%; 15 | left: 0; 16 | } 17 | .hover-link:hover .hover-in { 18 | transform: translateY(-100%); 19 | color: var(--accentColor); 20 | } 21 | -------------------------------------------------------------------------------- /src/components/utils/GsapScroll.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import gsap from "gsap"; 3 | 4 | export function setCharTimeline( 5 | character: THREE.Object3D | null, 6 | camera: THREE.PerspectiveCamera 7 | ) { 8 | let intensity: number = 0; 9 | setInterval(() => { 10 | intensity = Math.random(); 11 | }, 200); 12 | const tl1 = gsap.timeline({ 13 | scrollTrigger: { 14 | trigger: ".landing-section", 15 | start: "top top", 16 | end: "bottom top", 17 | scrub: true, 18 | invalidateOnRefresh: true, 19 | }, 20 | }); 21 | const tl2 = gsap.timeline({ 22 | scrollTrigger: { 23 | trigger: ".about-section", 24 | start: "center 55%", 25 | end: "bottom top", 26 | scrub: true, 27 | invalidateOnRefresh: true, 28 | }, 29 | }); 30 | const tl3 = gsap.timeline({ 31 | scrollTrigger: { 32 | trigger: ".whatIDO", 33 | start: "top top", 34 | end: "bottom top", 35 | scrub: true, 36 | invalidateOnRefresh: true, 37 | }, 38 | }); 39 | let screenLight: any, monitor: any; 40 | character?.children.forEach((object: any) => { 41 | if (object.name === "Plane004") { 42 | object.children.forEach((child: any) => { 43 | child.material.transparent = true; 44 | child.material.opacity = 0; 45 | if (child.material.name === "Material.027") { 46 | monitor = child; 47 | child.material.color.set("#FFFFFF"); 48 | } 49 | }); 50 | } 51 | if (object.name === "screenlight") { 52 | object.material.transparent = true; 53 | object.material.opacity = 0; 54 | object.material.emissive.set("#C8BFFF"); 55 | gsap.timeline({ repeat: -1, repeatRefresh: true }).to(object.material, { 56 | emissiveIntensity: () => intensity * 8, 57 | duration: () => Math.random() * 0.6, 58 | delay: () => Math.random() * 0.1, 59 | }); 60 | screenLight = object; 61 | } 62 | }); 63 | let neckBone = character?.getObjectByName("spine005"); 64 | if (window.innerWidth > 1024) { 65 | if (character) { 66 | tl1 67 | .fromTo(character.rotation, { y: 0 }, { y: 0.7, duration: 1 }, 0) 68 | .to(camera.position, { z: 22 }, 0) 69 | .fromTo(".character-model", { x: 0 }, { x: "-25%", duration: 1 }, 0) 70 | .to(".landing-container", { opacity: 0, duration: 0.4 }, 0) 71 | .to(".landing-container", { y: "40%", duration: 0.8 }, 0) 72 | .fromTo(".about-me", { y: "-50%" }, { y: "0%" }, 0); 73 | 74 | tl2 75 | .to( 76 | camera.position, 77 | { z: 75, y: 8.4, duration: 6, delay: 2, ease: "power3.inOut" }, 78 | 0 79 | ) 80 | .to(".about-section", { y: "30%", duration: 6 }, 0) 81 | .to(".about-section", { opacity: 0, delay: 3, duration: 2 }, 0) 82 | .fromTo( 83 | ".character-model", 84 | { pointerEvents: "inherit" }, 85 | { pointerEvents: "none", x: "-12%", delay: 2, duration: 5 }, 86 | 0 87 | ) 88 | .to(character.rotation, { y: 0.92, x: 0.12, delay: 3, duration: 3 }, 0) 89 | .to(neckBone!.rotation, { x: 0.6, delay: 2, duration: 3 }, 0) 90 | .to(monitor.material, { opacity: 1, duration: 0.8, delay: 3.2 }, 0) 91 | .to(screenLight.material, { opacity: 1, duration: 0.8, delay: 4.5 }, 0) 92 | .fromTo( 93 | ".what-box-in", 94 | { display: "none" }, 95 | { display: "flex", duration: 0.1, delay: 6 }, 96 | 0 97 | ) 98 | .fromTo( 99 | monitor.position, 100 | { y: -10, z: 2 }, 101 | { y: 0, z: 0, delay: 1.5, duration: 3 }, 102 | 0 103 | ) 104 | .fromTo( 105 | ".character-rim", 106 | { opacity: 1, scaleX: 1.4 }, 107 | { opacity: 0, scale: 0, y: "-70%", duration: 5, delay: 2 }, 108 | 0.3 109 | ); 110 | 111 | tl3 112 | .fromTo( 113 | ".character-model", 114 | { y: "0%" }, 115 | { y: "-100%", duration: 4, ease: "none", delay: 1 }, 116 | 0 117 | ) 118 | .fromTo(".whatIDO", { y: 0 }, { y: "15%", duration: 2 }, 0) 119 | .to(character.rotation, { x: -0.04, duration: 2, delay: 1 }, 0); 120 | } 121 | } else { 122 | if (character) { 123 | const tM2 = gsap.timeline({ 124 | scrollTrigger: { 125 | trigger: ".what-box-in", 126 | start: "top 70%", 127 | end: "bottom top", 128 | }, 129 | }); 130 | tM2.to(".what-box-in", { display: "flex", duration: 0.1, delay: 0 }, 0); 131 | } 132 | } 133 | } 134 | 135 | export function setAllTimeline() { 136 | const careerTimeline = gsap.timeline({ 137 | scrollTrigger: { 138 | trigger: ".career-section", 139 | start: "top 30%", 140 | end: "100% center", 141 | scrub: true, 142 | invalidateOnRefresh: true, 143 | }, 144 | }); 145 | careerTimeline 146 | .fromTo( 147 | ".career-timeline", 148 | { maxHeight: "10%" }, 149 | { maxHeight: "100%", duration: 0.5 }, 150 | 0 151 | ) 152 | 153 | .fromTo( 154 | ".career-timeline", 155 | { opacity: 0 }, 156 | { opacity: 1, duration: 0.1 }, 157 | 0 158 | ) 159 | .fromTo( 160 | ".career-info-box", 161 | { opacity: 0 }, 162 | { opacity: 1, stagger: 0.1, duration: 0.5 }, 163 | 0 164 | ) 165 | .fromTo( 166 | ".career-dot", 167 | { animationIterationCount: "infinite" }, 168 | { 169 | animationIterationCount: "1", 170 | delay: 0.3, 171 | duration: 0.1, 172 | }, 173 | 0 174 | ); 175 | 176 | if (window.innerWidth > 1024) { 177 | careerTimeline.fromTo( 178 | ".career-section", 179 | { y: 0 }, 180 | { y: "20%", duration: 0.5, delay: 0.2 }, 181 | 0 182 | ); 183 | } else { 184 | careerTimeline.fromTo( 185 | ".career-section", 186 | { y: 0 }, 187 | { y: 0, duration: 0.5, delay: 0.2 }, 188 | 0 189 | ); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/components/utils/initialFX.ts: -------------------------------------------------------------------------------- 1 | import { SplitText } from "gsap-trial/SplitText"; 2 | import gsap from "gsap"; 3 | import { smoother } from "../Navbar"; 4 | 5 | export function initialFX() { 6 | document.body.style.overflowY = "auto"; 7 | smoother.paused(false); 8 | document.getElementsByTagName("main")[0].classList.add("main-active"); 9 | gsap.to("body", { 10 | backgroundColor: "#0b080c", 11 | duration: 0.5, 12 | delay: 1, 13 | }); 14 | 15 | var landingText = new SplitText( 16 | [".landing-info h3", ".landing-intro h2", ".landing-intro h1"], 17 | { 18 | type: "chars,lines", 19 | linesClass: "split-line", 20 | } 21 | ); 22 | gsap.fromTo( 23 | landingText.chars, 24 | { opacity: 0, y: 80, filter: "blur(5px)" }, 25 | { 26 | opacity: 1, 27 | duration: 1.2, 28 | filter: "blur(0px)", 29 | ease: "power3.inOut", 30 | y: 0, 31 | stagger: 0.025, 32 | delay: 0.3, 33 | } 34 | ); 35 | 36 | let TextProps = { type: "chars,lines", linesClass: "split-h2" }; 37 | 38 | var landingText2 = new SplitText(".landing-h2-info", TextProps); 39 | gsap.fromTo( 40 | landingText2.chars, 41 | { opacity: 0, y: 80, filter: "blur(5px)" }, 42 | { 43 | opacity: 1, 44 | duration: 1.2, 45 | filter: "blur(0px)", 46 | ease: "power3.inOut", 47 | y: 0, 48 | stagger: 0.025, 49 | delay: 0.3, 50 | } 51 | ); 52 | 53 | gsap.fromTo( 54 | ".landing-info-h2", 55 | { opacity: 0, y: 30 }, 56 | { 57 | opacity: 1, 58 | duration: 1.2, 59 | ease: "power1.inOut", 60 | y: 0, 61 | delay: 0.8, 62 | } 63 | ); 64 | gsap.fromTo( 65 | [".header", ".icons-section", ".nav-fade"], 66 | { opacity: 0 }, 67 | { 68 | opacity: 1, 69 | duration: 1.2, 70 | ease: "power1.inOut", 71 | delay: 0.1, 72 | } 73 | ); 74 | 75 | var landingText3 = new SplitText(".landing-h2-info-1", TextProps); 76 | var landingText4 = new SplitText(".landing-h2-1", TextProps); 77 | var landingText5 = new SplitText(".landing-h2-2", TextProps); 78 | 79 | LoopText(landingText2, landingText3); 80 | LoopText(landingText4, landingText5); 81 | } 82 | 83 | function LoopText(Text1: SplitText, Text2: SplitText) { 84 | var tl = gsap.timeline({ repeat: -1, repeatDelay: 1 }); 85 | const delay = 4; 86 | const delay2 = delay * 2 + 1; 87 | 88 | tl.fromTo( 89 | Text2.chars, 90 | { opacity: 0, y: 80 }, 91 | { 92 | opacity: 1, 93 | duration: 1.2, 94 | ease: "power3.inOut", 95 | y: 0, 96 | stagger: 0.1, 97 | delay: delay, 98 | }, 99 | 0 100 | ) 101 | .fromTo( 102 | Text1.chars, 103 | { y: 80 }, 104 | { 105 | duration: 1.2, 106 | ease: "power3.inOut", 107 | y: 0, 108 | stagger: 0.1, 109 | delay: delay2, 110 | }, 111 | 1 112 | ) 113 | .fromTo( 114 | Text1.chars, 115 | { y: 0 }, 116 | { 117 | y: -80, 118 | duration: 1.2, 119 | ease: "power3.inOut", 120 | stagger: 0.1, 121 | delay: delay, 122 | }, 123 | 0 124 | ) 125 | .to( 126 | Text2.chars, 127 | { 128 | y: -80, 129 | duration: 1.2, 130 | ease: "power3.inOut", 131 | stagger: 0.1, 132 | delay: delay2, 133 | }, 134 | 1 135 | ); 136 | } 137 | -------------------------------------------------------------------------------- /src/components/utils/splitText.ts: -------------------------------------------------------------------------------- 1 | import { gsap } from "gsap"; 2 | import { ScrollTrigger } from "gsap/ScrollTrigger"; 3 | import { ScrollSmoother } from "gsap-trial/ScrollSmoother"; 4 | import { SplitText } from "gsap-trial/SplitText"; 5 | 6 | interface ParaElement extends HTMLElement { 7 | anim?: gsap.core.Animation; 8 | split?: SplitText; 9 | } 10 | 11 | gsap.registerPlugin(ScrollTrigger, ScrollSmoother, SplitText); 12 | 13 | export default function setSplitText() { 14 | ScrollTrigger.config({ ignoreMobileResize: true }); 15 | if (window.innerWidth < 900) return; 16 | const paras: NodeListOf = document.querySelectorAll(".para"); 17 | const titles: NodeListOf = document.querySelectorAll(".title"); 18 | 19 | const TriggerStart = window.innerWidth <= 1024 ? "top 60%" : "20% 60%"; 20 | const ToggleAction = "play pause resume reverse"; 21 | 22 | paras.forEach((para: ParaElement) => { 23 | para.classList.add("visible"); 24 | if (para.anim) { 25 | para.anim.progress(1).kill(); 26 | para.split?.revert(); 27 | } 28 | 29 | para.split = new SplitText(para, { 30 | type: "lines,words", 31 | linesClass: "split-line", 32 | }); 33 | 34 | para.anim = gsap.fromTo( 35 | para.split.words, 36 | { autoAlpha: 0, y: 80 }, 37 | { 38 | autoAlpha: 1, 39 | scrollTrigger: { 40 | trigger: para.parentElement?.parentElement, 41 | toggleActions: ToggleAction, 42 | start: TriggerStart, 43 | }, 44 | duration: 1, 45 | ease: "power3.out", 46 | y: 0, 47 | stagger: 0.02, 48 | } 49 | ); 50 | }); 51 | titles.forEach((title: ParaElement) => { 52 | if (title.anim) { 53 | title.anim.progress(1).kill(); 54 | title.split?.revert(); 55 | } 56 | title.split = new SplitText(title, { 57 | type: "chars,lines", 58 | linesClass: "split-line", 59 | }); 60 | title.anim = gsap.fromTo( 61 | title.split.chars, 62 | { autoAlpha: 0, y: 80, rotate: 10 }, 63 | { 64 | autoAlpha: 1, 65 | scrollTrigger: { 66 | trigger: title.parentElement?.parentElement, 67 | toggleActions: ToggleAction, 68 | start: TriggerStart, 69 | }, 70 | duration: 0.8, 71 | ease: "power2.inOut", 72 | y: 0, 73 | rotate: 0, 74 | stagger: 0.03, 75 | } 76 | ); 77 | }); 78 | 79 | ScrollTrigger.addEventListener("refresh", () => setSplitText()); 80 | } 81 | -------------------------------------------------------------------------------- /src/context/LoadingProvider.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createContext, 3 | PropsWithChildren, 4 | useContext, 5 | useEffect, 6 | useState, 7 | } from "react"; 8 | import Loading from "../components/Loading"; 9 | 10 | interface LoadingType { 11 | isLoading: boolean; 12 | setIsLoading: (state: boolean) => void; 13 | setLoading: (percent: number) => void; 14 | } 15 | 16 | export const LoadingContext = createContext(null); 17 | 18 | export const LoadingProvider = ({ children }: PropsWithChildren) => { 19 | const [isLoading, setIsLoading] = useState(true); 20 | const [loading, setLoading] = useState(0); 21 | 22 | const value = { 23 | isLoading, 24 | setIsLoading, 25 | setLoading, 26 | }; 27 | useEffect(() => {}, [loading]); 28 | 29 | return ( 30 | 31 | {isLoading && } 32 |
{children}
33 |
34 | ); 35 | }; 36 | 37 | export const useLoading = () => { 38 | const context = useContext(LoadingContext); 39 | if (!context) { 40 | throw new Error("useLoading must be used within a LoadingProvider"); 41 | } 42 | return context; 43 | }; 44 | -------------------------------------------------------------------------------- /src/data/boneData.ts: -------------------------------------------------------------------------------- 1 | export const typingBoneNames = [ 2 | "thighL", 3 | "thighR", 4 | // "footL", 5 | // "footR", 6 | "shinL", 7 | "shinR", 8 | "forearmL", 9 | "forearmR", 10 | "handL", 11 | "handR", 12 | "f_pinky03R", 13 | "f_pinky02L", 14 | "f_pinky02R", 15 | "f_pinky01L", 16 | "f_pinky01R", 17 | "palm04L", 18 | "palm04R", 19 | "f_ring01L", 20 | "thumb01L", 21 | "thumb01R", 22 | "thumb03L", 23 | "thumb03R", 24 | "palm02L", 25 | "palm02R", 26 | "palm01L", 27 | "palm01R", 28 | "f_index01L", 29 | "f_index01R", 30 | "palm03L", 31 | "palm03R", 32 | "f_ring02L", 33 | "f_ring02R", 34 | "f_ring01R", 35 | "f_ring03L", 36 | "f_ring03R", 37 | "f_middle01L", 38 | "f_middle02L", 39 | "f_middle03L", 40 | "f_middle01R", 41 | "f_middle02R", 42 | "f_middle03R", 43 | "f_index02L", 44 | "f_index03L", 45 | "f_index02R", 46 | "f_index03R", 47 | "thumb02L", 48 | "f_pinky03L", 49 | "upper_armL", 50 | "upper_armR", 51 | "thumb02R", 52 | "toeL", 53 | "heel02L", 54 | "toeR", 55 | "heel02R", 56 | ]; 57 | 58 | export const eyebrowBoneNames = ["eyebrow_L", "eyebrow_R"]; 59 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap"); 2 | 3 | :root { 4 | font-family: "Geist", sans-serif; 5 | 6 | font-optical-sizing: auto; 7 | font-style: normal; 8 | line-height: 1.5; 9 | scroll-behavior: smooth; 10 | color-scheme: light dark; 11 | color: #eae5ec; 12 | background-color: var(--backgroundColor); 13 | 14 | font-synthesis: none; 15 | text-rendering: optimizeLegibility; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; 18 | user-select: none; 19 | -webkit-user-select: none; 20 | -moz-user-select: none; 21 | --accentColor: #c2a4ff; 22 | --backgroundColor: #0b080c; 23 | --vh: 100vh; 24 | --vh: 100svh; 25 | } 26 | h1, 27 | h2, 28 | h3, 29 | h4, 30 | h5, 31 | h6 { 32 | font-family: "Geist", sans-serif; 33 | } 34 | body { 35 | overflow: hidden; 36 | } 37 | a { 38 | color: inherit; 39 | text-decoration: inherit; 40 | } 41 | a:hover { 42 | color: var(--accentColor); 43 | } 44 | main { 45 | opacity: 1; 46 | transition: 0.3s; 47 | } 48 | .main-active { 49 | opacity: 0; 50 | animation: fadeIn 1s 1; 51 | animation-fill-mode: forwards; 52 | } 53 | @keyframes fadeIn { 54 | 100% { 55 | opacity: 1; 56 | } 57 | } 58 | body { 59 | margin: 0; 60 | height: auto; 61 | background-color: #000; 62 | flex-grow: 1; 63 | --cWidth: calc(100% - 30px); 64 | --cMaxWidth: 1920px; 65 | max-width: 100vw; 66 | overflow-x: hidden; 67 | } 68 | .main-body { 69 | max-width: 100vw; 70 | overflow-x: hidden; 71 | } 72 | 73 | .container-main { 74 | width: 100%; 75 | margin: auto; 76 | position: relative; 77 | } 78 | .container1 { 79 | width: var(--cWidth); 80 | height: var(--vh); 81 | margin: auto; 82 | position: relative; 83 | } 84 | .split-line { 85 | overflow: hidden; 86 | } 87 | .split-h2 { 88 | overflow: hidden; 89 | display: flex; 90 | white-space: nowrap; 91 | flex-wrap: nowrap; 92 | } 93 | 94 | .techstack { 95 | width: 100%; 96 | position: relative; 97 | height: var(--vh); 98 | margin: auto; 99 | margin-top: 50px; 100 | margin-bottom: -100px; 101 | } 102 | 103 | .techstack h2 { 104 | font-size: 80px; 105 | text-align: center; 106 | position: absolute; 107 | width: 100%; 108 | top: 120px; 109 | left: 0; 110 | font-weight: 400; 111 | text-transform: uppercase; 112 | } 113 | 114 | @media screen and (min-width: 768px) { 115 | body { 116 | --cWidth: 94%; 117 | } 118 | } 119 | @media screen and (max-width: 900px) { 120 | .techstack h2 { 121 | font-size: 40px; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | import "./index.css"; 5 | 6 | createRoot(document.getElementById("root")!).render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | let endListener = false; 2 | 3 | clickElement.addEventListener("click", () => { 4 | if (endListener) { 5 | endListener = false; 6 | } else { 7 | endListener = true; 8 | } 9 | }); 10 | 11 | video.addEventListener("ended", () => { 12 | if (endListener) return console.log("listener ended"); 13 | 14 | //endListener =false then below code runs 15 | 16 | console.log("Video has ended"); 17 | }); 18 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"] 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.app.tsbuildinfo: -------------------------------------------------------------------------------- 1 | {"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/about.tsx","./src/components/career.tsx","./src/components/contact.tsx","./src/components/cursor.tsx","./src/components/hoverlinks.tsx","./src/components/landing.tsx","./src/components/loading.tsx","./src/components/maincontainer.tsx","./src/components/navbar.tsx","./src/components/socialicons.tsx","./src/components/techstack.tsx","./src/components/whatido.tsx","./src/components/work.tsx","./src/components/workimage.tsx","./src/components/character/scene.tsx","./src/components/character/exports.ts","./src/components/character/index.tsx","./src/components/character/utils/animationutils.ts","./src/components/character/utils/character.ts","./src/components/character/utils/decrypt.ts","./src/components/character/utils/lighting.ts","./src/components/character/utils/mouseutils.ts","./src/components/character/utils/resizeutils.ts","./src/components/utils/gsapscroll.ts","./src/components/utils/initialfx.ts","./src/components/utils/splittext.ts","./src/context/loadingprovider.tsx","./src/data/bonedata.ts"],"version":"5.6.2"} -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["ES2023"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | 8 | /* Bundler mode */ 9 | "moduleResolution": "bundler", 10 | "allowImportingTsExtensions": true, 11 | "isolatedModules": true, 12 | "moduleDetection": "force", 13 | "noEmit": true, 14 | 15 | /* Linting */ 16 | "strict": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noFallthroughCasesInSwitch": true 20 | }, 21 | "include": ["vite.config.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.node.tsbuildinfo: -------------------------------------------------------------------------------- 1 | {"root":["./vite.config.ts"],"version":"5.6.2"} -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }); 8 | --------------------------------------------------------------------------------