Implement a full portfolio in vite

This commit is contained in:
TropiiDev 2025-02-03 20:29:28 -05:00
parent e0a9a7db81
commit 31745cfab3
97 changed files with 12073 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

41
eslint.config.js Normal file
View File

@ -0,0 +1,41 @@
import js from '@eslint/js'
import globals from 'globals'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import {ReactThreeFiber} from "@react-three/fiber";
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
settings: { react: { version: '18.3' } },
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
'@react-three': ReactThreeFiber
},
rules: {
...js.configs.recommended.rules,
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react/jsx-no-target-blank': 'off',
'react/no-unknown-property': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

8484
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

42
package.json Normal file
View File

@ -0,0 +1,42 @@
{
"name": "threejs_portfolio",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@emailjs/browser": "^4.4.1",
"@gsap/react": "^2.1.2",
"@react-three/drei": "^9.121.4",
"@react-three/fiber": "^8.17.14",
"gsap": "^3.12.7",
"leva": "^0.10.0",
"maath": "^0.10.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-globe.gl": "^2.20.0",
"react-responsive": "^10.0.0",
"three": "^0.173.0"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
"@react-three/eslint-plugin": "^0.1.2",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"eslint": "^9.17.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.14.0",
"postcss": "^8.5.1",
"tailwindcss": "^3.4.17",
"vite": "^6.0.5"
}
}

BIN
public/assets/arrow-up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

BIN
public/assets/blob-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 KiB

3
public/assets/close.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="#fff" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>

After

Width:  |  Height:  |  Size: 213 B

3
public/assets/copy.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M27 4.5H11C10.7348 4.5 10.4804 4.60536 10.2929 4.79289C10.1054 4.98043 10 5.23478 10 5.5V10.5H5C4.73478 10.5 4.48043 10.6054 4.29289 10.7929C4.10536 10.9804 4 11.2348 4 11.5V27.5C4 27.7652 4.10536 28.0196 4.29289 28.2071C4.48043 28.3946 4.73478 28.5 5 28.5H21C21.2652 28.5 21.5196 28.3946 21.7071 28.2071C21.8946 28.0196 22 27.7652 22 27.5V22.5H27C27.2652 22.5 27.5196 22.3946 27.7071 22.2071C27.8946 22.0196 28 21.7652 28 21.5V5.5C28 5.23478 27.8946 4.98043 27.7071 4.79289C27.5196 4.60536 27.2652 4.5 27 4.5ZM20 26.5H6V12.5H20V26.5ZM26 20.5H22V11.5C22 11.2348 21.8946 10.9804 21.7071 10.7929C21.5196 10.6054 21.2652 10.5 21 10.5H12V6.5H26V20.5Z" fill="#E4E4E6" fill-opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 796 B

BIN
public/assets/cybermano.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

19
public/assets/figma.svg Normal file
View File

@ -0,0 +1,19 @@
<svg width="44" height="45" viewBox="0 0 44 45" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4781_65534)">
<g clip-path="url(#clip1_4781_65534)">
<path d="M21.75 22.17C21.75 19.408 23.934 17.17 26.625 17.17C29.319 17.17 31.5 19.406 31.5 22.17C31.5 24.932 29.319 27.17 26.625 27.17C23.935 27.17 21.75 24.934 21.75 22.17Z" fill="#1ABCFE"/>
<path d="M12 32.17C12 29.409 14.184 27.17 16.875 27.17H21.75V32.17C21.75 34.93 19.566 37.17 16.875 37.17C14.184 37.17 12 34.93 12 32.17Z" fill="#0ACF83"/>
<path d="M21.75 7.17004V17.17H26.625C29.319 17.17 31.5 14.931 31.5 12.17C31.5 9.41004 29.319 7.17004 26.625 7.17004H21.75Z" fill="#FF7262"/>
<path d="M12 12.17C12 14.931 14.184 17.17 16.875 17.17H21.75V7.17004H16.875C14.184 7.17004 12 9.41004 12 12.17Z" fill="#F24E1E"/>
<path d="M12 22.17C12 24.932 14.184 27.17 16.875 27.17H21.75V17.17H16.875C14.184 17.17 12 19.407 12 22.17Z" fill="#A259FF"/>
</g>
</g>
<defs>
<clipPath id="clip0_4781_65534">
<rect width="44" height="44" fill="white" transform="translate(0 0.170044)"/>
</clipPath>
<clipPath id="clip1_4781_65534">
<rect width="44" height="44" fill="white" transform="translate(0 0.170044)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/assets/framer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

15
public/assets/framer.svg Normal file
View File

@ -0,0 +1,15 @@
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4781_65519)">
<g clip-path="url(#clip1_4781_65519)">
<path d="M12 7H32V17H22L12 7ZM12 17H22L32 27H12V17ZM12 27H22V37L12 27Z" fill="#E6E6E6"/>
</g>
</g>
<defs>
<clipPath id="clip0_4781_65519">
<rect width="44" height="44" fill="white"/>
</clipPath>
<clipPath id="clip1_4781_65519">
<rect width="44" height="44" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 473 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.0 KiB

1
public/assets/github.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512" fill="#fff"><path d="M256 32C132.3 32 32 134.9 32 261.7c0 101.5 64.2 187.5 153.2 217.9a17.56 17.56 0 003.8.4c8.3 0 11.5-6.1 11.5-11.4 0-5.5-.2-19.9-.3-39.1a102.4 102.4 0 01-22.6 2.7c-43.1 0-52.9-33.5-52.9-33.5-10.2-26.5-24.9-33.6-24.9-33.6-19.5-13.7-.1-14.1 1.4-14.1h.1c22.5 2 34.3 23.8 34.3 23.8 11.2 19.6 26.2 25.1 39.6 25.1a63 63 0 0025.6-6c2-14.8 7.8-24.9 14.2-30.7-49.7-5.8-102-25.5-102-113.5 0-25.1 8.7-45.6 23-61.6-2.3-5.8-10-29.2 2.2-60.8a18.64 18.64 0 015-.5c8.1 0 26.4 3.1 56.6 24.1a208.21 208.21 0 01112.2 0c30.2-21 48.5-24.1 56.6-24.1a18.64 18.64 0 015 .5c12.2 31.6 4.5 55 2.2 60.8 14.3 16.1 23 36.6 23 61.6 0 88.2-52.4 107.6-102.3 113.3 8 7.1 15.2 21.1 15.2 42.5 0 30.7-.3 55.5-.3 63 0 5.4 3.1 11.5 11.4 11.5a19.35 19.35 0 004-.4C415.9 449.2 480 363.1 480 261.7 480 134.9 379.7 32 256 32z"/></svg>

After

Width:  |  Height:  |  Size: 888 B

BIN
public/assets/grid1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
public/assets/grid2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

BIN
public/assets/grid3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
public/assets/grid4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512" fill="#fff"><path d="M349.33 69.33a93.62 93.62 0 0193.34 93.34v186.66a93.62 93.62 0 01-93.34 93.34H162.67a93.62 93.62 0 01-93.34-93.34V162.67a93.62 93.62 0 0193.34-93.34h186.66m0-37.33H162.67C90.8 32 32 90.8 32 162.67v186.66C32 421.2 90.8 480 162.67 480h186.66C421.2 480 480 421.2 480 349.33V162.67C480 90.8 421.2 32 349.33 32z"/><path d="M377.33 162.67a28 28 0 1128-28 27.94 27.94 0 01-28 28zM256 181.33A74.67 74.67 0 11181.33 256 74.75 74.75 0 01256 181.33m0-37.33a112 112 0 10112 112 112 112 0 00-112-112z"/></svg>

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

3
public/assets/menu.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="#fff" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 5.25h16.5m-16.5 4.5h16.5m-16.5 4.5h16.5m-16.5 4.5h16.5" />
</svg>

After

Width:  |  Height:  |  Size: 252 B

15
public/assets/notion.svg Normal file
View File

