Implement a full portfolio in vite
24
.gitignore
vendored
Normal 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
@ -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
@ -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
42
package.json
Normal 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
After Width: | Height: | Size: 276 B |
BIN
public/assets/blob-logo.png
Normal file
After Width: | Height: | Size: 985 KiB |
3
public/assets/close.svg
Normal 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
@ -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
After Width: | Height: | Size: 46 KiB |
6
public/assets/discord-py.svg
Normal file
After Width: | Height: | Size: 13 KiB |
19
public/assets/figma.svg
Normal 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
After Width: | Height: | Size: 2.2 KiB |
15
public/assets/framer.svg
Normal 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 |
1
public/assets/freelance.svg
Normal file
After Width: | Height: | Size: 6.0 KiB |
1
public/assets/github.svg
Normal 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
After Width: | Height: | Size: 106 KiB |
BIN
public/assets/grid2.png
Normal file
After Width: | Height: | Size: 174 KiB |
BIN
public/assets/grid3.png
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
public/assets/grid4.png
Normal file
After Width: | Height: | Size: 141 KiB |
1
public/assets/instagram.svg
Normal 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 |
BIN
public/assets/left-arrow.png
Normal file
After Width: | Height: | Size: 315 B |
4
public/assets/linkedin.svg
Normal file
After Width: | Height: | Size: 6.5 KiB |
3
public/assets/menu.svg
Normal 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
@ -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 |
BIN
public/assets/project-logo4.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
public/assets/project1-logo.png
Normal file
After Width: | Height: | Size: 1.6 MiB |
6
public/assets/pymongo.svg
Normal 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
After Width: | Height: | Size: 15 KiB |
1
public/assets/react.svg
Normal 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
After Width: | Height: | Size: 15 KiB |
BIN
public/assets/review2.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
public/assets/review3.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
public/assets/review4.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
public/assets/right-arrow.png
Normal file
After Width: | Height: | Size: 313 B |
5
public/assets/sentry.svg
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
public/assets/spotlight1.png
Normal file
After Width: | Height: | Size: 354 KiB |
BIN
public/assets/spotlight2.png
Normal file
After Width: | Height: | Size: 393 KiB |
BIN
public/assets/spotlight3.png
Normal file
After Width: | Height: | Size: 419 KiB |
BIN
public/assets/spotlight4.png
Normal file
After Width: | Height: | Size: 322 KiB |
BIN
public/assets/spotlight5.png
Normal file
After Width: | Height: | Size: 319 KiB |
BIN
public/assets/star.png
Normal file
After Width: | Height: | Size: 505 B |
BIN
public/assets/tailwindcss.png
Normal file
After Width: | Height: | Size: 688 B |
BIN
public/assets/terminal.png
Normal file
After Width: | Height: | Size: 966 KiB |
3
public/assets/tick.svg
Normal 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 |
1
public/assets/twitter.svg
Normal 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 |
BIN
public/assets/typescript.png
Normal file
After Width: | Height: | Size: 568 B |
BIN
public/models/animations/clapping.fbx
Normal file
BIN
public/models/animations/idle.fbx
Normal file
BIN
public/models/animations/salute.fbx
Normal file
BIN
public/models/animations/victory.fbx
Normal file
BIN
public/models/computer.glb
Normal file
BIN
public/models/cube.glb
Normal file
BIN
public/models/desk.glb
Normal file
BIN
public/models/hacker-room.glb
Normal file
BIN
public/models/human/developer.glb
Normal file
BIN
public/models/react.glb
Normal file
BIN
public/textures/cube.png
Normal file
After Width: | Height: | Size: 129 KiB |
BIN
public/textures/desk/chair.png
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
public/textures/desk/cpu.png
Normal file
After Width: | Height: | Size: 334 KiB |
BIN
public/textures/desk/cushion.png
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
public/textures/desk/monitor.png
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
public/textures/desk/screen-old.png
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
public/textures/desk/screen.png
Normal file
After Width: | Height: | Size: 701 KiB |
BIN
public/textures/desk/table.png
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
public/textures/project/aftermath.mp4
Normal file
BIN
public/textures/project/blob.mp4
Normal file
BIN
public/textures/project/project1.png
Normal file
After Width: | Height: | Size: 141 KiB |
BIN
public/textures/project/project4.mp4
Normal file
BIN
public/textures/rings.png
Normal file
After Width: | Height: | Size: 335 KiB |
1
public/vite.svg
Normal 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
@ -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
@ -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
|
23
src/components/CanvasLoader.jsx
Normal 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
@ -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;
|
1020
src/components/DemoComputer.jsx
Normal file
101
src/components/Developer.jsx
Normal 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;
|
43
src/components/HackerRoom.jsx
Normal 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;
|
20
src/components/HeroCamera.jsx
Normal 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
|
24
src/components/ReactLogo.jsx
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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, I’m Colin!</p>
|
||||
<p className="grid-subtext">
|
||||
With 5 years of experience, I have honed my skills in backend development, creating API'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">I’m very flexible with time zone communications & locations</p>
|
||||
<p className="grid-subtext">I'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't just my
|
||||
profession—it'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
@ -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
@ -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's talk</h3>
|
||||
<p className="text-lg text-white-600 mt-3">
|
||||
Whether you're looking to build a new website,
|
||||
improve your existing platform, or bring a unique
|
||||
project to life, I'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
|
58
src/sections/Experience.jsx
Normal 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
@ -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
@ -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'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
@ -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
@ -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
|
13
src/sections/tsconfig.json
Normal 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
@ -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
@ -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()],
|
||||
},
|
||||
}
|
||||
})
|