{"id":18632340,"url":"https://github.com/rafaeldevvv/portfolio","last_synced_at":"2026-04-11T18:03:06.643Z","repository":{"id":184557126,"uuid":"669626579","full_name":"rafaeldevvv/portfolio","owner":"rafaeldevvv","description":"My very own portfolio!","archived":false,"fork":false,"pushed_at":"2024-07-20T23:51:50.000Z","size":8226,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-12-27T07:42:52.418Z","etag":null,"topics":["babel","css","html","javascript","portfolio","react","sass","webpack"],"latest_commit_sha":null,"homepage":"https://rafaeldevvv.github.io/portfolio/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rafaeldevvv.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-22T22:39:46.000Z","updated_at":"2024-07-20T23:51:53.000Z","dependencies_parsed_at":"2023-07-29T01:51:10.529Z","dependency_job_id":"a669e2e7-ec76-4a37-a6ef-ec4ce9fce60f","html_url":"https://github.com/rafaeldevvv/portfolio","commit_stats":null,"previous_names":["rafaeldevvv/portfolio"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaeldevvv%2Fportfolio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaeldevvv%2Fportfolio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaeldevvv%2Fportfolio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaeldevvv%2Fportfolio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rafaeldevvv","download_url":"https://codeload.github.com/rafaeldevvv/portfolio/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239427032,"owners_count":19636663,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["babel","css","html","javascript","portfolio","react","sass","webpack"],"created_at":"2024-11-07T05:11:47.191Z","updated_at":"2025-11-04T07:30:23.209Z","avatar_url":"https://github.com/rafaeldevvv.png","language":"JavaScript","funding_links":["https://www.buymeacoffee.com/rafael.maia"],"categories":[],"sub_categories":[],"readme":"# My Portfolio\n\n## Table of Contents\n\n- [Introduction](#introduction)\n- [Overview](#overview)\n  - [Screenshot](#screenshot)\n  - [Links](#links)\n- [Process](#process)\n  - [Built with](#built-with)\n  - [Highlights](#highlights)\n  - [Useful Resources](#useful-resources)\n- [Author](#author)\n- [Donate](#donate)\n- [License](#license)\n\n## Introduction\n\nWelcome to my portfolio's repository. Here you can see all the code written for the page.\n\nMy portfolio is a single page application built primarily with React. It showcases all of my projects, services, some personal information and contact information.\n\nI made sure the portfolio is accessible following best practices. I used proper markup. I made it operable, understandable, perceivable and robust.\n\nPlease, feel free to contact me and give feedback on how to improve my portfolio. I'll be happy to talk to you!\n\n## Overview\n\n### Screenshot\n\n![](./images/screenshot.png)\n\n### Links\n\n- [Live Site](https://rafaeldevvv.github.io/portfolio)\n\n## Process\n\n### Built with\n\n- HTML\n- CSS\n- SASS/SCSS\n- JavaScript\n- React.js\n- Git/GitHub\n- Font Awesome\n- Webpack\n- Babel\n- JSX\n\n### Highlights\n\nI used context to make the data about the author(me) available to all components.\n\n```jsx\nimport React from \"react\";\nimport Header from \"./components/Header.jsx\";\nimport Main from \"./components/Main.jsx\";\nimport Footer from \"./components/Footer.jsx\";\nimport { AuthorContext } from \"./AuthorContext.js\";\n\nexport default function App({ author }) {\n  return (\n    \u003cReact.StrictMode\u003e\n      \u003cAuthorContext.Provider value={author}\u003e\n        \u003cHeader /\u003e\n        \u003cMain /\u003e\n        \u003cFooter /\u003e\n      \u003c/AuthorContext.Provider\u003e\n    \u003c/React.StrictMode\u003e\n  );\n}\n```\n\nWhen the \"Show more projects\" button is clicked, the first new project is focused and the page is scrolled to the last new project.\n\n```jsx\nexport function ProjectList({ projects }) {\n  const initialNumberOfProjects = 6;\n  const [numberOfProjectsShown, setNumberOfProjectsShown] = useState(\n    initialNumberOfProjects\n  );\n  const projectRefs = useRef(null); // these are the list items\n\n  function getMap() {\n    if (!projectRefs.current) {\n      projectRefs.current = new Map();\n    }\n\n    return projectRefs.current;\n  }\n\n  function handleShowClick() {\n    const scrollOptions = { block: \"center\", behavior: \"smooth\" };\n\n    if (numberOfProjectsShown \u003c projects.length) {\n      const nextNumberOfProjectsShown = Math.min(\n        numberOfProjectsShown + 3,\n        projects.length\n      );\n\n      // update dom immediately\n      flushSync(() =\u003e {\n        setNumberOfProjectsShown(nextNumberOfProjectsShown);\n      });\n\n      // a ref has the last values regardless of the which render the handler is being run in\n      const updatedMap = getMap();\n      const projectNames = Array.from(updatedMap.keys());\n\n      // get the first new project and focus it\n      const firstNewProjectName = projectNames[numberOfProjectsShown];\n      const firstNewProject = updatedMap.get(firstNewProjectName);\n      firstNewProject.focus();\n\n      // get last project in the dom\n      const lastProjectName = projectNames[nextNumberOfProjectsShown - 1];\n      const lastProject = updatedMap.get(lastProjectName);\n\n      // get the last image and then wait for it to load to scroll it into view\n      const lastProjectImage = lastProject.querySelector(\".project-image\");\n\n      lastProjectImage.onload = function () {\n        lastProject.scrollIntoView(scrollOptions);\n      };\n    } else {\n      flushSync(() =\u003e {\n        setNumberOfProjectsShown(initialNumberOfProjects);\n      });\n      const map = getMap();\n      const firstProjectName = Array.from(map.keys())[0];\n      const firstProject = map.get(firstProjectName);\n\n      firstProject.scrollIntoView(scrollOptions);\n      firstProject.focus();\n    }\n  }\n\n  const allProjectsAreShown = numberOfProjectsShown === projects.length;\n  const buttonMessage = allProjectsAreShown\n    ? `Show less projects`\n    : \"Show more projects\";\n\n  return (\n    \u003cdiv\u003e\n      \u003cul\n        id=\"projects-list\"\n        aria-live=\"polite\"\n        aria-relevant=\"additions removals\"\n      \u003e\n        {projects.map((p, i) =\u003e {\n          if (i \u003c numberOfProjectsShown) {\n            return (\n              \u003cli\n                key={p.name}\n                ref={(node) =\u003e {\n                  const map = getMap();\n\n                  if (node) map.set(p.name, node);\n                  else map.delete(p.name);\n                }}\n                tabIndex=\"-1\"\n                className=\"no-focus-ring\"\n              \u003e\n                \u003cProject project={p} /\u003e\n              \u003c/li\u003e\n            );\n          }\n        })}\n      \u003c/ul\u003e\n      \u003cdiv className=\"show-button-container\"\u003e\n        \u003cbutton type=\"button\" onClick={handleShowClick}\u003e\n          \u003cspan className=\"button-label\"\u003e{buttonMessage}\u003c/span\u003e\n          \u003ci\n            className={`fa-solid fa-chevron-${\n              allProjectsAreShown ? \"up\" : \"down\"\n            } icon`}\n            aria-hidden=\"true\"\n          \u003e\u003c/i\u003e\n        \u003c/button\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\nUseful properties for scrolling:\n\n```scss\nhtml {\n  scroll-padding-top: 5rem;\n  scroll-behavior: smooth;\n\n  @media (prefers-reduced-motion: reduce) {\n    scroll-behavior: auto;\n  }\n}\n```\n\nI had to use `encodeURIComponent()` and manually change the url of the browser because the default behavior caused spaces to be replaced with plus signs.\n\n```jsx\nfunction EmailForm() {\n  const [subject, setSubject] = useState(\"\");\n  const [body, setBody] = useState(\"\");\n\n  const author = useAuthor();\n\n  return (\n    \u003cform\n      id=\"email-form\"\n      onSubmit={(e) =\u003e {\n        e.preventDefault();\n        const encodedBody = encodeURIComponent(body);\n        const encodedSubject = encodeURIComponent(subject);\n\n        //  window.open(`mailto:${author.email}?body=${encodedBody}\u0026subject=${encodedSubject}`);\n        location.href = `mailto:${author.email}?body=${encodedBody}\u0026subject=${encodedSubject}`;\n      }}\n    \u003e\n      \u003cp\u003e\n        \u003clabel htmlFor=\"inSubject\"\u003eSubject\u003c/label\u003e\n        \u003cinput\n          name=\"subject\"\n          id=\"inSubject\"\n          value={subject}\n          onChange={(e) =\u003e setSubject(e.target.value)}\n          placeholder=\"e.g. Landing Page\"\n        /\u003e\n      \u003c/p\u003e\n\n      \u003cp\u003e\n        \u003clabel htmlFor=\"inBody\"\u003eBody\u003c/label\u003e\n        \u003ctextarea\n          name=\"body\"\n          id=\"inBody\"\n          value={body}\n          onChange={(e) =\u003e setBody(e.target.value)}\n        /\u003e\n      \u003c/p\u003e\n\n      \u003cbutton type=\"submit\"\u003e\n        \u003cspan className=\"text\"\u003eSend email\u003c/span\u003e\n        \u003cspan className=\"icon\"\u003e\n          \u003ci className=\"fa-regular fa-paper-plane\"\u003e\u003c/i\u003e\n        \u003c/span\u003e\n      \u003c/button\u003e\n    \u003c/form\u003e\n  );\n}\n\n```\n\n### Useful Resources\n\n- [How I made my portfolio](https://tekolio.com/how-i-made-my-portfolio-in-react/#:~:text=Making%20a%20portfolio%20in%20HTML,we%20have%20as%20React%20developers.)\n- [StackOverflow - How to get the user language](https://stackoverflow.com/questions/8199760/how-to-get-the-browser-language-using-javascript)\n- [HTML to JSX](https://transform.tools/html-to-jsx)\n- [ChatGPT](https://chat.openai.com/)\n- [Image Compressor](https://imagecompressor.com/)\n- [Image Resizer](https://imageresizer.com/) - This was very useful as my computer does not have sufficient computing resources to keep photoshop and vs code open at the same time.\n- [HTML element reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) - A huge list of html elements.\n- [date-fns package](https://www.npmjs.com/package/date-fns)\n- [Stack Overflow - How do i add html form data to a mailto link](https://stackoverflow.com/questions/52637406/how-do-i-add-html-form-data-to-a-mailto-link) - This helped me figure out how to send emails without spaces being replaced with plus signs.\n- [Stack Overflow - Whatsapp API related question](https://stackoverflow.com/questions/30344476/web-link-to-specific-whatsapp-contact) - How to send messages via whatsapp from my website.\n- [Possible Heading](https://equalizedigital.com/accessibility-checker/possible-heading/)\n- [Favicon not showing - Stack Overflow](https://stackoverflow.com/questions/11797094/favicon-not-showing)\n- [Tips for designing focus indicators](https://www.deque.com/blog/give-site-focus-tips-designing-usable-focus-indicators/)\n- [WebAIM - Contrast Checker](https://webaim.org/resources/contrastchecker/)\n- [MDN AI Help](https://developer.mozilla.org/en-US/plus/ai-help)\n- [Pexels](https://www.pexels.com/)\n- [Stack Overflow - Open Graph og:image not pulling images](https://stackoverflow.com/questions/8855361/fb-opengraph-ogimage-not-pulling-images-possibly-https)\n- [Open Graph Data checker](https://www.opengraph.xyz/)\n- [scroll-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior)\n- [smooth scrolling and accessibility](https://css-tricks.com/smooth-scrolling-accessibility/)\n- [How do I modify the URL without reloading the page?](https://stackoverflow.com/questions/824349/how-do-i-modify-the-url-without-reloading-the-page)\n- [Working with the History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API/Working_with_the_History_API)\n- [Stop Using Fixed Headers and Start Using Sticky Ones](https://dev.to/luisaugusto/stop-using-fixed-headers-and-start-using-sticky-ones-1k30)\n- [One line CSS solution to prevent anchor links from scrolling behind a sticky or fixed header](https://getpublii.com/blog/one-line-css-solution-to-prevent-anchor-links-from-scrolling-behind-a-sticky-header.html)\n- [How to focus an element without triggering screen reader](https://stackoverflow.com/questions/68567802/how-to-focus-an-element-without-triggering-screen-reader)\n- [CoreUI](https://coreui.io/bootstrap/docs/icons/) - I used this to get the [LeetCode icon](https://coreui.io/icons/brand/leetcode/).\n\n## Author\n\nThe name's Rafael Maia. You can find me on [Instagram](https://www.instagram.com/rafaeldevvv), [X](https://www.twitter.com/rafaeldevvv), and [Linkedin](https://www.linkedin.com/in/rafael-maia-b69662263). Or you can send me an email [rafaeldeveloperr@gmail.com](mailto:rafaeldeveloperr@gmail.com).\n\nI am a freelancer, so you can also find me on [Fiverr](https://www.fiverr.com/rafael787) and [Upwork](https://www.upwork.com/freelancers/~01a4dc9692c96839dc).\n\n## Donate\n\nAlso, if you liked this project, consider [buying me a coffee](https://www.buymeacoffee.com/rafael.maia) ☕. I'd be grateful if you could support my work 😁👍❤!\n\n## License\n\nMIT License\n\nCopyright (c) 2023 Rafael Maia\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafaeldevvv%2Fportfolio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frafaeldevvv%2Fportfolio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafaeldevvv%2Fportfolio/lists"}