@ -0,0 +1,15 @@
<svg width="44" height="45" viewBox="0 0 44 45" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4781_65553)">
<g clip-path="url(#clip1_4781_65553)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.405 7.40792L9.805 8.63492C8.465 8.74992 8 9.62492 8 10.6749V28.8719C8 29.6889 8.29 30.3879 8.99 31.3219L12.892 36.3959C13.533 37.2129 14.116 37.3879 15.34 37.3299L34.617 36.1629C36.247 36.0469 36.714 35.2879 36.714 34.0049V13.5319C36.714 12.8689 36.452 12.6779 35.681 12.1119L30.25 8.28292C28.968 7.35092 28.444 7.23292 26.405 7.40792ZM15.775 13.1979C14.202 13.3029 13.845 13.3279 12.951 12.5999L10.678 10.7919C10.448 10.5579 10.563 10.2669 11.145 10.2079L27.103 9.04192C28.443 8.92492 29.141 9.39192 29.665 9.79992L32.402 11.7829C32.519 11.8429 32.81 12.1909 32.46 12.1909L15.98 13.1829L15.776 13.1969L15.775 13.1979ZM13.942 33.8299V16.4499C13.942 15.6909 14.175 15.3409 14.872 15.2829L33.8 14.1739C34.442 14.1159 34.732 14.5239 34.732 15.2819V32.5459C34.732 33.3049 34.615 33.9469 33.567 34.0049L15.454 35.0549C14.406 35.1129 13.941 34.7639 13.941 33.8299H13.942ZM31.822 17.3819C31.938 17.9069 31.822 18.4319 31.297 18.4919L30.424 18.6649V31.4969C29.666 31.9049 28.968 32.1379 28.384 32.1379C27.453 32.1379 27.22 31.8459 26.522 30.9719L20.812 21.9899V30.6799L22.619 31.0889C22.619 31.0889 22.619 32.1389 21.162 32.1389L17.145 32.3719C17.028 32.1379 17.145 31.5549 17.552 31.4389L18.601 31.1489V19.6589L17.145 19.5399C17.028 19.0149 17.319 18.2569 18.135 18.1979L22.445 17.9079L28.385 27.0059V18.9569L26.871 18.7829C26.754 18.1399 27.22 17.6729 27.801 17.6159L31.821 17.3819H31.822Z" fill="#E6E6E6"/>
</g>
</g>
<defs>
<clipPath id="clip0_4781_65553">
<rect width="44" height="44" fill="white" transform="translate(0 0.339966)"/>
</clipPath>
<clipPath id="clip1_4781_65553">
<rect width="44" height="44" fill="white" transform="translate(0 0.339966)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="64" height="64">
<path d="M0 0 C4.82785509 3.01421641 8.87433861 6.44155841 11.0625 11.8125 C11.7225 11.8125 12.3825 11.8125 13.0625 11.8125 C15.77922996 18.02216849 16.52948154 23.06718718 16.4375 29.8125 C16.44910156 30.66328125 16.46070312 31.5140625 16.47265625 32.390625 C16.44598889 38.18633165 15.3856085 42.50253772 13.0625 47.8125 C12.4025 47.8125 11.7425 47.8125 11.0625 47.8125 C10.835625 48.369375 10.60875 48.92625 10.375 49.5 C6.76412204 55.86202308 0.99050704 59.82728161 -5.9375 61.8125 C-15.38971342 63.0516309 -25.77255437 63.43499251 -34.08984375 58.2421875 C-37.98623002 55.22722476 -41.05552836 52.43188494 -42.9375 47.8125 C-43.5975 47.8125 -44.2575 47.8125 -44.9375 47.8125 C-47.65422996 41.60283151 -48.40448154 36.55781282 -48.3125 29.8125 C-48.32990234 28.53632812 -48.32990234 28.53632812 -48.34765625 27.234375 C-48.32098889 21.43866835 -47.2606085 17.12246228 -44.9375 11.8125 C-44.2775 11.8125 -43.6175 11.8125 -42.9375 11.8125 C-42.710625 11.255625 -42.48375 10.69875 -42.25 10.125 C-34.34116899 -3.80960703 -13.70577335 -6.49220843 0 0 Z " fill="#001F2B" transform="translate(47.9375,2.1875)"/>
<path d="M0 0 C5.03918835 2.20866553 7.74738375 5.83955285 9.76171875 10.921875 C12.18051172 19.49915503 11.55261357 25.95451487 7.38671875 33.8359375 C6 36 6 36 4.375 37.62109375 C2.46105837 39.54047272 2.35500625 41.37295374 2 44 C1.34 44 0.68 44 0 44 C-0.20625 43.195625 -0.4125 42.39125 -0.625 41.5625 C-2.15695414 37.5933461 -4.21700224 34.50942714 -6.66015625 31.05078125 C-9.91074104 26.07539636 -9.66962423 20.35379594 -8.5390625 14.69140625 C-6.83400814 8.90425117 -3.86115846 4.58590665 0 0 Z " fill="#00E662" transform="translate(31,10)"/>
<path d="M0 0 C0.66 0 1.32 0 2 0 C2.22730289 3.54071421 2.42844022 7.08245666 2.625 10.625 C2.68945312 11.62402344 2.75390625 12.62304688 2.8203125 13.65234375 C3.09829231 18.89921265 3.19579577 23.08879266 1 28 C0.34 28 -0.32 28 -1 28 C-2.72393487 22.861348 -3.17753614 18.92233861 -2.75 13.4375 C-2.66234375 12.17808594 -2.5746875 10.91867188 -2.484375 9.62109375 C-2.02423877 6.18120436 -1.2181556 3.24009419 0 0 Z " fill="#00AC52" transform="translate(32,26)"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

5
public/assets/python.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

1
public/assets/react.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
public/assets/review1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/assets/review2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
public/assets/review3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/assets/review4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

5
public/assets/sentry.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

BIN
public/assets/star.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

BIN
public/assets/terminal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

3
public/assets/tick.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="23" height="18" viewBox="0 0 23 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.8402 2.49999L22.0446 3.29548L8.85534 16.4848C7.83023 17.5099 6.16817 17.5099 5.14304 16.4848L0.953698 12.2955L0.158203 11.5L1.7492 9.909L2.54469 10.7045L6.73403 14.8939C6.88047 15.0403 7.11791 15.0403 7.26435 14.8939L20.4537 1.70449L21.2492 0.908997L22.8402 2.49999Z" fill="#00FF80"/>
</svg>

After

Width:  |  Height:  |  Size: 445 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512" fill="#fff"><path d="M496 109.5a201.8 201.8 0 01-56.55 15.3 97.51 97.51 0 0043.33-53.6 197.74 197.74 0 01-62.56 23.5A99.14 99.14 0 00348.31 64c-54.42 0-98.46 43.4-98.46 96.9a93.21 93.21 0 002.54 22.1 280.7 280.7 0 01-203-101.3A95.69 95.69 0 0036 130.4c0 33.6 17.53 63.3 44 80.7A97.5 97.5 0 0135.22 199v1.2c0 47 34 86.1 79 95a100.76 100.76 0 01-25.94 3.4 94.38 94.38 0 01-18.51-1.8c12.51 38.5 48.92 66.5 92.05 67.3A199.59 199.59 0 0139.5 405.6a203 203 0 01-23.5-1.4A278.68 278.68 0 00166.74 448c181.36 0 280.44-147.7 280.44-275.8 0-4.2-.11-8.4-.31-12.5A198.48 198.48 0 00496 109.5z"/></svg>

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/models/computer.glb Normal file

Binary file not shown.

BIN
public/models/cube.glb Normal file

Binary file not shown.

BIN
public/models/desk.glb Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/models/react.glb Normal file

Binary file not shown.

BIN
public/textures/cube.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

BIN
public/textures/rings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

25
src/App.jsx Normal file
View File

@ -0,0 +1,25 @@
import React from 'react'
import Navbar from "./sections/Navbar.jsx";
import Hero from "./sections/Hero.jsx";
import About from "./sections/About.jsx";
import Projects from "./sections/Projects.jsx";
import Clients from "./sections/Clients.jsx";
import Contact from "./sections/Contact.jsx";
import Footer from "./sections/Footer.jsx";
import Experience from "./sections/Experience.jsx";
const App = () => {
return (
<main className="max-w-7xl mx-auto relative">
<Navbar />
<Hero />
<About />
<Projects />
<Clients />
<Experience />
<Contact />
<Footer />
</main>
)
}
export default App

16
src/components/Button.jsx Normal file
View File

