├── 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 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 | 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 | 17 | 20 |
21 | 22 |
23 | {orderProjects.map((project, index) => { 24 | return ( 25 |
setCurrentProject(index)}> 26 | {project.name} 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 | <About /> 48 | <Skills /> 49 | <Social /> 50 | <Logo /> 51 | <Projects /> 52 | <Settings /> 53 | </main> 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 | <dialog className='projects-dialog' onClick={handleCloseOutside} ref={dialog}> 33 | <div className='current-project'> 34 | <button className='close' onClick={() => handleClose()} title={translations.close} data-sound='switch-off.mp3'> 35 | <img src={closeIcon} alt={translations.close} aria-label={translations.close} data-sound='switch-off.mp3' /> 36 | </button> 37 | 38 | <Images currentProject={currentProject} orderProjects={orderProjects} /> 39 | <Project orderProjects={orderProjects} currentProject={currentProject} /> 40 | </div> 41 | 42 | <Slider setCurrentProject={setCurrentProject} currentProject={currentProject} orderProjects={orderProjects} /> 43 | </dialog> 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 | <section> 11 | <h3 className='project-title'> 12 | <a href={orderProjects[currentProject].live} target='_blank' rel='noopener noreferrer' > 13 | {orderProjects[currentProject].name} 14 | </a> 15 | </h3> 16 | <p className='project-description'> 17 | {orderProjects[currentProject].description.long[language]} 18 | </p> 19 | 20 | <ul className='project-technologies'> 21 | {orderProjects[currentProject].techs.map((tech, index) => { 22 | return <li key={index} 23 | className={`project-skill ${tech.contrast ? 'skill-contrast' : ''}`} 24 | style={{ backgroundColor: tech.color ?? 'var(--primary-color)' }}> 25 | {tech.image !== false && 26 | <img src={`/skills/${tech.name.toLowerCase()}.svg`} alt={tech.name + ' icon'} /> 27 | } 28 | {tech.name} 29 | </li> 30 | })} 31 | </ul> 32 | 33 | <p className='project-links'> 34 | { 35 | orderProjects[currentProject]?.repo && ( 36 | <a href={orderProjects[currentProject].repo} target='_blank' rel='noopener noreferrer' className='code-link'> 37 | <img src={repoIcon} alt='' /> 38 | {translations.projects.code} 39 | </a> 40 | ) 41 | } 42 | { 43 | orderProjects[currentProject]?.live && ( 44 | <a href={orderProjects[currentProject].live} target='_blank' rel='noopener noreferrer' className='live-link'> 45 | <img src={liveIcon} alt='' /> 46 | {translations.projects.live} 47 | </a> 48 | ) 49 | } 50 | </p> 51 | </section> 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: <GithubIcon />, 14 | invert: true 15 | }, 16 | { 17 | name: 'Gmail', 18 | link: 'mailto:cosmohydra17@gmail.com', 19 | user: 'cosmohydra17@gmail.com', 20 | color: '#EA4335', 21 | icon: <GmailIcon /> 22 | }, 23 | { 24 | name: 'Discord', 25 | link: 'https://discord.com/users/734087835472232559', 26 | user: 'Cosmo#3910', 27 | color: '#5865F2', 28 | icon: <DiscordIcon /> 29 | }, 30 | { 31 | name: 'X', 32 | link: 'https://x.com/CosmoArt0', 33 | user: 'CosmoArt0', 34 | color: '#000000', 35 | icon: <XIcon />, 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: <InstagramIcon /> 44 | } 45 | ] 46 | 47 | return ( 48 | <section className='section social'> 49 | <h2 className='hidden'>{translations.social.title}</h2> 50 | 51 | <nav> 52 | <ul className='social-container'> 53 | {socialInfo.map((social, index) => { 54 | return ( 55 | <li key={index} className={`social-link ${social.invert ? 'invert' : ''} `} style={{ '--bg-color': social.color, '--animation-delay': `${index * 0.1}s` }} title={social.name} > 56 | <a href={social.link} target='_blank' rel='noopener noreferrer' aria-label={social.name}> 57 | {social.icon} 58 | </a> 59 | </li> 60 | ) 61 | })} 62 | </ul> 63 | </nav> 64 | </section> 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 | <!DOCTYPE html> 2 | <html lang="en"> 3 | 4 | <head> 5 | <meta charset="UTF-8" /> 6 | <title>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 {`Project 39 | } 40 | ) 41 | ) 42 | } 43 |
44 | 45 | 48 | 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 | Portafolio preview 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 | 49 | 52 | 55 | 56 | 57 | 60 | 63 | 66 | 67 |
47 | 48 | 50 | 51 | 53 | 54 |
58 | 59 | 61 | 62 | 64 | 65 |
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 |
36 |

{translations.projects.title}

37 | 38 | {translations.projects.more} 39 | 40 | 41 |
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 | {project.name} 50 |
{translations.projects.more}
51 |
52 |
53 |

{project.name}

54 |

{project.description.short[language]}

55 |

56 | { 57 | project.repo && ( 58 | 59 | Repository 60 | {translations.projects.code} 61 | 62 | ) 63 | } 64 | { 65 | project.live && ( 66 | 67 | Go to project 68 | {translations.projects.live} 69 | 70 | ) 71 | } 72 |

73 |
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 | 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 | --------------------------------------------------------------------------------