├── public
├── .well-known
│ └── discord
├── og.jpg
├── noise.gif
├── sounds
│ ├── bite.mp3
│ ├── click.wav
│ ├── hover.ogg
│ ├── switch-off.mp3
│ └── switch-on.mp3
├── projects-images
│ ├── quizi
│ │ ├── home.avif
│ │ ├── play-classic.avif
│ │ ├── play-infinity.avif
│ │ ├── poster.svg
│ │ └── poster2.svg
│ ├── codedev
│ │ ├── about.avif
│ │ ├── home.avif
│ │ ├── contact.avif
│ │ ├── poster.avif
│ │ └── projects.avif
│ ├── freesets
│ │ ├── home.avif
│ │ ├── icons.avif
│ │ ├── light.avif
│ │ └── poster.avif
│ ├── jobzilla
│ │ ├── home.avif
│ │ ├── job.avif
│ │ ├── jobs.avif
│ │ ├── company.avif
│ │ ├── poster.avif
│ │ └── companies.avif
│ └── space-tourism
│ │ ├── crew.avif
│ │ ├── home.avif
│ │ ├── poster.avif
│ │ ├── destination.avif
│ │ └── technology.avif
├── skills
│ ├── axios.svg
│ ├── supabase.svg
│ ├── css.svg
│ ├── html.svg
│ ├── leafletjs.svg
│ ├── tailwind.svg
│ ├── git.svg
│ ├── astro.svg
│ ├── express.svg
│ ├── bootstrap.svg
│ ├── three.js.svg
│ ├── javascript.svg
│ ├── figma.svg
│ ├── blender.svg
│ ├── typescript.svg
│ ├── svelte.svg
│ ├── next.js.svg
│ ├── node.svg
│ ├── styled components.svg
│ ├── react.svg
│ ├── sass.svg
│ └── gsap.svg
├── social
│ ├── x.svg
│ ├── instagram.svg
│ ├── gmail.svg
│ ├── github.svg
│ └── discord.svg
├── wave.svg
└── favicon.svg
├── readme
├── desktop.webp
├── mobile.webp
├── tablet.webp
├── light_mobile.webp
├── light_tablet.webp
├── light_desktop.webp
└── preview-header.webp
├── src
├── assets
│ ├── fonts
│ │ ├── Poppins-Bold.ttf
│ │ ├── Poppins-Light.ttf
│ │ ├── Poppins-Regular.ttf
│ │ └── Poppins-SemiBold.ttf
│ ├── json
│ │ ├── skills.js
│ │ ├── techs.json
│ │ └── projects.js
│ ├── settings
│ │ ├── sound-off.svg
│ │ ├── external-link.svg
│ │ ├── sound-on.svg
│ │ ├── lightmode.svg
│ │ ├── star.svg
│ │ ├── darkmode.svg
│ │ └── language.svg
│ ├── icons
│ │ ├── close.svg
│ │ ├── live.svg
│ │ ├── arrow.svg
│ │ ├── repo.svg
│ │ └── social-links.jsx
│ ├── logo.svg
│ └── languages
│ │ ├── en.json
│ │ └── es.json
├── utils
│ ├── play-sound.js
│ └── order-array.js
├── components
│ ├── ui
│ │ └── Tooltip.jsx
│ ├── About.jsx
│ ├── Title.jsx
│ ├── Logo.jsx
│ ├── LoadingScreen.jsx
│ ├── Skills.jsx
│ ├── ProjectsDialog
│ │ ├── Slider.jsx
│ │ ├── ProjectsDialog.jsx
│ │ ├── Project.jsx
│ │ └── Images.jsx
│ ├── Social.jsx
│ ├── Projects.jsx
│ └── Settings.jsx
├── main.jsx
├── styles
│ ├── about.css
│ ├── ui
│ │ └── tooltip.css
│ ├── logo.css
│ ├── normalize.css
│ ├── title.css
│ ├── projectsDialog
│ │ ├── slider.css
│ │ ├── project.css
│ │ ├── images.css
│ │ └── projectsDialog.css
│ ├── settings.css
│ ├── skills.css
│ ├── loadingScreen.css
│ ├── social.css
│ ├── index.css
│ └── projects.css
├── context
│ └── language.jsx
└── App.jsx
├── .stylelintrc.json
├── vite.config.js
├── .gitignore
├── .eslintrc.cjs
├── package.json
├── LICENCE
├── index.html
└── README.md
/public/.well-known/discord:
--------------------------------------------------------------------------------
1 | dh=225603871beee6d2bdedae3d0b401e6e0802219a
--------------------------------------------------------------------------------
/public/og.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/og.jpg
--------------------------------------------------------------------------------
/public/noise.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/noise.gif
--------------------------------------------------------------------------------
/readme/desktop.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/readme/desktop.webp
--------------------------------------------------------------------------------
/readme/mobile.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/readme/mobile.webp
--------------------------------------------------------------------------------
/readme/tablet.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/readme/tablet.webp
--------------------------------------------------------------------------------
/public/sounds/bite.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/sounds/bite.mp3
--------------------------------------------------------------------------------
/public/sounds/click.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/sounds/click.wav
--------------------------------------------------------------------------------
/public/sounds/hover.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/sounds/hover.ogg
--------------------------------------------------------------------------------
/readme/light_mobile.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/readme/light_mobile.webp
--------------------------------------------------------------------------------
/readme/light_tablet.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/readme/light_tablet.webp
--------------------------------------------------------------------------------
/readme/light_desktop.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/readme/light_desktop.webp
--------------------------------------------------------------------------------
/readme/preview-header.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/readme/preview-header.webp
--------------------------------------------------------------------------------
/public/sounds/switch-off.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/sounds/switch-off.mp3
--------------------------------------------------------------------------------
/public/sounds/switch-on.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/sounds/switch-on.mp3
--------------------------------------------------------------------------------
/src/assets/fonts/Poppins-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/src/assets/fonts/Poppins-Bold.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Poppins-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/src/assets/fonts/Poppins-Light.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Poppins-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/src/assets/fonts/Poppins-Regular.ttf
--------------------------------------------------------------------------------
/public/projects-images/quizi/home.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/quizi/home.avif
--------------------------------------------------------------------------------
/src/assets/fonts/Poppins-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/src/assets/fonts/Poppins-SemiBold.ttf
--------------------------------------------------------------------------------
/public/projects-images/codedev/about.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/codedev/about.avif
--------------------------------------------------------------------------------
/public/projects-images/codedev/home.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/codedev/home.avif
--------------------------------------------------------------------------------
/public/projects-images/freesets/home.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/freesets/home.avif
--------------------------------------------------------------------------------
/public/projects-images/jobzilla/home.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/jobzilla/home.avif
--------------------------------------------------------------------------------
/public/projects-images/jobzilla/job.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/jobzilla/job.avif
--------------------------------------------------------------------------------
/public/projects-images/jobzilla/jobs.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/jobzilla/jobs.avif
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["stylelint-config-standard"],
3 | "rules": {
4 | "property-no-vendor-prefix": null
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/public/projects-images/codedev/contact.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/codedev/contact.avif
--------------------------------------------------------------------------------
/public/projects-images/codedev/poster.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/codedev/poster.avif
--------------------------------------------------------------------------------
/public/projects-images/codedev/projects.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/codedev/projects.avif
--------------------------------------------------------------------------------
/public/projects-images/freesets/icons.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/freesets/icons.avif
--------------------------------------------------------------------------------
/public/projects-images/freesets/light.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/freesets/light.avif
--------------------------------------------------------------------------------
/public/projects-images/freesets/poster.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/freesets/poster.avif
--------------------------------------------------------------------------------
/public/projects-images/jobzilla/company.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/jobzilla/company.avif
--------------------------------------------------------------------------------
/public/projects-images/jobzilla/poster.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/jobzilla/poster.avif
--------------------------------------------------------------------------------
/public/projects-images/jobzilla/companies.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/jobzilla/companies.avif
--------------------------------------------------------------------------------
/public/projects-images/quizi/play-classic.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/quizi/play-classic.avif
--------------------------------------------------------------------------------
/public/projects-images/space-tourism/crew.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/space-tourism/crew.avif
--------------------------------------------------------------------------------
/public/projects-images/space-tourism/home.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/space-tourism/home.avif
--------------------------------------------------------------------------------
/public/projects-images/quizi/play-infinity.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/quizi/play-infinity.avif
--------------------------------------------------------------------------------
/public/projects-images/space-tourism/poster.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/space-tourism/poster.avif
--------------------------------------------------------------------------------
/public/projects-images/space-tourism/destination.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/space-tourism/destination.avif
--------------------------------------------------------------------------------
/public/projects-images/space-tourism/technology.avif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cosmoart/Portafolio/HEAD/public/projects-images/space-tourism/technology.avif
--------------------------------------------------------------------------------
/src/assets/json/skills.js:
--------------------------------------------------------------------------------
1 | import techs from './techs.json'
2 |
3 | const skills = []
4 | for (const tech in techs) {
5 | if (techs[tech].skills) skills.push(techs[tech])
6 | }
7 |
8 | export default skills
9 |
--------------------------------------------------------------------------------
/src/utils/play-sound.js:
--------------------------------------------------------------------------------
1 | export default function playSound (sound, volume = 0.25) {
2 | if (localStorage.getItem('sound') === 'false') return
3 | const audio = new Audio(`/sounds/${sound}`)
4 | audio.volume = volume
5 | audio.play()
6 | }
7 |
--------------------------------------------------------------------------------
/public/skills/axios.svg:
--------------------------------------------------------------------------------
1 | Axios
--------------------------------------------------------------------------------
/public/social/x.svg:
--------------------------------------------------------------------------------
1 | X
--------------------------------------------------------------------------------
/src/assets/settings/sound-off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/supabase.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/wave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/settings/external-link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/utils/order-array.js:
--------------------------------------------------------------------------------
1 | export function orderArray (array) {
2 | // [1, 2, 3, 4, 5, 6, 7] => [7, 5, 3, 1, 2, 4, 6]
3 | return array.sort((a, b) => a - b)
4 | .reduce((result, value, index) => {
5 | index % 2 === 0 ? result.unshift(value) : result.push(value)
6 | return result
7 | }, [])
8 | }
9 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | resolve: {
8 | caseSensitive: true,
9 | alias: {
10 | '@': '/src'
11 | }
12 | }
13 | })
14 |
--------------------------------------------------------------------------------
/src/assets/settings/sound-on.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/css.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/html.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/settings/lightmode.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/ui/Tooltip.jsx:
--------------------------------------------------------------------------------
1 | import '@/styles/ui/tooltip.css'
2 |
3 | export default function Tooltip ({ children, dir = 'top', ariaHidden = 'true' }) {
4 | const direction = ['top', 'right', 'bottom', 'left'].includes(dir) ? dir : 'top'
5 |
6 | return (
7 |
8 | {children}
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | .vercel
27 |
--------------------------------------------------------------------------------
/public/social/instagram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/leafletjs.svg:
--------------------------------------------------------------------------------
1 | Leaflet
--------------------------------------------------------------------------------
/src/components/About.jsx:
--------------------------------------------------------------------------------
1 | import { useLanguage } from '@/context/language'
2 | import '@/styles/about.css'
3 |
4 | export default function About () {
5 | const { translations } = useLanguage()
6 |
7 | return (
8 |
9 | {translations.about.title}
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/assets/icons/live.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Title.jsx:
--------------------------------------------------------------------------------
1 | import { useLanguage } from '@/context/language'
2 | import '@/styles/title.css'
3 |
4 | export default function Title () {
5 | const { translations } = useLanguage()
6 |
7 | return (
8 |
9 |
10 | {translations.title.hello}
11 | COSMO
12 | {translations.title.webdeveloper}
13 |
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import { Analytics } from '@vercel/analytics/react'
2 | import { LanguageProvider } from '@/context/language'
3 | import React from 'react'
4 | import ReactDOM from 'react-dom/client'
5 | import App from '@/App.jsx'
6 |
7 | import '@/styles/index.css'
8 | import '@/styles/normalize.css'
9 |
10 | ReactDOM.createRoot(document.getElementById('root')).render(
11 |
12 |
13 |
14 |
15 |
16 |
17 | )
18 |
--------------------------------------------------------------------------------
/src/assets/settings/star.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Logo.jsx:
--------------------------------------------------------------------------------
1 | import '@/styles/logo.css'
2 | import LogoImg from '@/assets/logo.svg'
3 |
4 | export default function Logo () {
5 | function changeColor (e) {
6 | document.documentElement.style.setProperty('--primary-color', e.target.value)
7 | }
8 |
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/public/skills/tailwind.svg:
--------------------------------------------------------------------------------
1 | Tailwind CSS
--------------------------------------------------------------------------------
/public/social/gmail.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: { browser: true, es2020: true },
3 | extends: [
4 | 'eslint:recommended',
5 | 'plugin:react/recommended',
6 | 'plugin:react/jsx-runtime',
7 | 'plugin:react-hooks/recommended',
8 | 'standard'
9 | ],
10 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
11 | settings: { react: { version: '18.2' } },
12 | plugins: ['react-refresh'],
13 | rules: {
14 | 'react-refresh/only-export-components': 'warn',
15 | indent: [1, 'tab'],
16 | 'no-tabs': 0,
17 | 'jsx-quotes': [1, 'prefer-single'],
18 | 'react/react-in-jsx-scope': 'off',
19 | 'react/prop-types': 'off',
20 | 'no-unused-vars': 1,
21 | useUnknownInCatchVariables: 0
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/public/skills/git.svg:
--------------------------------------------------------------------------------
1 | Git
--------------------------------------------------------------------------------
/src/styles/about.css:
--------------------------------------------------------------------------------
1 | .about {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | }
6 |
7 | .about h2 {
8 | margin-bottom: 10px;
9 | }
10 |
11 | .about p {
12 | font-weight: 400;
13 | line-height: 140%;
14 | text-wrap: pretty;
15 | margin: 0;
16 | color: var(--dark-text-color);
17 | }
18 |
19 | .about p span {
20 | color: black;
21 | text-decoration: underline var(--primary-color) 2px;
22 | }
23 |
24 | .darkmode .about p {
25 | font-weight: 300;
26 | color: rgb(255 255 255 / 80%);
27 | }
28 |
29 | .darkmode .about p span {
30 | color: white;
31 | }
32 |
33 | @media (570px <= width) {
34 | .about h2,
35 | .about p {
36 | max-width: 40rem;
37 | margin-inline: auto;
38 | width: 100%;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/skills/astro.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/LoadingScreen.jsx:
--------------------------------------------------------------------------------
1 | import '@/styles/loadingScreen.css'
2 |
3 | export default function LoadingScreen () {
4 | if (window.innerWidth < 1050) return null
5 |
6 | return (
7 |
8 |
9 |
10 |
11 |
14 |
17 |
18 |
19 |
22 |
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/public/skills/express.svg:
--------------------------------------------------------------------------------
1 | Express
--------------------------------------------------------------------------------
/public/social/github.svg:
--------------------------------------------------------------------------------
1 | GitHub
--------------------------------------------------------------------------------
/public/skills/bootstrap.svg:
--------------------------------------------------------------------------------
1 | Bootstrap
--------------------------------------------------------------------------------
/src/assets/icons/arrow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "portafolio",
3 | "private": true,
4 | "version": "1.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite --host",
8 | "build": "vite build",
9 | "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@vercel/analytics": "1.0.2",
14 | "react": "18.2.0",
15 | "react-dom": "18.2.0"
16 | },
17 | "devDependencies": {
18 | "@types/react": "18.0.28",
19 | "@types/react-dom": "18.0.11",
20 | "@vitejs/plugin-react": "4.3.4",
21 | "eslint": "8.38.0",
22 | "eslint-plugin-react": "7.32.2",
23 | "eslint-plugin-react-hooks": "4.6.0",
24 | "eslint-plugin-react-refresh": "0.3.4",
25 | "standard": "17.0.0",
26 | "stylelint": "15.10.3",
27 | "stylelint-config-standard": "34.0.0",
28 | "vite": "6.3.6"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/public/skills/three.js.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/settings/darkmode.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Skills.jsx:
--------------------------------------------------------------------------------
1 | import skills from '@/assets/json/skills.js'
2 | import { useLanguage } from '@/context/language'
3 | import Tooltip from './ui/Tooltip'
4 | import '@/styles/skills.css'
5 |
6 | export default function Skills () {
7 | const { translations } = useLanguage()
8 |
9 | return (
10 |
11 | {translations.skills.title}
12 |
13 |
14 | {skills.map((skill, index) => {
15 | return (
16 |
19 |
22 | {skill.name}
23 |
24 | )
25 | })}
26 |
27 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/public/skills/javascript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/styles/ui/tooltip.css:
--------------------------------------------------------------------------------
1 | .tooltip {
2 | position: absolute;
3 | background-color: #fff;
4 | border: 1px solid #1a1924;
5 | border-radius: 5px;
6 | padding: 3px 7px;
7 | font-size: 11px;
8 | transition: all 0.2s ease-in-out;
9 | translate: 0 1rem;
10 | opacity: 0;
11 | z-index: 99999;
12 | }
13 |
14 | .darkmode .tooltip {
15 | background-color: #1a1924;
16 | border: 1px solid #fff;
17 | }
18 |
19 | .tooltip-container:hover .tooltip {
20 | opacity: 1;
21 | pointer-events: auto;
22 | translate: 0;
23 | }
24 |
25 | .tooltip[data-dir='top'] {
26 | bottom: calc(100% + 5px);
27 | left: 50%;
28 | transform: translateX(-50%);
29 | }
30 |
31 | .tooltip[data-dir='top']::after {
32 | content: '';
33 | position: absolute;
34 | bottom: -10px;
35 | left: 50%;
36 | transform: translateX(-50%);
37 | border: 5px solid transparent;
38 | border-top-color: #1a1924;
39 | }
40 |
41 | .darkmode .tooltip[data-dir='top']::after {
42 | border-top-color: #fff;
43 | }
44 |
--------------------------------------------------------------------------------
/src/assets/languages/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "close": "Close",
3 | "title": {
4 | "hello": "Hello, i am",
5 | "webdeveloper": "Front-end web developer"
6 | },
7 | "about": {
8 | "title": "About me",
9 | "description": "Self-taught front-end web developer with 2 year of experience as a freelancer, learning through personal and professional projects. I build web applications using technologies like React, Next.js, Tailwind , among others. I speak Spanish and English , and I'm passionate about creating 3D art."
10 | },
11 | "settings": {
12 | "darkmode": "Dark mode",
13 | "lightmode": "Light mode",
14 | "english": "English",
15 | "spanish": "Spanish",
16 | "sound": "Sound",
17 | "mute": "Mute",
18 | "theme": "Change theme",
19 | "code": "View code"
20 | },
21 | "social": {
22 | "title": "Social networks"
23 | },
24 | "skills": {
25 | "title": "Skills"
26 | },
27 | "projects": {
28 | "title": "Projects",
29 | "more": "View more",
30 | "code": "Code",
31 | "live": "Live"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/assets/languages/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "close": "Cerrar",
3 | "title": {
4 | "hello": "Hola, soy",
5 | "webdeveloper": "Desarrollador web"
6 | },
7 | "about": {
8 | "title": "Sobre mí",
9 | "description": "Desarrollador web front-end autodidacta con 2 años de experiencia como freelancer y aprendiendo de proyectos personales y profesionales. Desarrollo aplicaciones web con tecnologías como React, Next.js, Tailwind , entre otras. Hablo Español e Inglés y me apasiona la creación de arte 3D."
10 | },
11 | "settings": {
12 | "darkmode": "Modo oscuro",
13 | "lightmode": "Modo claro",
14 | "english": "Inglés",
15 | "spanish": "Español",
16 | "sound": "Sonido",
17 | "mute": "Silenciar",
18 | "theme": "Cambiar tema",
19 | "code": "Ver código"
20 | },
21 | "social": {
22 | "title": "Redes sociales"
23 | },
24 | "skills": {
25 | "title": "Habilidades"
26 | },
27 | "projects": {
28 | "title": "Proyectos",
29 | "more": "Ver más",
30 | "code": "Código",
31 | "live": "Página"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/assets/icons/repo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/figma.svg:
--------------------------------------------------------------------------------
1 | Figma
--------------------------------------------------------------------------------
/src/styles/logo.css:
--------------------------------------------------------------------------------
1 | .logo {
2 | display: grid;
3 | place-items: center;
4 | background: linear-gradient(23deg, rgb(19 16 156 / 100%), rgb(0 0 0 / 0%)),
5 | url('/noise.svg');
6 | position: relative;
7 | padding: 1rem;
8 | }
9 |
10 | .logo img {
11 | width: 100px;
12 | z-index: 1;
13 |
14 | /* filter: invert(1); */
15 | }
16 |
17 | .color-picker {
18 | overflow: hidden;
19 | border-radius: 8px;
20 | position: absolute;
21 | width: 100%;
22 | height: 100%;
23 | -webkit-appearance: none;
24 | appearance: none;
25 | padding: 0;
26 | margin: 0;
27 | border: none;
28 | outline: none;
29 | }
30 |
31 | .color-picker::-webkit-color-swatch-wrapper {
32 | padding: 0;
33 | }
34 |
35 | .color-picker::-webkit-color-swatch {
36 | border: none;
37 | }
38 |
39 | .logo-border {
40 | --padding: 10px;
41 |
42 | border: 1px solid rgb(255 255 255 / 15%);
43 | position: absolute;
44 | top: var(--padding);
45 | left: var(--padding);
46 | width: calc(100% - var(--padding) * 2);
47 | height: calc(100% - var(--padding) * 2);
48 | border-radius: 5px;
49 | pointer-events: none;
50 | }
51 |
--------------------------------------------------------------------------------
/src/assets/settings/language.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Cosmo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/context/language.jsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react'
2 | import enTranslations from '@/assets/languages/en.json'
3 | import esTranslations from '@/assets/languages/es.json'
4 | const LanguageContext = createContext()
5 |
6 | const languages = ['en', 'es']
7 |
8 | export const LanguageProvider = ({ children }) => {
9 | let languageValue = localStorage.getItem('language')
10 | ? localStorage.getItem('language')
11 | : navigator.language.substring(0, 2) || navigator.userLanguage.substring(0, 2)
12 |
13 | languageValue = languages.includes(languageValue) ? languageValue : 'en'
14 |
15 | const [language, setLanguage] = useState(languageValue)
16 | const translations = language === 'en' ? enTranslations : esTranslations
17 |
18 | useEffect(() => {
19 | localStorage.setItem('language', language)
20 | }, [language])
21 |
22 | const changeLanguage = newLanguage => {
23 | setLanguage(languages.includes(newLanguage) ? newLanguage : 'en')
24 | document.documentElement.lang = newLanguage
25 | }
26 |
27 | return (
28 |
29 | {children}
30 |
31 | )
32 | }
33 |
34 | export const useLanguage = () => useContext(LanguageContext)
35 |
--------------------------------------------------------------------------------
/public/skills/blender.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/typescript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/social/discord.svg:
--------------------------------------------------------------------------------
1 | Discord
--------------------------------------------------------------------------------
/src/styles/normalize.css:
--------------------------------------------------------------------------------
1 | /*! modern-normalize v2.0.0 | MIT License | https://github.com/sindresorhus/modern-normalize */*,::after,::before{box-sizing:border-box}html{font-family:system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji';line-height:1.15;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4}body{margin:0}hr{height:0;color:inherit}abbr[title]{text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}:-moz-focusring{outline:1px dotted ButtonText}:-moz-ui-invalid{box-shadow:none}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}
--------------------------------------------------------------------------------
/src/components/ProjectsDialog/Slider.jsx:
--------------------------------------------------------------------------------
1 | import arrowIcon from '@/assets/icons/arrow.svg'
2 | import '@/styles/projectsDialog/slider.css'
3 |
4 | export default function Slider ({ setCurrentProject, currentProject, orderProjects }) {
5 | const handleWheel = (e) => {
6 | if (e.deltaY < 0 && currentProject === 0) return
7 | if (e.deltaY > 0 && currentProject === orderProjects.length - 1) return
8 | setCurrentProject(currentProject + (e.deltaY > 0 ? 1 : -1))
9 | }
10 |
11 | return (
12 |
13 |
14 |
setCurrentProject(currentProject - 1)} disabled={currentProject === 0}>
15 |
16 |
17 |
setCurrentProject(currentProject + 1)} disabled={currentProject === orderProjects.length - 1}>
18 |
19 |
20 |
21 |
22 |
23 | {orderProjects.map((project, index) => {
24 | return (
25 |
setCurrentProject(index)}>
26 |
27 |
28 | )
29 | })}
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/src/styles/title.css:
--------------------------------------------------------------------------------
1 | .title h1 {
2 | gap: 0 15px;
3 | position: relative;
4 | text-transform: uppercase;
5 | z-index: 10;
6 | display: flex;
7 | flex-direction: column;
8 | align-items: center;
9 | }
10 |
11 | .title-hello {
12 | grid-area: title-hello;
13 | }
14 |
15 | .title-name {
16 | grid-area: title-name;
17 | line-height: 4rem;
18 | font-size: clamp(3rem, 4.7vw, 4.4rem);
19 | background: linear-gradient(90deg, var(--primary-color) 30%, #2568ff 100%);
20 | -webkit-text-fill-color: transparent;
21 | -webkit-background-clip: text;
22 | background-clip: text;
23 | }
24 |
25 | .title-subtitle {
26 | grid-area: title-subtitle;
27 | font-size: clamp(0.9rem, 1.3vw, 1rem);
28 | }
29 |
30 | @media (width <= 570px) {
31 | .title-hello {
32 | line-height: 1.2rem;
33 | }
34 |
35 | .title-name {
36 | font-size: 4rem;
37 | }
38 | }
39 |
40 | @media (570px <= width <= 1050px) {
41 | .title-name {
42 | font-size: 4rem;
43 | }
44 |
45 | .title-subtitle {
46 | font-size: clamp(0.7rem, 2vw, 1.2rem);
47 | }
48 | }
49 |
50 | @media (570px <= width) {
51 | .title h1 {
52 | display: grid;
53 | grid-template-areas:
54 | 'title-hello title-name'
55 | 'title-subtitle title-name';
56 | }
57 |
58 | .title-hello,
59 | .title-subtitle {
60 | text-align: right;
61 | height: 100%;
62 | }
63 |
64 | .title-hello {
65 | display: flex;
66 | align-items: flex-end;
67 | justify-content: flex-end;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/styles/projectsDialog/slider.css:
--------------------------------------------------------------------------------
1 | .projects-slider {
2 | position: relative;
3 | margin-inline: auto;
4 | width: 80%;
5 | max-width: 45rem;
6 | }
7 |
8 | .projects-slider .slider {
9 | position: relative;
10 | height: 5rem;
11 | display: flex;
12 | gap: 1rem;
13 | }
14 |
15 | .projects-slider .project {
16 | flex-shrink: inherit;
17 | }
18 |
19 | .projects-slider .project-title {
20 | margin: 0;
21 | }
22 |
23 | .projects-slider article {
24 | cursor: pointer;
25 | }
26 |
27 | .slider img {
28 | height: 100%;
29 | object-fit: cover;
30 | }
31 |
32 | .project-buttons button {
33 | position: absolute;
34 | top: 50%;
35 | transform: translateY(-50%);
36 | z-index: 10;
37 | background: white;
38 | border-radius: 100%;
39 | padding: 4px;
40 | transition: all 0.2s ease-in-out;
41 | transform-origin: top;
42 | }
43 |
44 | .project-buttons button:hover {
45 | scale: 1.08;
46 | }
47 |
48 | .project-buttons button:disabled {
49 | filter: grayscale(1);
50 | opacity: 0.7;
51 | pointer-events: none;
52 | }
53 |
54 | .project-buttons button:first-child {
55 | left: -4rem;
56 | }
57 |
58 | .project-buttons button:last-child {
59 | right: -4rem;
60 | }
61 |
62 | .project-buttons button img {
63 | width: 1.5rem;
64 | height: 1.5rem;
65 | vertical-align: middle;
66 | }
67 |
68 | .arrow-left {
69 | rotate: 180deg;
70 | }
71 |
72 | .project-selected {
73 | outline: white solid 1px;
74 | outline-offset: 3px;
75 | scale: 1.1;
76 | }
77 |
--------------------------------------------------------------------------------
/public/skills/svelte.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/next.js.svg:
--------------------------------------------------------------------------------
1 | Next.js
--------------------------------------------------------------------------------
/src/styles/settings.css:
--------------------------------------------------------------------------------
1 | .settings {
2 | padding: 1rem 2rem !important;
3 | }
4 |
5 | .sound-btn {
6 | position: relative;
7 | height: 28px;
8 | width: 28px;
9 | }
10 |
11 | .sound-btn img {
12 | position: absolute;
13 | transition: all 0.2s;
14 | top: 0;
15 | left: 0;
16 | right: 0;
17 | margin: auto;
18 | width: 24px;
19 | }
20 |
21 | .img-hidden {
22 | opacity: 0;
23 | scale: 0.8;
24 | z-index: -10;
25 | }
26 |
27 | .settings ul {
28 | display: flex;
29 | flex-direction: row;
30 | justify-content: space-between;
31 | align-items: center;
32 | height: 100%;
33 | }
34 |
35 | .settings li {
36 | width: 22px;
37 | height: 22px;
38 | }
39 |
40 | .settings button,
41 | .settings a {
42 | vertical-align: middle;
43 | padding: 0;
44 | width: 100%;
45 | height: 100%;
46 | background: none;
47 | transition: all 0.2s;
48 | display: inline-block;
49 | }
50 |
51 | .settings button:focus-visible,
52 | .settings a:focus-visible {
53 | outline: 4px dashed white;
54 | }
55 |
56 | .settings img {
57 | width: 100%;
58 | height: 100%;
59 | filter: invert(1);
60 | }
61 |
62 | .settings button:active,
63 | .settings a:active {
64 | scale: 1;
65 | }
66 |
67 | @media (width >= 570px) {
68 | .settings {
69 | padding: 1.3rem 0.8rem !important;
70 | }
71 |
72 | .settings ul {
73 | flex-direction: column;
74 | justify-content: space-between;
75 | }
76 | }
77 |
78 | @media (any-hover: hover) {
79 | .settings button:hover,
80 | .settings a:hover {
81 | scale: 1.15;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/styles/skills.css:
--------------------------------------------------------------------------------
1 | .skills {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .skills-container {
7 | flex-grow: 1;
8 | gap: 8px;
9 | display: grid;
10 | grid-template-columns: repeat(auto-fit, minmax(43px, 1fr));
11 | }
12 |
13 | .skill {
14 | position: relative;
15 | flex-grow: 1;
16 | height: 52px;
17 | transition: all 0.2s;
18 | }
19 |
20 | .skill::after {
21 | content: '';
22 | border-radius: 9999px;
23 | position: absolute;
24 | top: 50%;
25 | left: 50%;
26 | transform: translate(-50%, -50%);
27 | width: 100%;
28 | max-width: 4rem;
29 | aspect-ratio: 1/1;
30 | background: var(--skill-color);
31 | }
32 |
33 | .skill img {
34 | position: absolute;
35 | width: 100%;
36 | height: 100%;
37 | padding: 8px;
38 | max-width: 3.7rem;
39 | left: 0;
40 | right: 0;
41 | margin: auto;
42 | z-index: 10;
43 | object-fit: contain;
44 | }
45 |
46 | .skills img::after {
47 | background: red;
48 | width: 100%;
49 | height: 100%;
50 | position: absolute;
51 | }
52 |
53 | @media (1050px <= width) {
54 | .skill::after {
55 | width: auto;
56 | height: 100%;
57 | max-width: 100%;
58 | }
59 |
60 | .skill {
61 | height: auto;
62 | }
63 | }
64 |
65 | @media (570px <= width <= 1050px) {
66 | .skills-container {
67 | align-items: center;
68 | }
69 | }
70 |
71 | @media (1400px <= width) {
72 | .skill::after {
73 | height: auto;
74 | width: 100%;
75 | }
76 | }
77 |
78 | @media (any-hover: hover) {
79 | .skill:hover {
80 | scale: 1.1;
81 | z-index: 100;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/public/skills/node.svg:
--------------------------------------------------------------------------------
1 | Node.js
--------------------------------------------------------------------------------
/src/styles/projectsDialog/project.css:
--------------------------------------------------------------------------------
1 | .project-links a {
2 | padding: 0.5rem 1.5rem;
3 | display: flex;
4 | align-items: center;
5 | background-color: var(--dark-body-color);
6 | transition: all 0.2s ease-in-out;
7 | text-decoration: none;
8 | color: white;
9 | border-radius: 6px;
10 | gap: 6px;
11 | line-height: 10px;
12 | }
13 |
14 | .project-links .live-link {
15 | background: var(--primary-color);
16 | }
17 |
18 | .project-links a:hover {
19 | transform: translateY(-2px);
20 | box-shadow: 0 4px 8px rgb(0 0 0 / 20%);
21 | }
22 |
23 | .current-project .project-title {
24 | font-size: 2rem;
25 | margin-top: 2rem;
26 | display: inline-block;
27 | }
28 |
29 | .current-project .project-title a {
30 | text-decoration: none;
31 | color: var(--dark-text-color);
32 | }
33 |
34 | .darkmode .current-project .project-title a {
35 | color: white;
36 | }
37 |
38 | .current-project .project-title:hover a {
39 | text-decoration: underline;
40 | }
41 |
42 | .current-project .project-description {
43 | margin-block: 1rem;
44 | padding-right: 1.5rem;
45 | text-wrap: balance;
46 | font-size: 16px;
47 | }
48 |
49 | .project-technologies {
50 | display: flex;
51 | flex-wrap: wrap;
52 | gap: 10px;
53 | font-size: 14px;
54 | }
55 |
56 | .project-skill {
57 | display: flex;
58 | gap: 10px;
59 | padding: 5px 10px;
60 | border-radius: 6px;
61 | color: white;
62 | align-items: flex-end;
63 | }
64 |
65 | .project-skill img {
66 | width: 1rem;
67 | }
68 |
69 | .project-links img {
70 | filter: invert(1);
71 | }
72 |
73 | .project-links {
74 | position: absolute;
75 | bottom: 1rem;
76 | right: 2rem;
77 | display: flex;
78 | gap: 1rem;
79 | }
80 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import LoadingScreen from '@/components/LoadingScreen'
2 | import Settings from '@/components/Settings'
3 | import Projects from '@/components/Projects'
4 | import Skills from '@/components/Skills'
5 | import Social from '@/components/Social'
6 | import Title from '@/components/Title'
7 | import About from '@/components/About'
8 | import Logo from '@/components/Logo'
9 |
10 | import playSound from '@/utils/play-sound'
11 | import { useEffect } from 'react'
12 |
13 | export default function App () {
14 | console.log('%cHELLO DEV!', 'color: #9d58fd; font-size: 1rem; font-weight: bold; font-family: sans-serif;')
15 |
16 | useEffect(() => {
17 | function clickSound (e) {
18 | if (e.target.dataset.sound) return playSound(e.target.dataset.sound)
19 | playSound('click.wav')
20 | }
21 |
22 | function onLoad () {
23 | document.querySelectorAll('.loading-page .container > div').forEach(div => {
24 | div.style.animation = 'none'
25 | })
26 | const loadingPage = document.querySelector('.loading-page')
27 |
28 | if (loadingPage) loadingPage.style.animation = 'loading-pag 1s cubic-bezier(0.53, 0.55, 0.23, 1.07) forwards'
29 | }
30 |
31 | if (document.readyState === 'complete') onLoad()
32 | else window.addEventListener('load', onLoad)
33 |
34 | document.addEventListener('click', clickSound)
35 |
36 | return () => {
37 | window.removeEventListener('load', onLoad)
38 | document.removeEventListener('click', clickSound)
39 | }
40 | }, [])
41 |
42 | return (
43 | <>
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | >
55 | )
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/ProjectsDialog/ProjectsDialog.jsx:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react'
2 | import projectsData from '@/assets/json/projects.js'
3 | import closeIcon from '@/assets/icons/close.svg'
4 | import '@/styles/projectsDialog/projectsDialog.css'
5 |
6 | import { orderArray } from '@/utils/order-array'
7 | import { useLanguage } from '@/context/language'
8 | import Images from './Images'
9 | import Project from './Project'
10 | import Slider from './Slider'
11 |
12 | export default function ProjectsDialog ({ currentProject, setCurrentProject }) {
13 | const orderProjects = orderArray(projectsData)
14 | const { translations } = useLanguage()
15 | const dialog = useRef(null)
16 |
17 | const handleClose = () => {
18 | dialog.current.classList.add('hide')
19 | setTimeout(() => {
20 | dialog.current.close()
21 | dialog.current.classList.remove('hide')
22 | }, 200)
23 | }
24 |
25 | const handleCloseOutside = (e) => {
26 | if (e.target.classList.contains('projects-dialog')) handleClose()
27 | }
28 |
29 | if (window.innerWidth < 1050) return null
30 |
31 | return (
32 |
33 |
34 |
handleClose()} title={translations.close} data-sound='switch-off.mp3'>
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/src/components/ProjectsDialog/Project.jsx:
--------------------------------------------------------------------------------
1 | import { useLanguage } from '@/context/language'
2 | import repoIcon from '@/assets/icons/repo.svg'
3 | import liveIcon from '@/assets/icons/live.svg'
4 | import '@/styles/projectsDialog/project.css'
5 |
6 | export default function Project ({ orderProjects, currentProject }) {
7 | const { translations, language } = useLanguage()
8 |
9 | return (
10 |
11 |
16 |
17 | {orderProjects[currentProject].description.long[language]}
18 |
19 |
20 |
21 | {orderProjects[currentProject].techs.map((tech, index) => {
22 | return
25 | {tech.image !== false &&
26 |
27 | }
28 | {tech.name}
29 |
30 | })}
31 |
32 |
33 |
34 | {
35 | orderProjects[currentProject]?.repo && (
36 |
37 |
38 | {translations.projects.code}
39 |
40 | )
41 | }
42 | {
43 | orderProjects[currentProject]?.live && (
44 |
45 |
46 | {translations.projects.live}
47 |
48 | )
49 | }
50 |
51 |
52 | )
53 | }
54 |
--------------------------------------------------------------------------------
/src/styles/loadingScreen.css:
--------------------------------------------------------------------------------
1 | .loading-page {
2 | width: 100vw;
3 | position: absolute;
4 | height: 100vh;
5 | top: 0;
6 | left: 0;
7 | z-index: 1000;
8 | background: white;
9 | }
10 |
11 | .darkmode .loading-page {
12 | background-color: var(--dark-body-color);
13 | }
14 |
15 | .loading-page .container {
16 | padding: 36px;
17 | padding-right: calc(36px - 12px);
18 | width: 100%;
19 | height: 100%;
20 | grid-template: 0.64fr 0.1fr 1fr 0.4fr / 0.9fr 1fr 2fr 0.15fr;
21 | margin: auto !important;
22 | max-height: 680px;
23 | grid-template-areas:
24 | 'title title about settings'
25 | 'skills social about settings'
26 | 'skills projects projects projects'
27 | 'logo projects projects projects' !important;
28 | }
29 |
30 | .loading-page .container > div {
31 | background-color: #cbc5f8;
32 | border-radius: 8px;
33 |
34 | /* animation: pulse-loading 0.5s linear infinite alternate; */
35 | transition: opacity 0.2s ease-in-out;
36 | border-top: 1px solid rgb(119 129 143 / 20%);
37 | }
38 |
39 | .loading-page .title {
40 | animation: pulse-loading 0.6s linear infinite alternate;
41 | }
42 |
43 | .loading-page .social,
44 | .loading-page .about,
45 | .loading-page .skills {
46 | animation: pulse-loading 0.6s 200ms linear infinite alternate;
47 | }
48 |
49 | .loading-page .projects,
50 | .loading-page .settings,
51 | .loading-page .logo {
52 | animation: pulse-loading 0.6s 400ms linear infinite alternate;
53 | }
54 |
55 | @keyframes pulse-loading {
56 | from {
57 | opacity: 0.5;
58 | }
59 |
60 | to {
61 | opacity: 1;
62 | }
63 | }
64 |
65 | .darkmode .loading-page .container > div {
66 | background-color: var(--dark-section-color);
67 | }
68 |
69 | @keyframes loading-pag {
70 | from {
71 | clip-path: polygon(0 0, 100% 0%, 100% 100%, 0% 100%);
72 | }
73 |
74 | to {
75 | clip-path: polygon(100% 0, 100% 0%, 100% 100%, 100% 100%);
76 | pointer-events: none;
77 | }
78 | }
79 |
80 | @media (width < 1050px) {
81 | .loading-page {
82 | display: none;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/components/Social.jsx:
--------------------------------------------------------------------------------
1 | import { GmailIcon, GithubIcon, DiscordIcon, InstagramIcon, XIcon } from '@/assets/icons/social-links'
2 | import { useLanguage } from '@/context/language'
3 | import '@/styles/social.css'
4 |
5 | export default function Social () {
6 | const { translations } = useLanguage()
7 | const socialInfo = [
8 | {
9 | name: 'Github',
10 | link: 'https://github.com/cosmoart',
11 | user: 'cosmoart',
12 | color: '#181717',
13 | icon: ,
14 | invert: true
15 | },
16 | {
17 | name: 'Gmail',
18 | link: 'mailto:cosmohydra17@gmail.com',
19 | user: 'cosmohydra17@gmail.com',
20 | color: '#EA4335',
21 | icon:
22 | },
23 | {
24 | name: 'Discord',
25 | link: 'https://discord.com/users/734087835472232559',
26 | user: 'Cosmo#3910',
27 | color: '#5865F2',
28 | icon:
29 | },
30 | {
31 | name: 'X',
32 | link: 'https://x.com/CosmoArt0',
33 | user: 'CosmoArt0',
34 | color: '#000000',
35 | icon: ,
36 | invert: true
37 | },
38 | {
39 | name: 'Instagram',
40 | link: 'https://www.instagram.com/cosmoart0/',
41 | user: 'cosmo_art0',
42 | color: 'radial-gradient(circle at 30% 107%, #fdf497 0%, #fdf497 5%, #fd5949 45%, #d6249f 60%,#285AEB 90%)',
43 | icon:
44 | }
45 | ]
46 |
47 | return (
48 |
49 | {translations.social.title}
50 |
51 |
52 |
53 | {socialInfo.map((social, index) => {
54 | return (
55 |
56 |
57 | {social.icon}
58 |
59 |
60 | )
61 | })}
62 |
63 |
64 |
65 | )
66 | }
67 |
--------------------------------------------------------------------------------
/src/styles/social.css:
--------------------------------------------------------------------------------
1 | .social {
2 | padding: 1rem 2rem !important;
3 | display: flex;
4 | align-items: center;
5 | }
6 |
7 | .social nav {
8 | width: 100%;
9 | height: 100%;
10 | }
11 |
12 | .social-container {
13 | display: flex;
14 | height: 100%;
15 | gap: 2rem;
16 | flex-wrap: wrap;
17 | justify-content: space-between;
18 | padding: 0;
19 | list-style: none;
20 | margin: 0;
21 | }
22 |
23 | .social-link {
24 | height: 24px;
25 | transition: all 0.2s;
26 | position: relative;
27 | }
28 |
29 | .social-link svg {
30 | width: 24px;
31 | vertical-align: middle;
32 | position: relative;
33 | z-index: 10;
34 | transition: all 0.2s;
35 | }
36 |
37 | .social-link a::after {
38 | content: '';
39 | position: absolute;
40 | top: -6px;
41 | left: -6px;
42 | width: 150%;
43 | height: 150%;
44 | border-radius: 9999px;
45 | transition: all 0.2s;
46 | background: transparent;
47 | scale: 0.8;
48 | }
49 |
50 | .social-link path {
51 | fill: var(--dark-text-color);
52 | }
53 |
54 | .darkmode .social-link path {
55 | fill: white;
56 | }
57 |
58 | @media (width >= 570px) {
59 | .social-container {
60 | flex-direction: column;
61 | }
62 |
63 | .social {
64 | padding: 1.3rem 1rem !important;
65 | }
66 | }
67 |
68 | @media (width >= 1050px) {
69 | .social {
70 | padding: 1rem 1.2rem !important;
71 | }
72 |
73 | .social-container {
74 | gap: 1rem;
75 | flex-direction: row;
76 | justify-content: space-between;
77 | }
78 | }
79 |
80 | @media (any-hover: hover) {
81 | .social-link:hover path {
82 | fill: white;
83 | }
84 |
85 | .social-link:hover svg {
86 | scale: 1.1;
87 | }
88 |
89 | .social-link:hover a::after {
90 | scale: 1.2;
91 | background: var(--bg-color);
92 | }
93 |
94 | .social-container .invert:hover path {
95 | fill: var(--dark-text-color);
96 | }
97 |
98 | .social-container .invert:hover img {
99 | filter: invert(0);
100 | }
101 |
102 | .social-container .invert:hover a::after {
103 | background: white;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Cosmo - Web Developer
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
46 |
47 |
48 |
49 |
50 |
51 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/assets/json/techs.json:
--------------------------------------------------------------------------------
1 | {
2 | "html": {
3 | "name": "HTML",
4 | "color": "#E34F26",
5 | "skills": true
6 | },
7 | "css": {
8 | "name": "CSS",
9 | "color": "#1572B6",
10 | "skills": true
11 | },
12 | "javascript": {
13 | "name": "JavaScript",
14 | "color": "#F7DF1E",
15 | "contrast": true,
16 | "skills": true
17 | },
18 | "typescript": {
19 | "name": "TypeScript",
20 | "color": "#3178C6",
21 | "skills": true
22 | },
23 | "react": {
24 | "name": "React",
25 | "color": "#61DBFB",
26 | "contrast": true,
27 | "skills": true
28 | },
29 | "nextjs": {
30 | "name": "Next.js",
31 | "color": "#000000",
32 | "skills": true
33 | },
34 | "tailwind": {
35 | "name": "Tailwind",
36 | "color": "#06B6D4",
37 | "skills": true
38 | },
39 | "git": {
40 | "name": "Git",
41 | "color": "#F05032",
42 | "skills": true
43 | },
44 | "figma": {
45 | "name": "Figma",
46 | "color": "#F24E1E",
47 | "skills": true
48 | },
49 | "bootstrap": {
50 | "name": "Bootstrap",
51 | "color": "#7952B3"
52 | },
53 | "sass": {
54 | "name": "Sass",
55 | "color": "#CC6699",
56 | "skills": true
57 | },
58 | "node": {
59 | "name": "Node",
60 | "color": "#339933",
61 | "skills": true
62 | },
63 | "express": {
64 | "name": "Express",
65 | "color": "#000000",
66 | "skills": true
67 | },
68 | "threejs": {
69 | "name": "Three.js",
70 | "color": "#000000",
71 | "skills": true
72 | },
73 | "blender": {
74 | "name": "Blender",
75 | "color": "#F5792A",
76 | "skills": true
77 | },
78 | "leaflet": {
79 | "name": "LeafletJS",
80 | "color": "#199900"
81 | },
82 | "axios": {
83 | "name": "Axios",
84 | "color": "#5A29E4"
85 | },
86 | "styledComponents": {
87 | "name": "Styled Components",
88 | "color": "#DB7093"
89 | },
90 | "svelte": {
91 | "name": "Svelte",
92 | "color": "#FF3E00",
93 | "skills": true
94 | },
95 | "astro": {
96 | "name": "Astro",
97 | "color": "#BC52EE",
98 | "skills": true
99 | },
100 | "supabase": {
101 | "name": "Supabase",
102 | "color": "#27b072"
103 | },
104 | "gsap": {
105 | "name": "GSAP",
106 | "color": "#88CE02"
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/components/ProjectsDialog/Images.jsx:
--------------------------------------------------------------------------------
1 | import arrowIcon from '@/assets/icons/arrow.svg'
2 | import '@/styles/projectsDialog/images.css'
3 | import { useEffect, useState } from 'react'
4 |
5 | export default function Images ({ currentProject, orderProjects }) {
6 | const [currentImg, setCurrentImg] = useState(0)
7 |
8 | useEffect(() => changueCurrentImg(0), [currentProject])
9 |
10 | function handleWheel (e) {
11 | if (orderProjects[currentProject].scroll) return
12 | changueCurrentImg(currentImg + (e.deltaY > 0 ? 1 : -1))
13 | }
14 |
15 | function changueCurrentImg (number) {
16 | if (number > orderProjects[currentProject].images.length - 1 || number < 0) return
17 | document.querySelectorAll('.project-image').forEach(projectImg => {
18 | projectImg.classList.remove('slide-left', 'slide-right')
19 | if (projectImg.id !== `project-image-${number}`) {
20 | projectImg.classList.add(Number(projectImg.id.replace('project-image-', '')) < number ? 'slide-left' : 'slide-right')
21 | }
22 | })
23 | setCurrentImg(number)
24 | }
25 |
26 | return (
27 |
28 |
29 |
30 | {
31 | orderProjects[currentProject].images && (
32 | orderProjects[currentProject].images.map((image, index) => {
33 | return
39 | }
40 | )
41 | )
42 | }
43 |
44 |
45 |
changueCurrentImg(currentImg - 1)} disabled={currentImg < 1} className='prev-img'>
46 |
47 |
48 |
changueCurrentImg(currentImg + 1)}
51 | disabled={currentImg === orderProjects[currentProject].images.length - 1}>
52 |
53 |
54 |
55 |
56 |
57 | {
58 | orderProjects[currentProject].images && (
59 | orderProjects[currentProject].images.map((_, index) => {
60 | return changueCurrentImg(index)}>
63 | }
64 | )
65 | )
66 | }
67 |
68 |
69 |
70 | )
71 | }
72 |
--------------------------------------------------------------------------------
/src/styles/projectsDialog/images.css:
--------------------------------------------------------------------------------
1 | .project-image {
2 | transition: all 0.2s ease-in-out;
3 | position: absolute;
4 | }
5 |
6 | .project-imgs-container {
7 | width: 100%;
8 | aspect-ratio: 16/9;
9 | border-radius: 0 14px 14px 0;
10 | overflow: hidden;
11 | position: relative;
12 | }
13 |
14 | .project-imgs-container article {
15 | width: 100%;
16 | height: 100%;
17 | overflow: hidden auto;
18 | position: relative;
19 | }
20 |
21 | .project-imgs-container button {
22 | height: 100%;
23 | padding: 0;
24 | background: rgb(32 32 32 / 20%);
25 | opacity: 0.4;
26 | transition: opacity 0.2s ease-in-out;
27 | position: absolute;
28 | align-items: center;
29 | display: flex;
30 | top: 0;
31 | }
32 |
33 | .prev-img {
34 | left: 0;
35 | right: auto;
36 | }
37 |
38 | .next-img {
39 | right: 0;
40 | left: auto;
41 | }
42 |
43 | .prev-img img,
44 | .next-img {
45 | filter: invert(1);
46 | }
47 |
48 | .project-imgs-container button:last-child {
49 | border-radius: 0 14px 14px 0;
50 | }
51 |
52 | .project-imgs-container button:disabled {
53 | cursor: auto;
54 | }
55 |
56 | .project-imgs-container button:not(:disabled):hover {
57 | opacity: 1;
58 | background: rgb(32 32 32 / 30%);
59 | }
60 |
61 | .project-imgs-container button:not(:disabled):hover img {
62 | scale: 1.1;
63 | }
64 |
65 | .images-slider {
66 | position: absolute;
67 | bottom: 0;
68 | right: 0;
69 | left: 0;
70 | margin: auto;
71 | display: flex;
72 | gap: 8px;
73 | width: fit-content;
74 | background: white;
75 | padding: 5px 8px;
76 | font-weight: 600;
77 | border-radius: 8px 8px 0 0;
78 | align-items: center;
79 | }
80 |
81 | .darkmode .images-slider {
82 | background: #1a1924e0;
83 | backdrop-filter: blur(2px);
84 | }
85 |
86 | .images-slider li {
87 | background: var(--dark-text-color);
88 | border-radius: 100%;
89 | aspect-ratio: 1/1;
90 | width: 14px;
91 | cursor: pointer;
92 | transition: all 0.2s ease-in-out;
93 | }
94 |
95 | .darkmode .images-slider li {
96 | background: white;
97 | }
98 |
99 | .images-slider li:hover {
100 | scale: 1.1;
101 | background-color: var(--primary-color);
102 | }
103 |
104 | .selected-p {
105 | background-color: var(--primary-color) !important;
106 | scale: 1.15 !important;
107 | }
108 |
109 | /* Slides ============================ */
110 | .slide-left {
111 | transform: translateX(-100%);
112 | pointer-events: none;
113 | }
114 |
115 | .slide-right {
116 | transform: translateX(100%);
117 | pointer-events: none;
118 | }
119 |
--------------------------------------------------------------------------------
/public/skills/styled components.svg:
--------------------------------------------------------------------------------
1 | styled-components
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | # Cosmo - Portafolio
12 |
13 | My portafolio, made with React, CSS y JS. Design made by me in figma.
14 |
15 |
View Demo
16 | ·
17 |
Report Bug
18 | ·
19 |
Send suggestion
20 |
21 |
22 |
23 |
24 |
25 |
26 | Table of contents
27 |
28 | - [About The Project](#about-the-project)
29 | - [Screenshots](#screenshots)
30 | - [Built With](#built-with)
31 | - [License](#license)
32 | - [Contact](#contact)
33 |
34 |
35 |
36 | ## About The Project
37 |
38 | This is my portfolio, I made it with React, CSS and JS. I wanted to make it as pure as possible, without using any library or framework. I also made the design in Figma, inspired by many other incredible portfolios.
39 |
40 |
41 |
42 | ## Screenshots
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | ### Built With
71 |
72 | * [Vite](https://vitejs.dev/)
73 | * [React.js](https://reactjs.org/)
74 |
75 |
76 |
77 | ## License
78 |
79 | Distributed under the MIT License. See [`LICENSE`](https://github.com/cosmoart/portafolio/blob/main/LICENCE) for more information.
80 |
81 |
82 |
83 |
84 | ## Contact
85 |
86 | - My website - [cosmoart.vercel.app](https://cosmoart.vercel.app)
87 | - Twitter - [@CosmoArt0](https://twitter.com/cosmoart0)
88 | - Instagram - [@cosmo_art0](https://www.instagram.com/cosmo_art0/)
89 |
90 | ⬆️ Back to top
91 |
--------------------------------------------------------------------------------
/public/skills/react.svg:
--------------------------------------------------------------------------------
1 | React
--------------------------------------------------------------------------------
/src/styles/projectsDialog/projectsDialog.css:
--------------------------------------------------------------------------------
1 | .projects-dialog {
2 | width: 90%;
3 | max-width: 1100px;
4 | height: 90%;
5 | max-height: 780px;
6 | top: 0;
7 | bottom: 0;
8 | z-index: 100;
9 | flex-direction: column;
10 | justify-content: center;
11 | gap: 2rem;
12 | background: transparent;
13 | border: none;
14 | }
15 |
16 | .projects-dialog.hide {
17 | animation: hide 0.2s ease-in-out forwards !important;
18 | }
19 |
20 | .projects-dialog.hide::backdrop {
21 | animation: hideblur 0.2s ease-in-out forwards;
22 | }
23 |
24 | .projects-dialog[open] {
25 | display: flex;
26 | animation: appear 0.2s ease-in-out;
27 | }
28 |
29 | @keyframes appear {
30 | from {
31 | opacity: 0;
32 | scale: 0.9;
33 | }
34 |
35 | to {
36 | opacity: 1;
37 | scale: 1;
38 | }
39 | }
40 |
41 | @keyframes hide {
42 | to {
43 | opacity: 0;
44 | scale: 0.9;
45 | }
46 | }
47 |
48 | @keyframes hideblur {
49 | to {
50 | backdrop-filter: blur(0);
51 | }
52 | }
53 |
54 | .projects-dialog::backdrop {
55 | background: #0f0f136b;
56 | backdrop-filter: blur(6px);
57 | }
58 |
59 | .darkmode .projects-dialog {
60 | color: white;
61 | }
62 |
63 | .current-project {
64 | background: white;
65 | backdrop-filter: blur(10px);
66 | border-radius: 10px;
67 | box-shadow: inset -1px 0 2px rgb(255 255 255 / 25%),
68 | inset 0 1px 2px rgb(255 255 255 / 25%),
69 | inset 0 0 0 1px rgb(255 255 255 / 3%);
70 | display: flex;
71 | gap: 2rem;
72 | justify-content: center;
73 | height: 26rem;
74 | }
75 |
76 | .darkmode .current-project {
77 | background: #0f0f139f;
78 | }
79 |
80 | .current-project section:first-of-type {
81 | width: 100%;
82 | display: flex;
83 | align-items: center;
84 | overflow: hidden;
85 | position: relative;
86 | }
87 |
88 | .current-project section:last-of-type {
89 | width: 50%;
90 | padding-right: 2rem;
91 | }
92 |
93 | .current-project .poster {
94 | width: 50%;
95 | object-fit: cover;
96 | border-radius: 10px 0 0;
97 | }
98 |
99 | .current-project > article {
100 | width: 50%;
101 | }
102 |
103 | .skill-contrast img {
104 | filter: invert(1) !important;
105 | }
106 |
107 | .projects-dialog .close {
108 | position: absolute;
109 | top: -0.6rem;
110 | right: -0.6rem;
111 | border-radius: 100%;
112 | cursor: pointer;
113 | width: 2rem;
114 | height: 2rem;
115 | display: grid;
116 | place-items: center;
117 | transition: all 0.2s ease-in-out;
118 | background: var(--dark-body-color);
119 | }
120 |
121 | .darkmode .projects-dialog .close {
122 | background: white;
123 | }
124 |
125 | .projects-dialog .close:hover {
126 | scale: 1.07;
127 | }
128 |
129 | .projects-dialog .close img {
130 | transition: all 0.2s ease-in-out;
131 | filter: invert(1);
132 | }
133 |
134 | .darkmode .projects-dialog .close img {
135 | filter: invert(0);
136 | }
137 |
138 | .projects-dialog .close:hover img {
139 | scale: 1.13;
140 | }
141 |
142 | .skill-contrast {
143 | color: black !important;
144 | font-weight: 600;
145 | }
146 |
147 | @media (width <= 1050px) {
148 | .projects-dialog {
149 | display: none !important;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/components/Projects.jsx:
--------------------------------------------------------------------------------
1 | import projectssData from '@/assets/json/projects.js'
2 | import { useLanguage } from '@/context/language'
3 | import { orderArray } from '@/utils/order-array'
4 | import { useState } from 'react'
5 | import playSound from '@/utils/play-sound'
6 |
7 | import repoIcon from '@/assets/icons/repo.svg'
8 | import liveIcon from '@/assets/icons/live.svg'
9 | import ProjectsDialog from './ProjectsDialog/ProjectsDialog'
10 | import '@/styles/projects.css'
11 |
12 | export default function Projects () {
13 | const [currentProject, setCurrentProject] = useState(Math.floor(projectssData.length / 2))
14 | const { translations, language } = useLanguage()
15 | const orderProjects = orderArray(projectssData)
16 |
17 | function handleProjectModal (name) {
18 | if (window.innerWidth < 1050) return
19 | setCurrentProject(orderProjects.findIndex(el => el.name === name))
20 | document.querySelector('.projects-dialog').showModal()
21 | }
22 |
23 | function handleWheel (e) {
24 | if (window.innerWidth < 1050) return
25 | e.preventDefault()
26 | document.querySelector('.projects-container').scrollBy({
27 | left: e.deltaY
28 | })
29 | }
30 |
31 | return (
32 |
33 |
34 |
35 |
42 |
43 |
44 | {projectssData.map((project, index) => {
45 | return (
46 |
handleProjectModal(project.name)} onMouseEnter={() => playSound('hover.ogg', 0.02)} tabIndex='0'
47 | style={{ '--animation-delay': `${index * 0.1}s` }}>
48 |
49 |
50 |
{translations.projects.more}
51 |
52 |
74 |
75 | )
76 | })}
77 |
78 |
79 |
80 |
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
/src/components/Settings.jsx:
--------------------------------------------------------------------------------
1 | import '@/styles/settings.css'
2 | import { useEffect, useState } from 'react'
3 | import { useLanguage } from '@/context/language'
4 |
5 | import LanguageIcon from '@/assets/settings/language.svg'
6 | import SoundOnIcon from '@/assets/settings/sound-on.svg'
7 | import SoundOffIcon from '@/assets/settings/sound-off.svg'
8 | import DarkModeIcon from '@/assets/settings/darkmode.svg'
9 | import LightModeIcon from '@/assets/settings/lightmode.svg'
10 | import ExternalLinkIcon from '@/assets/settings/external-link.svg'
11 | import StarIcon from '@/assets/settings/star.svg'
12 |
13 | export default function Settings () {
14 | const darkModeLocal = localStorage.getItem('darkmode')
15 | ? localStorage.getItem('darkmode') === 'true'
16 | : true
17 | const soundLocal = localStorage.getItem('sound')
18 | ? localStorage.getItem('sound') === 'true'
19 | : true
20 |
21 | const { translations, language, changeLanguage } = useLanguage()
22 | const [darkmode, setDarkmode] = useState(darkModeLocal)
23 | const [sound, setSound] = useState(soundLocal)
24 |
25 | function handleSound () {
26 | setSound(!sound)
27 | localStorage.setItem('sound', !sound)
28 | }
29 |
30 | useEffect(() => {
31 | document.documentElement.classList.toggle('darkmode', darkmode)
32 | localStorage.setItem('darkmode', darkmode)
33 | }, [darkmode])
34 |
35 | return (
36 |
37 |
38 |
39 | setDarkmode(!darkmode)}
42 | title={`${translations.settings[darkmode ? 'lightmode' : 'darkmode']}`}>
43 |
44 |
45 |
46 |
47 |
48 |
49 | changeLanguage(language === 'es' ? 'en' : 'es')}
51 | title={`${translations.settings[language === 'es' ? 'english' : 'spanish']}`}
52 | className='language-btn'>
53 |
54 | ES
55 | EN
56 |
57 |
58 |
59 |
60 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
/src/assets/icons/social-links.jsx:
--------------------------------------------------------------------------------
1 | export function DiscordIcon () {
2 | return (
3 |
4 | )
5 | }
6 | export function GithubIcon () {
7 | return (
8 | GitHub
9 | )
10 | }
11 | export function InstagramIcon () {
12 | return (
13 | Instagram
14 | )
15 | }
16 | export function XIcon () {
17 | return (
18 | X
19 | )
20 | }
21 |
22 | export function GmailIcon () {
23 | return (
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/src/assets/json/projects.js:
--------------------------------------------------------------------------------
1 | import techs from './techs.json'
2 |
3 | export default [
4 | {
5 | name: 'Quizi',
6 | repo: 'https://github.com/cosmoart/quiz-game',
7 | live: 'https://quizi.vercel.app',
8 | description: {
9 | short: {
10 | en: 'Quiz game with questions made with AI, game modes and wildcards.',
11 | es: 'Juego de preguntas con IA, modos de juego y comodines.'
12 | },
13 | long: {
14 | en: 'Quiz/trivia game with AI generated questions, 3 game modes and wildcards. Made with Tailwind and the Cohere API in Next.js.',
15 | es: 'Juego de preguntas generadas por IA con 3 modos de juego y comodines. Hecha con Tailwind y la API de Cohere en Next.js.'
16 | }
17 | },
18 | techs: [techs.css, techs.javascript, techs.react],
19 | poster: '/projects-images/quizi/poster2.svg',
20 | images: [
21 | '/projects-images/quizi/home.avif',
22 | '/projects-images/quizi/play-classic.avif',
23 | '/projects-images/quizi/play-infinity.avif'
24 | ]
25 | },
26 | {
27 | name: 'Freesets',
28 | repo: 'https://github.com/cosmoart/Freesets',
29 | live: 'https://freesets.vercel.app/',
30 | description: {
31 | short: {
32 | en: 'Collection of free multimedia resources like icons, images, illustrations...',
33 | es: 'Colección de recursos gratuitos como iconos, imágenes, ilustraciones...'
34 | },
35 | long: {
36 | en: 'Collection of free resources. Made with Svelte and Tailwind using Supabase for the database and Cloudinary for image storage. With an average of 30 visitors daily and +200 resources.',
37 | es: 'Colección de recursos gratuitos. Hecha con Svelte y Tailwind usando Supabase para la base de datos y Cloudinary para el almacenamiento de imágenes. Con una media de 30 visitas diarias y +200 recursos.'
38 | }
39 | },
40 | techs: [techs.svelte, techs.javascript, techs.supabase],
41 | poster: '/projects-images/freesets/poster.avif',
42 | images: [
43 | '/projects-images/freesets/home.avif',
44 | '/projects-images/freesets/icons.avif',
45 | '/projects-images/freesets/light.avif'
46 | ]
47 | },
48 | {
49 | name: 'Jobzilla',
50 | repo: 'https://github.com/cosmoart/jobzilla',
51 | live: 'https://jobzilla.vercel.app/',
52 | description: {
53 | short: {
54 | en: 'Web in Spanish for job and companies search, with filters and maps.',
55 | es: 'Buscador de empleos y empresas, con filtros y mapas.'
56 | },
57 | long: {
58 | en: 'Website in Spanish for job and company search, with filters and maps. The data is obtained from the Infojobs API and the map is made with Leaflet and the MapTiler API.',
59 | es: 'Página web para la búsqueda de empleos y empresas, con filtros y mapas. Los datos se obtienen de la API de Infojobs y el mapa está hecho con Leaflet y la API de MapTiler.'
60 | }
61 | },
62 | techs: [techs.nextjs, techs.leaflet, techs.tailwind],
63 | poster: '/projects-images/jobzilla/poster.avif',
64 | images: [
65 | '/projects-images/jobzilla/home.avif',
66 | '/projects-images/jobzilla/companies.avif',
67 | '/projects-images/jobzilla/company.avif',
68 | '/projects-images/jobzilla/jobs.avif',
69 | '/projects-images/jobzilla/job.avif'
70 | ]
71 | },
72 | {
73 | name: 'CodeDev',
74 | repo: 'https://github.com/cosmoart/CodeDev',
75 | live: 'https://coodedev.netlify.app/',
76 | description: {
77 | short: {
78 | en: 'Track and obtain information based on an IP address or a domain.',
79 | es: 'Rastrea y obten información basada en una dirección IP o un dominio.'
80 | },
81 | long: {
82 | en: 'Track and obtain information based on an IP address or a domain. Made with the IPify IP Geolocation API and LeafletJS.',
83 | es: 'Rastrea y obten información basada en una dirección IP o un dominio. Hecho con la API de IP Geolocation y LeafletJS.'
84 | }
85 | },
86 | techs: [
87 | techs.astro,
88 | techs.tailwind,
89 | techs.gsap,
90 | techs.threejs
91 | ],
92 | poster: '/projects-images/codedev/poster.avif',
93 | images: [
94 | '/projects-images/codedev/home.avif',
95 | '/projects-images/codedev/about.avif',
96 | '/projects-images/codedev/projects.avif',
97 | '/projects-images/codedev/contact.avif'
98 | ],
99 | scroll: true
100 | },
101 | {
102 | name: 'Space tourism',
103 | repo: 'https://github.com/cosmoart/Space-tourism',
104 | live: 'https://space-tourists.netlify.app',
105 | description: {
106 | short: {
107 | en: 'Landing page for a fictional space travel company.',
108 | es: 'Página web para una empresa ficticia de viajes espaciales.'
109 | },
110 | long: {
111 | en: 'Landing page for a fictional space travel company. Made with CSS and React in Next.js.',
112 | es: 'Página web para una empresa ficticia de viajes espaciales. Hecha con CSS y React en Next.js.'
113 | }
114 | },
115 | techs: [techs.css, techs.javascript, techs.nextjs, techs.react],
116 | poster: '/projects-images/space-tourism/poster.avif',
117 | images: [
118 | '/projects-images/space-tourism/home.avif',
119 | '/projects-images/space-tourism/destination.avif',
120 | '/projects-images/space-tourism/crew.avif',
121 | '/projects-images/space-tourism/technology.avif'
122 | ]
123 | }
124 | ]
125 |
--------------------------------------------------------------------------------
/src/styles/index.css:
--------------------------------------------------------------------------------
1 | html {
2 | scroll-behavior: smooth;
3 | font-family: Poppins, sans-serif !important;
4 | }
5 |
6 | * {
7 | transition: background-color 0.2s ease-in-out;
8 | }
9 |
10 | body {
11 | display: grid;
12 | place-items: center;
13 | min-height: 100vh;
14 | background-color: #ebebfa;
15 | }
16 |
17 | #root {
18 | width: 100%;
19 | }
20 |
21 | img {
22 | max-width: 100%;
23 | }
24 |
25 | ul {
26 | list-style: none;
27 | padding: 0;
28 | margin: 0;
29 | }
30 |
31 | h1,
32 | h2,
33 | h3,
34 | h4 {
35 | margin: 0;
36 | }
37 |
38 | h2 {
39 | margin-bottom: 1rem;
40 | font-weight: 600;
41 | font-size: 1.4rem;
42 | }
43 |
44 | .hidden {
45 | display: none;
46 | }
47 |
48 | button {
49 | cursor: pointer;
50 | border: none;
51 | }
52 |
53 | main {
54 | padding: 24px;
55 | }
56 |
57 | .darkmode body {
58 | background-color: var(--dark-body-color);
59 | }
60 |
61 | main,
62 | .loading-page .container {
63 | height: 100%;
64 | width: 100%;
65 | display: grid;
66 | gap: 24px;
67 | grid-template-areas:
68 | 'title title'
69 | 'about about'
70 | 'skills skills'
71 | 'social social'
72 | 'settings settings'
73 | 'projects projects'
74 | 'logo logo';
75 | grid-template-columns: 1fr;
76 | max-width: 900px;
77 | margin: auto;
78 | }
79 |
80 | .logo,
81 | .settings {
82 | box-shadow: var(--primary-color) 0 5px 30px -10px;
83 | background: var(--primary-color) !important;
84 | }
85 |
86 | .darkmode {
87 | color: white;
88 | }
89 |
90 | .section {
91 | border-radius: 8px;
92 | padding: 24px;
93 | background: #cbc5f8;
94 | border-top: 1px solid rgb(119 129 143 / 20%);
95 | }
96 |
97 | .darkmode .section {
98 | background: var(--dark-section-color);
99 | }
100 |
101 | .title {
102 | grid-area: title;
103 | position: relative;
104 | }
105 |
106 | .logo {
107 | grid-area: logo;
108 | }
109 |
110 | .skills {
111 | grid-area: skills;
112 | }
113 |
114 | .about {
115 | grid-area: about;
116 | }
117 |
118 | .settings {
119 | grid-area: settings;
120 | }
121 |
122 | .social {
123 | grid-area: social;
124 | }
125 |
126 | .projects {
127 | grid-area: projects;
128 | position: relative;
129 | }
130 |
131 | /* Noise bg ============= */
132 | .title::after,
133 | .projects::after {
134 | content: '';
135 | position: absolute;
136 | top: 0;
137 | left: 0;
138 | width: 100%;
139 | height: 100%;
140 | background: url('/noise.gif');
141 | pointer-events: none;
142 | z-index: 1;
143 | opacity: 0.03;
144 | }
145 |
146 | /* Poppins regular, semibold y bold */
147 | @font-face {
148 | font-family: Poppins;
149 | src: url('@/assets/fonts/Poppins-Light.ttf');
150 | font-weight: 300;
151 | font-display: swap;
152 | }
153 |
154 | @font-face {
155 | font-family: Poppins;
156 | src: url('@/assets/fonts/Poppins-Regular.ttf');
157 | font-weight: 400;
158 | font-display: swap;
159 | }
160 |
161 | @font-face {
162 | font-family: Poppins;
163 | src: url('@/assets/fonts/Poppins-SemiBold.ttf');
164 | font-weight: 600;
165 | font-display: swap;
166 | }
167 |
168 | @font-face {
169 | font-family: Poppins;
170 | src: url('@/assets/fonts/Poppins-Bold.ttf');
171 | font-weight: 700;
172 | font-display: swap;
173 | }
174 |
175 | /* ============ SELECTION & FOCUS ============ */
176 |
177 | ::selection {
178 | background: var(--primary-color);
179 | color: white;
180 | -webkit-text-fill-color: white;
181 | }
182 |
183 | :focus-visible {
184 | border-radius: 3px;
185 | outline: 4px dashed var(--primary-color);
186 | outline-offset: 4px;
187 | }
188 |
189 | /* ============ SCROLL-BAR ========= */
190 | ::-webkit-scrollbar {
191 | width: 12px;
192 | }
193 |
194 | ::-webkit-scrollbar-track {
195 | background: white;
196 | }
197 |
198 | ::-webkit-scrollbar-thumb {
199 | border-radius: 10px;
200 | border: 3px solid white;
201 | background: #1f2937;
202 | }
203 |
204 | ::-webkit-scrollbar-thumb:hover {
205 | background: #4c6180;
206 | }
207 |
208 | .darkmode ::-webkit-scrollbar-track {
209 | background: #1f2937;
210 | }
211 |
212 | .darkmode ::-webkit-scrollbar-thumb {
213 | border: 3px solid #1f2937;
214 | background: #cbc5f8;
215 | }
216 |
217 | @media (570px <= width) {
218 | main,
219 | .loading-page .container {
220 | padding: 36px;
221 | grid-template-areas:
222 | 'title title title'
223 | 'about about about'
224 | 'skills social settings'
225 | 'projects projects projects'
226 | 'logo logo logo';
227 | }
228 | }
229 |
230 | @media (1050px <= width) {
231 | main,
232 | .loading-page .container {
233 | grid-template: 0.3fr 0.1fr 1fr 0.4fr / 0.9fr 1fr 2fr 0.15fr;
234 | margin: auto;
235 | max-height: 720px;
236 | grid-template-areas:
237 | 'title title about settings'
238 | 'skills social about settings'
239 | 'skills projects projects projects'
240 | 'logo projects projects projects';
241 | position: absolute;
242 | inset: 0;
243 | max-width: 1500px;
244 | }
245 | }
246 |
247 | /* ==== Reduce motion ===== */
248 |
249 | @media (prefers-reduced-motion: reduce) {
250 | *,
251 | *::before,
252 | *::after {
253 | animation-duration: 0.01ms !important;
254 | animation-iteration-count: 1 !important;
255 | transition-duration: 0.01ms !important;
256 | scroll-behavior: auto !important;
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/public/skills/sass.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/styles/projects.css:
--------------------------------------------------------------------------------
1 | .projects {
2 | display: flex;
3 | flex-direction: column;
4 | padding: 0 !important;
5 | overflow: hidden;
6 | position: relative;
7 | }
8 |
9 | .projects-title {
10 | display: flex;
11 | justify-content: space-between;
12 | margin-inline: 24px;
13 | margin-top: 19px;
14 | margin-bottom: 4px;
15 | }
16 |
17 | .projects-title h2 {
18 | margin: 0;
19 | }
20 |
21 | .projects-title a {
22 | z-index: 10;
23 | font-size: 14px;
24 | transition: opacity 0.2s ease-in-out;
25 | }
26 |
27 | .darkmode .project-title a {
28 | color: white;
29 | }
30 |
31 | .projects-title a:hover {
32 | opacity: 0.8;
33 | }
34 |
35 | .project-title {
36 | font-size: 1.1rem;
37 | font-weight: 600;
38 | margin-bottom: 7px;
39 | }
40 |
41 | .projects-container .project-links {
42 | position: static;
43 | justify-content: flex-end;
44 | margin-bottom: 0;
45 | }
46 |
47 | .projects-container {
48 | z-index: 10;
49 | flex-grow: 1;
50 | display: flex;
51 | overflow: auto;
52 | gap: 1rem;
53 | padding: 15px;
54 | flex-direction: column;
55 | scroll-behavior: smooth;
56 | }
57 |
58 | .projects-container::-webkit-scrollbar {
59 | height: 4px;
60 | }
61 |
62 | .projects-container::-webkit-scrollbar-thumb {
63 | border: none !important;
64 | }
65 |
66 | .code-btn {
67 | position: relative;
68 | }
69 |
70 | .language-btn {
71 | position: relative;
72 | display: flex !important;
73 | justify-content: center;
74 | align-items: center;
75 | }
76 |
77 | .code-btn img {
78 | position: absolute;
79 | transition: 0.2s ease-in-out;
80 | }
81 |
82 | .language-btn img,
83 | .language-btn span {
84 | position: absolute;
85 | transition: 0.2s ease-in-out;
86 | color: white;
87 | font-weight: 500;
88 | }
89 |
90 | .image-container {
91 | position: relative;
92 | flex-grow: 1;
93 | height: 100%;
94 | }
95 |
96 | .image-container img {
97 | height: 100%;
98 | display: block;
99 | width: 100%;
100 | object-fit: cover;
101 | }
102 |
103 | .language-btn span {
104 | opacity: 0;
105 | }
106 |
107 | .project {
108 | max-width: 32rem;
109 | height: fit-content;
110 | margin: auto;
111 | cursor: pointer;
112 | flex-shrink: 0;
113 | border-radius: 8px;
114 | overflow: hidden;
115 | display: flex;
116 | flex-direction: column;
117 | transition: all 0.2s ease-in-out;
118 | }
119 |
120 | .see-more {
121 | color: var(--dark-text-color);
122 | font-weight: 600;
123 | font-size: 14px;
124 | display: flex;
125 | align-items: center;
126 | position: relative;
127 | overflow: hidden;
128 | }
129 |
130 | .see-more img {
131 | width: 18px;
132 | transition: right 0.16s ease-in-out;
133 | position: absolute;
134 | right: -1.1rem;
135 | }
136 |
137 | .see-more span {
138 | margin-right: 8px;
139 | transition: margin-right 0.16s ease-in-out;
140 | }
141 |
142 | .see-more:hover span {
143 | margin-right: 1.2rem;
144 | }
145 |
146 | .see-more:hover img {
147 | right: 0;
148 | }
149 |
150 | .darkmode .see-more img {
151 | filter: invert(1);
152 | }
153 |
154 | .darkmode .see-more {
155 | color: white;
156 | font-weight: 400;
157 | }
158 |
159 | .project-info {
160 | padding: 1rem;
161 | background: white;
162 | }
163 |
164 | .project-viewmore {
165 | position: absolute;
166 | width: 100%;
167 | height: 100%;
168 | top: 0;
169 | left: 0;
170 | background-color: rgb(0 0 0 / 60%);
171 | backdrop-filter: blur(3px);
172 | display: flex;
173 | justify-content: center;
174 | align-items: center;
175 | color: white;
176 | opacity: 0;
177 | transition: opacity 0.2s ease-in-out;
178 | border-radius: 8px 8px 0 0;
179 | }
180 |
181 | .darkmode .project-info {
182 | background: #272333;
183 | }
184 |
185 | .project-description {
186 | font-size: 14px;
187 | margin: 5px 0;
188 | font-weight: 400;
189 | }
190 |
191 | .darkmode .project-description {
192 | font-weight: 300;
193 | }
194 |
195 | .box:not(:first-child) {
196 | display: none;
197 | }
198 |
199 | .code-btn img:last-of-type {
200 | opacity: 0;
201 | }
202 |
203 | /* ==== Wave ==== */
204 |
205 | .wave {
206 | background: url('/wave.svg') repeat-x;
207 | position: absolute;
208 | bottom: -45px;
209 | width: 6400px;
210 | height: 85%;
211 | animation: wave 12s cubic-bezier(0.36, 0.45, 0.63, 0.53) infinite,
212 | swell 8s ease -1.25s infinite;
213 | transform: translate3d(0, 0, 0);
214 | opacity: 0.1;
215 | }
216 |
217 | @keyframes wave {
218 | 0% {
219 | margin-left: 0;
220 | }
221 |
222 | 100% {
223 | margin-left: -1600px;
224 | }
225 | }
226 |
227 | @keyframes swell {
228 | 0%,
229 | 100% {
230 | transform: translate3d(0, -40px, 0);
231 | }
232 |
233 | 50% {
234 | transform: translate3d(0, -10px, 0);
235 | }
236 | }
237 |
238 | @media (width >= 570px) {
239 | .projects-container {
240 | display: grid;
241 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
242 | }
243 |
244 | .project {
245 | height: 100%;
246 | }
247 | }
248 |
249 | @media (width >= 1050px) {
250 | .projects-container {
251 | display: flex;
252 | flex-direction: row;
253 | padding: 10px;
254 | padding-bottom: 20px;
255 | margin: 0 24px 0 14px;
256 | }
257 |
258 | .projects-container .project-links {
259 | display: none;
260 | }
261 |
262 | .project {
263 | width: 20.5rem;
264 | max-height: 19rem;
265 | }
266 |
267 | .image-container img {
268 | position: absolute;
269 | }
270 | }
271 |
272 | @media (any-hover: hover) {
273 | .code-btn:hover img {
274 | scale: 1.1;
275 | }
276 |
277 | .language-btn:hover img {
278 | opacity: 0;
279 | }
280 |
281 | .code-btn:hover img:last-of-type {
282 | opacity: 1;
283 | }
284 |
285 | .code-btn:hover img:first-of-type {
286 | opacity: 0;
287 | }
288 |
289 | .project:hover {
290 | /* scale: 1.04; */
291 | translate: 0 -3px;
292 | }
293 |
294 | .project:hover .project-viewmore {
295 | opacity: 1;
296 | }
297 |
298 | .language-btn:hover span {
299 | opacity: 1;
300 | }
301 | }
302 |
--------------------------------------------------------------------------------
/public/skills/gsap.svg:
--------------------------------------------------------------------------------
1 | GreenSock
--------------------------------------------------------------------------------
/public/projects-images/quizi/poster.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects-images/quizi/poster2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------