@ -0,0 +1,16 @@
import React from 'react'
const Button = ({ name, isBeam = false, containerClass }) => {
return (
<button className={`btn ${containerClass}`}>
{isBeam && (
<span className="relative flex h-3 w-3">
<span className="btn-ping" />
<span className="btn-ping_dot" />
</span>
)}
{name}
</button>
)
}
export default Button

View File

@ -0,0 +1,23 @@
import { Html, useProgress } from '@react-three/drei'
const CanvasLoader = () => {
const { progress } = useProgress()
return (
<Html
as="div"
center
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
}}
>
<span className="canvas-loader" />
<p style={{ fontSize: 14, color: '#F1F1F1', fontWeight: 800, marginTop: 40}}>
{progress !== 0 ? `${progress.toFixed(2)}%` : 'Loading...'}
</p>
</Html>
)
}
export default CanvasLoader

53
src/components/Cube.jsx Normal file
View File

@ -0,0 +1,53 @@
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
*/
import gsap from 'gsap';
import { useGSAP } from '@gsap/react';
import { useRef, useState } from 'react';
import { Float, useGLTF, useTexture } from '@react-three/drei';
const Cube = ({ ...props }) => {
const { nodes } = useGLTF('models/cube.glb');
const texture = useTexture('textures/cube.png');
const cubeRef = useRef();
const [hovered, setHovered] = useState(false);
useGSAP(() => {
gsap
.timeline({
repeat: -1,
repeatDelay: 0.5,
})
.to(cubeRef.current.rotation, {
y: hovered ? '+=2' : `+=${Math.PI * 2}`,
x: hovered ? '+=2' : `-=${Math.PI * 2}`,
duration: 2.5,
stagger: {
each: 0.15,
},
});
});
return (
<Float floatIntensity={2}>
<group position={[9, -4, 0]} rotation={[2.6, 0.8, -1.8]} scale={0.74} dispose={null} {...props}>
<mesh
ref={cubeRef}
castShadow
receiveShadow
geometry={nodes.Cube.geometry}
material={nodes.Cube.material}
onPointerEnter={() => setHovered(true)}>
<meshMatcapMaterial matcap={texture} toneMapped={false} />
</mesh>
</group>
</Float>
);
};
useGLTF.preload('models/cube.glb');
export default Cube;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
*/
import {useEffect, useRef} from 'react'
import {useAnimations, useFBX, useGLTF} from '@react-three/drei'
// eslint-disable-next-line react/prop-types
const Developer = ({ animationName = 'idle', ...props }) => {
const group = useRef();
const { nodes, materials } = useGLTF('/models/human/developer.glb');
const { animations: idleAnimation } = useFBX('/models/animations/idle.fbx');
const { animations: saluteAnimation } = useFBX('/models/animations/salute.fbx');
const { animations: clappingAnimation } = useFBX('/models/animations/clapping.fbx');
const { animations: victoryAnimation } = useFBX('/models/animations/victory.fbx');
idleAnimation[0].name = 'idle';
saluteAnimation[0].name = 'salute';
clappingAnimation[0].name = 'clapping';
victoryAnimation[0].name = 'victory';
const { actions } = useAnimations([idleAnimation[0], saluteAnimation[0], clappingAnimation[0], victoryAnimation[0]], group);
useEffect(() => {
actions[animationName].reset().fadeIn(0.5).play();
return () => actions[animationName].fadeOut
}, [actions, animationName]);
return (
<group {...props} dispose={null} ref={group}>
<primitive object={nodes.Hips} />
<skinnedMesh
name="EyeLeft"
geometry={nodes.EyeLeft.geometry}
material={materials.Wolf3D_Eye}
skeleton={nodes.EyeLeft.skeleton}
morphTargetDictionary={nodes.EyeLeft.morphTargetDictionary}
morphTargetInfluences={nodes.EyeLeft.morphTargetInfluences}
/>
<skinnedMesh
name="EyeRight"
geometry={nodes.EyeRight.geometry}
material={materials.Wolf3D_Eye}
skeleton={nodes.EyeRight.skeleton}
morphTargetDictionary={nodes.EyeRight.morphTargetDictionary}
morphTargetInfluences={nodes.EyeRight.morphTargetInfluences}
/>
<skinnedMesh
name="Wolf3D_Head"
geometry={nodes.Wolf3D_Head.geometry}
material={materials.Wolf3D_Skin}
skeleton={nodes.Wolf3D_Head.skeleton}
morphTargetDictionary={nodes.Wolf3D_Head.morphTargetDictionary}
morphTargetInfluences={nodes.Wolf3D_Head.morphTargetInfluences}
/>
<skinnedMesh
name="Wolf3D_Teeth"
geometry={nodes.Wolf3D_Teeth.geometry}
material={materials.Wolf3D_Teeth}
skeleton={nodes.Wolf3D_Teeth.skeleton}
morphTargetDictionary={nodes.Wolf3D_Teeth.morphTargetDictionary}
morphTargetInfluences={nodes.Wolf3D_Teeth.morphTargetInfluences}
/>
<skinnedMesh
geometry={nodes.Wolf3D_Hair.geometry}
material={materials.Wolf3D_Hair}
skeleton={nodes.Wolf3D_Hair.skeleton}
/>
<skinnedMesh
geometry={nodes.Wolf3D_Glasses.geometry}
material={materials.Wolf3D_Glasses}
skeleton={nodes.Wolf3D_Glasses.skeleton}
/>
<skinnedMesh
geometry={nodes.Wolf3D_Body.geometry}
material={materials.Wolf3D_Body}
skeleton={nodes.Wolf3D_Body.skeleton}
/>
<skinnedMesh
geometry={nodes.Wolf3D_Outfit_Bottom.geometry}
material={materials.Wolf3D_Outfit_Bottom}
skeleton={nodes.Wolf3D_Outfit_Bottom.skeleton}
/>
<skinnedMesh
geometry={nodes.Wolf3D_Outfit_Footwear.geometry}
material={materials.Wolf3D_Outfit_Footwear}
skeleton={nodes.Wolf3D_Outfit_Footwear.skeleton}
/>
<skinnedMesh
geometry={nodes.Wolf3D_Outfit_Top.geometry}
material={materials.Wolf3D_Outfit_Top}
skeleton={nodes.Wolf3D_Outfit_Top.skeleton}
/>
</group>
)
}
useGLTF.preload('/models/human/developer.glb')
export default Developer;

View File

@ -0,0 +1,43 @@
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
Command: npx gltfjsx@6.5.0 hacker-room-new.glb -T
Files: hacker-room-new.glb [34.62MB] > /Users/hsuwinlat/Desktop/jsm pj/threejscc-portfolio/public/models/hacker-room-new-transformed.glb [2.56MB] (93%)
*/
import { useGLTF, useTexture } from '@react-three/drei';
const HackerRoom = (props) => {
const { nodes, materials } = useGLTF('/models/hacker-room.glb');
const monitorTexture = useTexture('textures/desk/monitor.png');
const screenImages = useTexture('textures/desk/screen.png');
return (
<group {...props} dispose={null}>
<mesh geometry={nodes.screen_screens_0.geometry} material={materials.screens}>
<meshMatcapMaterial map={screenImages} />
</mesh>
<mesh geometry={nodes.screen_glass_glass_0.geometry} material={materials.glass} />
<mesh geometry={nodes.table_table_mat_0_1.geometry} material={materials.table_mat} />
<mesh geometry={nodes.table_table_mat_0_2.geometry} material={materials.computer_mat}>
<meshMatcapMaterial map={monitorTexture} />
</mesh>
<mesh geometry={nodes.table_table_mat_0_3.geometry} material={materials.server_mat} />
<mesh geometry={nodes.table_table_mat_0_4.geometry} material={materials.vhsPlayer_mat} />
<mesh geometry={nodes.table_table_mat_0_5.geometry} material={materials.stand_mat} />
<mesh geometry={nodes.table_table_mat_0_6.geometry} material={materials.mat_mat} />
<mesh geometry={nodes.table_table_mat_0_7.geometry} material={materials.arm_mat} />
<mesh geometry={nodes.table_table_mat_0_8.geometry} material={materials.tv_mat}>
<meshMatcapMaterial map={monitorTexture} />
</mesh>
<mesh geometry={nodes.table_table_mat_0_9.geometry} material={materials.cables_mat} />
<mesh geometry={nodes.table_table_mat_0_10.geometry} material={materials.props_mat} />
<mesh geometry={nodes.table_table_mat_0_11.geometry} material={materials.ground_mat} />
<mesh geometry={nodes.table_table_mat_0_12.geometry} material={materials.key_mat} />
</group>
);
}
useGLTF.preload('/models/hacker-room.glb');
export default HackerRoom;

View File

@ -0,0 +1,20 @@
import {useRef} from "react";
import {useFrame} from "@react-three/fiber";
import {easing} from "maath";
const HeroCamera = ({ children, isMobile }) => {
const groupRef = useRef();
useFrame((state, delta) => {
easing.damp3(state.camera.position, [0, 0, 20], 0.25, delta)
if (!isMobile) {
easing.dampE(groupRef.current.rotation, [-state.pointer.y / 3, -state.pointer.x / 5, 0], 0.25, delta)
}
});
return (
<group ref={groupRef} scale={isMobile ? 1 : 1.3}>{children}</group>
)
}
export default HeroCamera

View File

@ -0,0 +1,24 @@
import React, { useRef } from 'react'
import {Float, useGLTF} from '@react-three/drei'
const ReactLogo = (props) => {
const { nodes, materials } = useGLTF('/models/react.glb')
return (
<Float floatIntensity={1}>
<group position={[8, 8, 0]} scale={0.4} {...props} dispose={null}>
<mesh
geometry={nodes['React-Logo_Material002_0'].geometry}
material={materials['Material.002']}
position={[0, 0.79, 0.181]}
rotation={[0, 0, -Math.PI / 2]}
scale={[0.39, 0.39, 0.5]}
/>
</group>
</Float>
)
}
useGLTF.preload('/models/react.glb')
export default ReactLogo

60
src/components/Ring.jsx Normal file
View File

@ -0,0 +1,60 @@
import { useGSAP } from '@gsap/react';
import { Center, useTexture } from '@react-three/drei';
import gsap from 'gsap';
import { useCallback, useRef } from 'react';
const Rings = ({ position }) => {
const refList = useRef([]);
const getRef = useCallback((mesh) => {
if (mesh && !refList.current.includes(mesh)) {
refList.current.push(mesh);
}
}, []);
const texture = useTexture('textures/rings.png');
useGSAP(
() => {
if (refList.current.length === 0) return;
refList.current.forEach((r) => {
r.position.set(position[0], position[1], position[2]);
});
gsap
.timeline({
repeat: -1,
repeatDelay: 0.5,
})
.to(
refList.current.map((r) => r.rotation),
{
y: `+=${Math.PI * 2}`,
x: `-=${Math.PI * 2}`,
duration: 2.5,
stagger: {
each: 0.15,
},
},
);
},
{
dependencies: position,
},
);
return (
<Center>
<group scale={0.5}>
{Array.from({ length: 4 }, (_, index) => (
<mesh key={index} ref={getRef}>
<torusGeometry args={[(index + 1) * 0.5, 0.1]}></torusGeometry>
<meshMatcapMaterial matcap={texture} toneMapped={false} />
</mesh>
))}
</group>
</Center>
);
};
export default Rings;

25
src/components/Target.jsx Normal file
View File

@ -0,0 +1,25 @@
import {useGLTF} from "@react-three/drei";
import {useRef} from "react";
import gsap from 'gsap';
import {useGSAP } from "@gsap/react";
const Target = (props) => {
const targetRef = useRef();
const { scene } = useGLTF("https://vazxmixjsiawhamofees.supabase.co/storage/v1/object/public/models/target-stand/model.gltf");
useGSAP(() => {
gsap.to(targetRef.current.position, {
y: targetRef.current.position.y + 0.5,
duration: 1.5,
repeat: -1,
yoyo: true,
})
});
return (
<mesh {...props} ref={targetRef} rotation={[0, Math.PI / 5, 0]} scale={1.5}>
<primitive object={scene} />
</mesh>
)
}
export default Target

165
src/constants/index.js Normal file
View File

@ -0,0 +1,165 @@
export const navLinks = [
{
id: 1,
name: 'Home',
href: '#home',
},
{
id: 2,
name: 'About',
href: '#about',
},
{
id: 3,
name: 'Work',
href: '#work',
},
{
id: 4,
name: 'Contact',
href: '#contact',
},
];
export const clientReviews = [
{
id: 1,
name: 'Cybermano',
position: 'Lead Developer for the "Aftermath: The Cold War" mod.',
img: 'assets/cybermano.png',
review:
'Overall, I must say that Colin was an amazing person who was a very helpful and kind person (for many years). He has accepted my plea for help many times before and has made work that even surprised me. He is very helpful and overall I can say I can recommended his work! Undeniably he is the best person and coder I have seen in a lifetime!',
}
];
export const myProjects = [
{
title: 'Aftermath - Discord Bot',
desc: 'Aftermath is a Discord Bot built with functionality in mind. It revolutionizes the way players interact with the support server for the mod they love! Built with scalability in mind, it grows as the users grow.',
subdesc:
'Built as a unique purpose built bot with Python, Pymongo, and Sentry, Aftermath is designed for optimal performance and scalability.',
href: 'https://git.fstropii.com/tropii/aftermath',
texture: '/textures/project/aftermath.mp4',
logo: '/assets/project1-logo.png',
logoStyle: {
backgroundColor: '#2A1816',
border: '0.2px solid #36201D',
boxShadow: '0px 0px 60px 0px #AA3C304D',
},
spotlight: '/assets/spotlight1.png',
tags: [
{
id: 1,
name: 'Python',
path: '/assets/python.svg',
},
{
id: 2,
name: 'Pymongo',
path: 'assets/pymongo.svg',
},
{
id: 3,
name: 'Sentry',
path: '/assets/sentry.svg',
},
],
},
{
title: 'Blob - Discord Bot',
desc: 'Blob is a powerful discord bot app that elevates the capabilities of users. As a swiss army knife of Discord Bots, Blob allows users to start and complete any task they want to in seconds.',
subdesc:
'With Blob, users can experience the future of moderation, where moderators can work together in making sure communities stay safe. Written using Python and Sentry.',
href: 'https://git.fstropii.com/tropii/blob',
texture: '/textures/project/blob.mp4',
logo: '/assets/blob-logo.png',
logoStyle: {
backgroundColor: '#13202F',
border: '0.2px solid #17293E',
boxShadow: '0px 0px 60px 0px #2F6DB54D',
},
spotlight: '/assets/spotlight2.png',
tags: [
{
id: 1,
name: 'Python',
path: '/assets/python.svg',
},
{
id: 2,
name: 'Pymongo',
path: 'assets/pymongo.svg',
},
{
id: 3,
name: 'Sentry',
path: '/assets/sentry.svg',
},
{
id: 4,
name: 'Discord.PY',
path: '/assets/discord-py.svg',
},
],
},
{
title: 'Horizon - Online Banking Platform',
desc: 'Horizon is a comprehensive online banking platform that offers users a centralized finance management dashboard. It allows users to connect multiple bank accounts, monitor real-time transactions, and seamlessly transfer money to other users.',
subdesc:
'Built with Next.js 14 Appwrite, Dwolla and Plaid, Horizon ensures a smooth and secure banking experience, tailored to meet the needs of modern consumers.',
href: 'https://bank.fstropii.com',
texture: '/textures/project/project4.mp4',
logo: '/assets/project-logo4.png',
logoStyle: {
backgroundColor: '#0E1F38',
border: '0.2px solid #0E2D58',
boxShadow: '0px 0px 60px 0px #2F67B64D',
},
spotlight: '/assets/spotlight4.png',
tags: [
{
id: 1,
name: 'React.js',
path: '/assets/react.svg',
},
{
id: 2,
name: 'TailwindCSS',
path: 'assets/tailwindcss.png',
},
{
id: 3,
name: 'TypeScript',
path: '/assets/typescript.png',
},
{
id: 4,
name: 'Framer Motion',
path: '/assets/framer.png',
},
],
},
];
export const calculateSizes = (isSmall, isMobile, isTablet) => {
return {
deskScale: isSmall ? 0.05 : isMobile ? 0.06 : 0.065,
deskPosition: isMobile ? [0.5, -4.5, 0] : [0.25, -5.5, 0],
cubePosition: isSmall ? [4, -5, 0] : isMobile ? [5, -5, 0] : isTablet ? [5, -5, 0] : [9, -5.5, 0],
reactLogoPosition: isSmall ? [3, 4, 0] : isMobile ? [5, 4, 0] : isTablet ? [5, 4, 0] : [12, 3, 0],
ringPosition: isSmall ? [-5, 7, 0] : isMobile ? [-10, 10, 0] : isTablet ? [-12, 10, 0] : [-24, 10, 0],
targetPosition: isSmall ? [-5, -10, -10] : isMobile ? [-9, -10, -10] : isTablet ? [-11, -7, -10] : [-13, -13, -10],
};
};
export const workExperiences = [
{
id: 1,
name: 'Freelance',
pos: 'Back-End Developer',
duration: '2022 - Present',
title: "Currently, my focus is on learning and exploring many different projects. Maximizing my time and productivity to give me the best shot at success!",
icon: '/assets/freelance.svg',
animation: 'victory',
},
];

197
src/index.css Normal file
View File

@ -0,0 +1,197 @@
@import url('https://fonts.cdnfonts.com/css/general-sans');
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
scroll-behavior: smooth;
}
body {
background: #010103;
font-family: 'General Sans', sans-serif;
}
@layer utilities {
.c-space {
@apply sm:px-10 px-5;
}
.head-text {
@apply sm:text-4xl text-3xl font-semibold text-gray_gradient;
}
.nav-ul {
@apply flex flex-col items-center gap-4 sm:flex-row md:gap-6 relative z-20;
}
.nav-li {
@apply text-neutral-400 hover:text-white font-generalsans max-sm:hover:bg-black-500 max-sm:w-full max-sm:rounded-md py-2 max-sm:px-5;
}
.nav-li_a {
@apply text-lg md:text-base hover:text-white transition-colors;
}
.nav-sidebar {
@apply absolute left-0 right-0 bg-black-200 backdrop-blur-sm transition-all duration-300 ease-in-out overflow-hidden z-20 mx-auto sm:hidden block;
}
.text-gray_gradient {
@apply bg-gradient-to-r from-[#BEC1CF] from-60% via-[#D5D8EA] via-60% to-[#D5D8EA] to-100% bg-clip-text text-transparent;
}
/* button component */
.btn {
@apply flex gap-4 items-center justify-center cursor-pointer p-3 rounded-md bg-black-300 transition-all active:scale-95 text-white mx-auto;
}
.btn-ping {
@apply animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75;
}
.btn-ping_dot {
@apply relative inline-flex rounded-full h-3 w-3 bg-green-500;
}
/* hero section */
.hero_tag {
@apply text-center xl:text-6xl md:text-5xl sm:text-4xl text-3xl font-generalsans font-black !leading-normal;
}
/* about section */
.grid-container {
@apply w-full h-full border border-black-300 bg-black-200 rounded-lg sm:p-7 p-4 flex flex-col gap-5;
}
.grid-headtext {
@apply text-xl font-semibold mb-2 text-white font-generalsans;
}
.grid-subtext {
@apply text-[#afb0b6] text-base font-generalsans;
}
.copy-container {
@apply cursor-pointer flex justify-center items-center gap-2;
}
/* projects section */
.arrow-btn {
@apply w-10 h-10 p-3 cursor-pointer active:scale-95 transition-all rounded-full arrow-gradient;
}
.tech-logo {
@apply w-10 h-10 rounded-md p-2 bg-neutral-100 bg-opacity-10 backdrop-filter backdrop-blur-lg flex justify-center items-center;
}
/* clients section */
.client-container {
@apply grid md:grid-cols-2 grid-cols-1 gap-5 mt-12;
}
.client-review {
@apply rounded-lg md:p-10 p-5 col-span-1 bg-black-300 bg-opacity-50;
}
.client-content {
@apply flex lg:flex-row flex-col justify-between lg:items-center items-start gap-5 mt-7;
}
/* work experience section */
.work-container {
@apply grid lg:grid-cols-3 grid-cols-1 gap-5 mt-12;
}
.work-canvas {
@apply col-span-1 rounded-lg bg-black-200 border border-black-300;
}
.work-content {
@apply col-span-2 rounded-lg bg-black-200 border border-black-300;
}
.work-content_container {
@apply grid grid-cols-[auto_1fr] items-start gap-5 transition-all ease-in-out duration-500 cursor-pointer hover:bg-black-300 rounded-lg sm:px-5 px-2.5;
}
.work-content_logo {
@apply rounded-3xl w-16 h-16 p-2 bg-black-600;
}
.work-content_bar {
@apply flex-1 w-0.5 mt-4 h-full bg-black-300 group-hover:bg-black-500 group-last:hidden;
}
/* contact section */
.contact-container {
@apply max-w-xl relative z-10 sm:px-10 px-5 mt-12;
}
.field-label {
@apply text-lg text-white-600;
}
.field-input {
@apply w-full bg-black-300 px-5 py-2 min-h-14 rounded-lg placeholder:text-white-500 text-lg text-white-800 shadow-black-200 shadow-2xl focus:outline-none;
}
.field-btn {
@apply bg-black-500 px-5 py-2 min-h-12 rounded-lg shadow-black-200 shadow-2xl flex justify-center items-center text-lg text-white gap-3;
}
.field-btn_arrow {
@apply w-2.5 h-2.5 object-contain invert brightness-0;
}
/* footer */
.social-icon {
@apply w-12 h-12 rounded-full flex justify-center items-center bg-black-300 border border-black-200;
}
}
.waving-hand {
animation-name: wave-animation;
animation-duration: 2.5s;
animation-iteration-count: infinite;
transform-origin: 70% 70%;
display: inline-block;
}
.arrow-gradient {
background-image: linear-gradient(
to right,
rgba(255, 255, 255, 0.1) 10%,
rgba(255, 255, 255, 0.000025) 50%,
rgba(255, 255, 255, 0.000025) 50%,
rgba(255, 255, 255, 0.025) 100%
);
}
@keyframes wave-animation {
0% {
transform: rotate(0deg);
}
15% {
transform: rotate(14deg);
}
30% {
transform: rotate(-8deg);
}
40% {
transform: rotate(14deg);
}
50% {
transform: rotate(-4deg);
}
60% {
transform: rotate(10deg);
}
70% {
transform: rotate(0deg);
}
100% {
transform: rotate(0deg);
}
}

10
src/main.jsx Normal file
View File

@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)

979
src/output.css Normal file
View File

@ -0,0 +1,979 @@
@import url('https://fonts.cdnfonts.com/css/general-sans');
*, ::before, ::after{
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
::backdrop{
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
/*
! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com
*/
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box;
/* 1 */
border-width: 0;
/* 2 */
border-style: solid;
/* 2 */
border-color: #e5e7eb;
/* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/
html,
:host {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-moz-tab-size: 4;
/* 3 */
-o-tab-size: 4;
tab-size: 4;
/* 3 */
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
/* 4 */
font-feature-settings: normal;
/* 5 */
font-variation-settings: normal;
/* 6 */
-webkit-tap-highlight-color: transparent;
/* 7 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0;
/* 1 */
line-height: inherit;
/* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0;
/* 1 */
color: inherit;
/* 2 */
border-top-width: 1px;
/* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-feature-settings: normal;
/* 2 */
font-variation-settings: normal;
/* 3 */
font-size: 1em;
/* 4 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0;
/* 1 */
border-color: inherit;
/* 2 */
border-collapse: collapse;
/* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-feature-settings: inherit;
/* 1 */
font-variation-settings: inherit;
/* 1 */
font-size: 100%;
/* 1 */
font-weight: inherit;
/* 1 */
line-height: inherit;
/* 1 */
letter-spacing: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
/* 2 */
padding: 0;
/* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
input:where([type='button']),
input:where([type='reset']),
input:where([type='submit']) {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
/* 2 */
background-image: none;
/* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Reset default styling for dialogs.
*/
dialog {
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
/* 1 */
vertical-align: middle;
/* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden]:where(:not([hidden="until-found"])) {
display: none;
}
.fixed{
position: fixed;
}
.absolute{
position: absolute;
}
.relative{
position: relative;
}
.inset-0{
inset: 0px;
}
.left-0{
left: 0px;
}
.right-0{
right: 0px;
}
.top-0{
top: 0px;
}
.z-50{
z-index: 50;
}
.mx-auto{
margin-left: auto;
margin-right: auto;
}
.mt-20{
margin-top: 5rem;
}
.flex{
display: flex;
}
.hidden{
display: none;
}
.h-6{
height: 1.5rem;
}
.h-full{
height: 100%;
}
.max-h-0{
max-height: 0px;
}
.max-h-screen{
max-height: 100vh;
}
.min-h-screen{
min-height: 100vh;
}
.w-6{
width: 1.5rem;
}
.w-full{
width: 100%;
}
.max-w-7xl{
max-width: 80rem;
}
.flex-col{
flex-direction: column;
}
.items-center{
align-items: center;
}
.justify-between{
justify-content: space-between;
}
.gap-3{
gap: 0.75rem;
}
.border{
border-width: 1px;
}
.border-2{
border-width: 2px;
}
.border-blue-500{
--tw-border-opacity: 1;
border-color: rgb(59 130 246 / var(--tw-border-opacity, 1));
}
.bg-black\/90{
background-color: rgb(0 0 0 / 0.9);
}
.p-5{
padding: 1.25rem;
}
.py-5{
padding-top: 1.25rem;
padding-bottom: 1.25rem;
}
.text-center{
text-align: center;
}
.font-generalsans{
font-family: General Sans, sans-serif;
}
.text-xl{
font-size: 1.25rem;
line-height: 1.75rem;
}
.text-2xl{
font-size: 1.5rem;
line-height: 2rem;
}
.font-bold{
font-weight: 700;
}
.font-medium{
font-weight: 500;
}
.text-neutral-400{
--tw-text-opacity: 1;
color: rgb(163 163 163 / var(--tw-text-opacity, 1));
}
.text-white{
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
}
.transition-colors{
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.c-space{
padding-left: 1.25rem;
padding-right: 1.25rem;
}
@media (min-width: 640px){
.c-space{
padding-left: 2.5rem;
padding-right: 2.5rem;
}
}
.nav-ul{
position: relative;
z-index: 20;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
@media (min-width: 640px){
.nav-ul{
flex-direction: row;
}
}
@media (min-width: 768px){
.nav-ul{
gap: 1.5rem;
}
}
.nav-li{
padding-top: 0.5rem;
padding-bottom: 0.5rem;
font-family: General Sans, sans-serif;
--tw-text-opacity: 1;
color: rgb(163 163 163 / var(--tw-text-opacity, 1));
}
.nav-li:hover{
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
}
@media not all and (min-width: 640px){
.nav-li{
width: 100%;
border-radius: 0.375rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.nav-li:hover{
--tw-bg-opacity: 1;
background-color: rgb(58 58 73 / var(--tw-bg-opacity, 1));
}
}
.nav-li_a{
font-size: 1.125rem;
line-height: 1.75rem;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.nav-li_a:hover{
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
}
@media (min-width: 768px){
.nav-li_a{
font-size: 1rem;
line-height: 1.5rem;
}
}
.nav-sidebar{
position: absolute;
left: 0px;
right: 0px;
z-index: 20;
margin-left: auto;
margin-right: auto;
display: block;
overflow: hidden;
--tw-bg-opacity: 1;
background-color: rgb(14 14 16 / var(--tw-bg-opacity, 1));
--tw-backdrop-blur: blur(4px);
-webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
transition-property: all;
transition-duration: 300ms;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
@media (min-width: 640px){
.nav-sidebar{
display: none;
}
}
.text-gray_gradient{
background-image: linear-gradient(to right, var(--tw-gradient-stops));
--tw-gradient-from: #BEC1CF var(--tw-gradient-from-position);
--tw-gradient-to: rgb(190 193 207 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
--tw-gradient-from-position: 60%;
--tw-gradient-to: rgb(213 216 234 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), #D5D8EA var(--tw-gradient-via-position), var(--tw-gradient-to);
--tw-gradient-via-position: 60%;
--tw-gradient-to: #D5D8EA var(--tw-gradient-to-position);
--tw-gradient-to-position: 100%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
/* button component */
/* hero section */
.hero_tag{
text-align: center;
font-family: General Sans, sans-serif;
font-size: 1.875rem;
line-height: 2.25rem;
font-weight: 900;
line-height: 1.5 !important;
}
@media (min-width: 640px){
.hero_tag{
font-size: 2.25rem;
line-height: 2.5rem;
}
}
@media (min-width: 768px){
.hero_tag{
font-size: 3rem;
line-height: 1;
}
}
@media (min-width: 1280px){
.hero_tag{
font-size: 3.75rem;
line-height: 1;
}
}
/* about section */
/* projects section */
/* clients section */
/* work experience section */
/* contact section */
/* footer */
* {
scroll-behavior: smooth;
}
body {
background: #010103;
font-family: 'General Sans', sans-serif;
}
.waving-hand {
animation-name: wave-animation;
animation-duration: 2.5s;
animation-iteration-count: infinite;
transform-origin: 70% 70%;
display: inline-block;
}
.arrow-gradient {
background-image: linear-gradient(
to right,
rgba(255, 255, 255, 0.1) 10%,
rgba(255, 255, 255, 0.000025) 50%,
rgba(255, 255, 255, 0.000025) 50%,
rgba(255, 255, 255, 0.025) 100%
);
}
@keyframes wave-animation {
0% {
transform: rotate(0deg);
}
15% {
transform: rotate(14deg);
}
30% {
transform: rotate(-8deg);
}
40% {
transform: rotate(14deg);
}
50% {
transform: rotate(-4deg);
}
60% {
transform: rotate(10deg);
}
70% {
transform: rotate(0deg);
}
100% {
transform: rotate(0deg);
}
}
.hover\:text-white:hover{
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
}
.focus\:outline-none:focus{
outline: 2px solid transparent;
outline-offset: 2px;
}
@media (min-width: 640px){
.sm\:mt-36{
margin-top: 9rem;
}
.sm\:flex{
display: flex;
}
.sm\:hidden{
display: none;
}
.sm\:text-3xl{
font-size: 1.875rem;
line-height: 2.25rem;
}
}

106
src/sections/About.jsx Normal file
View File

@ -0,0 +1,106 @@
import { useState } from 'react';
import Globe from 'react-globe.gl';
import Button from '../components/Button.jsx';
const About = () => {
const [hasCopied, setHasCopied] = useState(false);
const handleCopy = () => {
navigator.clipboard.writeText('tropii@fstropii.com');
setHasCopied(true);
setTimeout(() => {
setHasCopied(false);
}, 2000);
};
return (
<section className="c-space my-20" id="about">
<div className="grid xl:grid-cols-3 xl:grid-rows-6 md:grid-cols-2 grid-cols-1 gap-5 h-full">
<div className="col-span-1 xl:row-span-3">
<div className="grid-container">
<img src="/assets/grid1.png" alt="grid-1" className="w-full sm:h-[276px] h-fit object-contain" />
<div>
<p className="grid-headtext">Hi, Im Colin!</p>
<p className="grid-subtext">
With 5 years of experience, I have honed my skills in backend development, creating API&apos;s
and working with data structures.
</p>
</div>
</div>
</div>
<div className="col-span-1 xl:row-span-3">
<div className="grid-container">
<img src="/assets/grid2.png" alt="grid-2" className="w-full sm:h-[276px] h-fit object-contain" />
<div>
<p className="grid-headtext">Tech Stack</p>
<p className="grid-subtext">
I specialize in Python, with a main focus in Flask or Django for API building
</p>
</div>
</div>
</div>
<div className="col-span-1 xl:row-span-4">
<div className="grid-container">
<div className="rounded-3xl w-full sm:h-[326px] h-fit flex justify-center items-center">
<Globe
height={326}
width={326}
backgroundColor="rgba(0, 0, 0, 0)"
backgroundImageOpacity={0.5}
showAtmosphere
showGraticules
globeImageUrl="//unpkg.com/three-globe/example/img/earth-night.jpg"
bumpImageUrl="//unpkg.com/three-globe/example/img/earth-topology.png"
/>
</div>
<div>
<p className="grid-headtext">Im very flexible with time zone communications & locations</p>
<p className="grid-subtext">I&apos;m based in New Jersey, USA and open to remote work worldwide.</p>
<Button name="Contact Me" isBeam containerClass="w-full mt-10" />
</div>
</div>
</div>
<div className="xl:col-span-2 xl:row-span-3">
<div className="grid-container">
<img src="/assets/grid3.png" alt="grid-3" className="w-full sm:h-[266px] h-fit object-contain" />
<div>
<p className="grid-headtext">My Passion for Coding</p>
<p className="grid-subtext">
I love solving problems and building applications through code. Programming isn&apos;t just my
professionit&apos;s my passion. I enjoy exploring new technologies, and enhancing my skills.
</p>
</div>
</div>
</div>
<div className="xl:col-span-1 xl:row-span-2">
<div className="grid-container">
<img
src="/assets/grid4.png"
alt="grid-4"
className="w-full md:h-[126px] sm:h-[276px] h-fit object-cover sm:object-top"
/>
<div className="space-y-2">
<p className="grid-subtext text-center">Contact me</p>
<div className="copy-container" onClick={handleCopy}>
<img src={hasCopied ? 'assets/tick.svg' : 'assets/copy.svg'} alt="copy" />
<p className="lg:text-2xl md:text-xl font-medium text-gray_gradient text-white">tropii@fstropii.com</p>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default About;

35
src/sections/Clients.jsx Normal file
View File

@ -0,0 +1,35 @@
import {clientReviews} from "../constants/index.js";
const Clients = () => {
return (
<section className="c-space my-20">
<h3 className="head-text">Hear from My Clients</h3>
<div className="client-container">
{clientReviews.map(({id, name, review, img, position}) => (
<div key={id} className="client-review">
<p className="text-white font-light">{review}</p>
<div className="client-content">
<div className="flex gap-3">
<img src={img} alt={name} className="w-12 h-12 rounded-full" />
<div className="flex flex-col">
<p className="font-semibold text-white-800">{name}</p>
<p className="text-white-500 md:text-base text-sm">{position}</p>
</div>
</div>
<div className="flex self-end items-center gap-2">
{Array.from({ length: 5 }).map((_, index) => (
<img key={index} src="/assets/star.png" alt="star" className="w-5 h-5"/>
))}
</div>
</div>
</div>
))}
</div>
</section>
)
}
export default Clients

118
src/sections/Contact.jsx Normal file
View File

@ -0,0 +1,118 @@
import {useRef, useState} from 'react'
import emailjs from '@emailjs/browser';
const Contact = () => {
const formRef = useRef();
const [loading, setLoading] = useState(false);
const [form, setForm] = useState({
name: '',
email: '',
message: ''
})
const handleChange = ({ target: { name, value }}) => {
setForm({ ...form, [name]: value })
}
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
await emailjs.send(
'service_vhjcslv',
'template_9gv4qe2',
{
from_name: form.name,
to_name: 'Colin',
from_email: form.email,
to_email: 'colinbassett28@proton.me',
message: form.message
},
'ZrFea_0jfsPFC8I50'
)
setLoading(false);
alert('Your message has been sent.')
setForm({
name: '',
email: '',
message: ''
})
} catch (error) {
setLoading(false);
console.log(error);
alert('Something went wrong.');
}
}
return (
<section className="c-space my-20">
<div className="relative min-h-screen flex items-center justify-center flex-col">
<img src="/assets/terminal.png" alt="terminal background" className="absolute inset-0 min-h-screen" />
<div className="contact-container">
<h3 className="head-text">Let&apos;s talk</h3>
<p className="text-lg text-white-600 mt-3">
Whether you&apos;re looking to build a new website,
improve your existing platform, or bring a unique
project to life, I&apos;m here to help.
</p>
<form ref={formRef} onSubmit={handleSubmit} className="mt-12 flex flex-col space-y-7">
<label className="space-y-3">
<span className="field-label">Full Name</span>
<input
type="text"
name="name"
value={form.name}
onChange={handleChange}
required
className="field-input"
placeholder="John Doe"
/>
</label>
<label className="space-y-3">
<span className="field-label">Email</span>
<input
type="email"
name="email"
value={form.email}
onChange={handleChange}
required
className="field-input"
placeholder="johndoe@gmail.com"
/>
</label>
<label className="space-y-3">
<span className="field-label">Your message</span>
<textarea
name="message"
value={form.message}
onChange={handleChange}
required
rows={5}
className="field-input"
placeholder="Hi, I wanna give you a job..."
/>
</label>
<button className="field-btn" type="submit" disabled={loading}>
{loading ? 'Sending...' : 'Send Message'}
<img src="/assets/arrow-up.png" alt="arrow-up" className="field-btn_arrow" />
</button>
</form>
</div>
</div>
</section>
)
}
export default Contact

View File

@ -0,0 +1,58 @@
import React, {Suspense, useState} from 'react'
import {Canvas} from "@react-three/fiber";
import {workExperiences} from "../constants/index.js";
import {OrbitControls} from "@react-three/drei";
import CanvasLoader from "../components/CanvasLoader.jsx";
import Developer from "../components/Developer.jsx";
const Experience = () => {
const [animationName, setAnimationName] = useState('idle');
return (
<section className="c-space my-20">
<div className="w-full text-white-600">
<h3 className="head-text">
My Work Experience
</h3>
<div className="work-container">
<div className="work-canvas">
<Canvas>
<ambientLight intensity={7} />
<spotLight position={[10, 10, 10]} angle={0.15} penubra={1} />
<directionalLight position={[10, 10, 10]} intensity={1} />
<OrbitControls enableZoom={false} maxPolarAngle={Math.PI / 2} />
<Suspense fallback={<CanvasLoader />}>
<Developer position-y={-3} scale={3} animationName={animationName} />
</Suspense>
</Canvas>
</div>
<div className="work-content">
<div className="sm:py-10 py-5 sm:px-5 px-2.5">
{workExperiences.map(({ id, name, pos, icon, duration, title, animation}) => (
<div key={id} className="work-content_container group" onClick={() => setAnimationName(animation.toLowerCase())} onPointerOver={() => setAnimationName(animation.toLowerCase())} onPointerOut={() => setAnimationName("idle")}>
<div className="flex flex-col h-full justify-start items-center py-2">
<div className="work-content_logo">
<img src={icon} alt="logo" className="w-full h-full rounded-lg" />
</div>
<div className="work-content_bar"/>
</div>
<div className="sm:p-5 px-2.5 py-5">
<p className="font-bold text-white-800">{name}</p>
<p className="text-sm mb-5">{pos}--{duration}</p>
<p className="group-hover:text-white transition ease-in-out duration-500">{title}</p>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</section>
)
}
export default Experience

32
src/sections/Footer.jsx Normal file
View File

@ -0,0 +1,32 @@
const Footer = () => {
return (
<section className="c-space pt-7 border-t border-black-300 flex justify-between items-center flex-wrap gap-5">
<div className="text-white-500 flex gap-2">
<p>Terms & Conditions</p>
<p>|</p>
<p>Privacy Policy</p>
</div>
<div className="flex gap-3">
<div className="social-icon">
<a href="https://git.fstropii.com" target="_blank" rel="noreferrer" className="social-icon">
<img src="/assets/github.svg" alt="Github" className="w-1/2 h-1/2" />
</a>
</div>
<div className="social-icon">
<a href="https://twitter.com/realTropii" target="_blank" rel="noreferrer" className="social-icon">
<img src="/assets/twitter.svg" alt="Twitter" className="w-1/2 h-1/2" />
</a>
</div>
<div className="social-icon">
<a href="https://www.linkedin.com/in/tropii/" target="_blank" rel="noreferrer" className="social-icon">
<img src="/assets/linkedin.svg" alt="LinkedIn" className="w-1/2 h-1/2" />
</a>
</div>
</div>
<p className="text-white-500">© 2025 Colin. All rights reserved</p>
</section>
)
}
export default Footer

68
src/sections/Hero.jsx Normal file
View File

@ -0,0 +1,68 @@
import { Leva } from 'leva';
import { Suspense } from 'react';
import { Canvas } from '@react-three/fiber';
import { useMediaQuery } from 'react-responsive';
import { PerspectiveCamera } from '@react-three/drei';
import Cube from '../components/Cube.jsx';
import Rings from '../components/Ring.jsx';
import ReactLogo from '../components/ReactLogo.jsx';
import Button from '../components/Button.jsx';
import Target from '../components/Target.jsx';
import CanvasLoader from '../components/CanvasLoader.jsx';
import HeroCamera from '../components/HeroCamera.jsx';
import { calculateSizes } from '../constants/index.js';
import HackerRoom from '../components/HackerRoom.jsx';
const Hero = () => {
// Use media queries to determine screen size
const isSmall = useMediaQuery({ maxWidth: 440 });
const isMobile = useMediaQuery({ maxWidth: 768 });
const isTablet = useMediaQuery({ minWidth: 768, maxWidth: 1024 });
const sizes = calculateSizes(isSmall, isMobile, isTablet);
return (
<section className="min-h-screen w-full flex flex-col relative" id="home">
<div className="w-full mx-auto flex flex-col sm:mt-36 mt-20 c-space gap-3">
<p className="sm:text-3xl text-xl font-medium text-white text-center font-generalsans">
Hi, I am Colin <span className="waving-hand">👋</span>
</p>
<p className="hero_tag text-gray_gradient">Building API&apos;s & Back-Ends</p>
</div>
<div className="w-full h-full absolute inset-0">
<Canvas className="w-full h-full">
<Suspense fallback={<CanvasLoader />}>
{/* To hide controller */}
<Leva hidden />
<PerspectiveCamera makeDefault position={[0, 0, 30]} />
<HeroCamera isMobile={isMobile}>
<HackerRoom scale={sizes.deskScale} position={sizes.deskPosition} rotation={[0.1, -Math.PI, 0]} />
</HeroCamera>
<group>
<Target position={sizes.targetPosition} />
{/*<ReactLogo position={sizes.reactLogoPosition} />*/}
{/*<Rings position={sizes.ringPosition} />*/}
{/* The ReactLogo and Rings are broken and I do not feel like fixing them */}
<Cube position={sizes.cubePosition} />
</group>
<ambientLight intensity={1} />
<directionalLight position={[10, 10, 10]} intensity={0.5} />
</Suspense>
</Canvas>
</div>
<div className="absolute bottom-7 left-0 right-0 w-full z-10 c-space">
<a href="#about" className="w-fit">
<Button name="Let's work together" isBeam containerClass="sm:w-fit w-full sm:min-w-96" />
</a>
</div>
</section>
);
};
export default Hero;

52
src/sections/Navbar.jsx Normal file
View File

@ -0,0 +1,52 @@
import React, {useState} from 'react'
import {navLinks} from "../constants/index.js";
const NavItems = () => {
return (
<ul className="nav-ul">
{navLinks.map(({id, href, name}) => (
<li key={id} className="nav-li">
<a href={href} className="nav-li_a" onClick={() => {}}>
{name}
</a>
</li>
))}
</ul>
)
}
const Navbar = () => {
const [isOpen, setIsOpen] = useState(false);
const toggleMenu = () => {
setIsOpen((prevIsOpen) => !prevIsOpen);
}
return (
<header className="fixed top-0 left-0 right-0 z-50 bg-black/90">
<div className="max-w-7xl mx-auto">
<div className="flex items-center justify-between py-5 mx-auto c-space">
<a href="/" className="text-neutral-400 font-bold text-xl hover:text-white transition-colors">
Colin
</a>
<button onClick={() => toggleMenu()} className="text-neutral-400 hover:text-white focus:outline-none sm:hidden flex" aria-label="Toggle Menu">
<img src={isOpen ? "assets/close.svg" : "assets/menu.svg"} alt="toggle" className="w-6 h-6" />
</button>
<nav className="sm:flex hidden">
<NavItems />
</nav>
</div>
<div className={`nav-sidebar ${isOpen ? 'max-h-screen' : 'max-h-0'}`}>
<nav className="p-5">
<NavItems />
</nav>
</div>
</div>
</header>
)
}
export default Navbar

89
src/sections/Projects.jsx Normal file
View File

@ -0,0 +1,89 @@
import React, {Suspense, useState} from 'react'
import {myProjects} from "../constants/index.js";
import {Canvas} from "@react-three/fiber";
import {Center, OrbitControls} from "@react-three/drei";
import CanvasLoader from "../components/CanvasLoader.jsx";
import DemoComputer from "../components/DemoComputer.jsx";
const projectCount = myProjects.length;
const Projects = () => {
const [selectedProjectIndex, setSelectedProjectIndex] = useState(0);
const currentProject = myProjects[selectedProjectIndex];
const handleNavigation = (direction) => {
setSelectedProjectIndex((prevIndex) => {
if (direction === 'previous') {
return prevIndex === 0 ? projectCount - 1 : prevIndex - 1;
} else {
return prevIndex === projectCount - 1 ? 0 : prevIndex + 1;
}
});
}
return (
<section className="c-space my-20">
<p className="head-text">My Work</p>
<div className="grid lg:grid-cols-2 grid-cols-1 mt-12 gap-5 w-full">
<div className="flex flex-col gap-5 relative sm:p-10 py-10 px-5 shadow-2xl shadow-black-200">
<div className="absolute top-0 right-0">
<img src={currentProject.spotlight} alt="spotlight" className="w-full h-96 object-cover rounded-xl" />
</div>
<div className="p-3 backdrop-filter backdrop-blur-3xl w-fit rounded-lg" style={currentProject.logoStyle}>
<img src={currentProject.logo} alt="logo" className="w-10 h-10 shadow-sm" />
</div>
<div className="flex flex-col gap-5 text-white-600 my-5">
<p className="text-white text-2xl font-semibold animatedText">{currentProject.title}</p>
<p className="animatedText">{currentProject.desc}</p>
<p className="animatedText">{currentProject.subdesc}</p>
</div>
<div className="flex items-center justify-between flex-wrap gap-5">
<div className="flex items-center gap-3">
{currentProject.tags.map((tag, index) => (
<div key={index} className="tech-logo">
<img src={tag.path} alt={tag.name} />
</div>
))}
</div>
<a className="flex items-center gap-2 cursor-pointer text-white-600" href={currentProject.href} target="_blank" rel="noreferrer">
<p>View Project</p>
<img src="/assets/arrow-up.png" className="w-3 h-3" alt="arrow" />
</a>
</div>
<div className="flex justify-between items-center mt-7">
<button className="arrow-btn" onClick={() => handleNavigation('previous')}>
<img src="/assets/left-arrow.png" alt="left arrow" className="w-4 h-4" />
</button>
<button className="arrow-btn" onClick={() => handleNavigation('next')}>
<img src="/assets/right-arrow.png" alt="right arrow" className="w-4 h-4" />
</button>
</div>
</div>
<div className="border border-black-300 bg-black-200 rounded-lg h-96 md:h-full">
<Canvas>
<ambientLight intensity={Math.PI} />
<directionalLight position={[10, 10, 5]} />
<Center>
<Suspense fallback={<CanvasLoader />}>
<group scale={2} position={[0, -3, 0]} rotation={[0, -0.1, 0]}>
<DemoComputer texture={currentProject.texture} />
</group>
</Suspense>
</Center>
<OrbitControls maxPolarAngle={Math.PI / 2} enableZoom={false} />
</Canvas>
</div>
</div>
</section>
)
}
export default Projects

View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"paths": {
"external/*": ["node_modules/*"]
}
}
}

32
tailwind.config.js Normal file
View File

@ -0,0 +1,32 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
fontFamily: {
generalsans: ['General Sans', 'sans-serif'],
},
colors: {
black: {
DEFAULT: '#000',
100: '#010103',
200: '#0E0E10',
300: '#1C1C21',
500: '#3A3A49',
600: '#1A1A1A',
},
white: {
DEFAULT: '#FFFFFF',
800: '#E4E4E6',
700: '#D6D9E9',
600: '#AFB0B6',
500: '#62646C',
},
},
backgroundImage: {
terminal: "url('/assets/terminal.png')",
},
},
},
plugins: [],
};

12
vite.config.js Normal file
View File

@ -0,0 +1,12 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from 'tailwindcss'
export default defineConfig({
plugins: [react()],
css: {
postcss: {
plugins: [tailwindcss()],
},
}
})