├── .prettierignore
├── .tool-versions
├── public
├── robots.txt
├── favicon.ico
├── favicon.png
├── _headers
├── favicon-16x16.png
├── favicon-32x32.png
├── mstile-150x150.png
├── apple-touch-icon.png
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── browserconfig.xml
├── app.webmanifest
└── safari-pinned-tab.svg
├── .npmrc
├── src
├── layouts
│ ├── simple.vue
│ └── default.vue
├── assets
│ ├── clip.mp4
│ ├── img
│ │ ├── me.png
│ │ ├── meme.png
│ │ ├── profile.jpg
│ │ └── water_drop.png
│ ├── clips
│ │ └── flicker.mp4
│ ├── posts
│ │ └── book_cover.jpeg
│ └── styles
│ │ ├── utility.css
│ │ ├── reset.css
│ │ ├── markdown.css
│ │ └── global.css
├── modules
│ ├── pwa.js
│ ├── gtag.js
│ ├── urql.js
│ └── firebase.js
├── config
│ └── siteconfig.json
├── components
│ ├── svg
│ │ ├── CircleFillSVG.vue
│ │ ├── BinaryTextSVG.vue
│ │ ├── PagesSVG.vue
│ │ ├── VueSVG.vue
│ │ ├── LampOffSVG.vue
│ │ ├── PullRequestSVG.vue
│ │ ├── GridsomeSVG.vue
│ │ ├── SpotLightSVG.vue
│ │ ├── LampOnSVG.vue
│ │ └── NetlifyLogoSVG.vue
│ ├── demos
│ │ ├── TwoCols.vue
│ │ ├── DemoWrapper.vue
│ │ ├── AnimatedBook.vue
│ │ ├── AnimatedButton.vue
│ │ ├── AnimatedBlocks.vue
│ │ ├── ColorBox.vue
│ │ ├── AnimatedBox.vue
│ │ ├── ContainerCard.vue
│ │ ├── BreadcrumbStyle.vue
│ │ ├── ResponsiveProfileCard.vue
│ │ ├── ContainerUnits.vue
│ │ ├── SmartGrid.vue
│ │ ├── CustomThemes.vue
│ │ ├── ColorMix.vue
│ │ ├── AnimatedSVG.vue
│ │ ├── GeneratePallete.vue
│ │ ├── Resizable.vue
│ │ └── CSSPreview.vue
│ ├── article
│ │ ├── CustomList.vue
│ │ ├── ArticlesHero.vue
│ │ ├── MeteorBackground.vue
│ │ ├── ArticleControls.vue
│ │ ├── GalaxyBackground.vue
│ │ └── ArticleHeading.vue
│ ├── ui
│ │ ├── TagGroup.vue
│ │ ├── SkipLink.vue
│ │ ├── Clock.vue
│ │ ├── HamburgerMenu.vue
│ │ ├── ScrollIndicator.vue
│ │ ├── InteractiveWrapper.vue
│ │ ├── ShareButton.vue
│ │ ├── ButtonGroup.vue
│ │ ├── Tag.vue
│ │ ├── ImgLazy.vue
│ │ ├── ThemeSwitcher.vue
│ │ ├── RippleButton.vue
│ │ ├── Button.vue
│ │ └── CoffeeButton.vue
│ ├── forms
│ │ ├── BaseSelect.vue
│ │ └── BaseRange.vue
│ ├── github
│ │ ├── GithubCardEmpty.vue
│ │ ├── GithubProjects.vue
│ │ └── GithubRepoCard.vue
│ ├── posts
│ │ ├── PostsList.vue
│ │ └── PostPreview.vue
│ ├── projects
│ │ ├── ProjectsGrid.vue
│ │ └── ProjectCard.vue
│ ├── base
│ │ └── AppLink.vue
│ ├── layout
│ │ ├── Footer.vue
│ │ ├── MobileNavbar.vue
│ │ └── Navbar.vue
│ └── hero
│ │ ├── HeroAvatar.vue
│ │ └── HeroSection.vue
├── App.vue
├── api
│ └── jokes
│ │ └── jokes.mjs
├── main.js
├── composables
│ ├── useCoffeeStore.js
│ ├── useArticles.js
│ ├── useContributions.js
│ └── useCoffeeControl.js
└── pages
│ ├── index.vue
│ ├── tags
│ └── [tag].vue
│ ├── articles
│ └── index.vue
│ ├── now.md
│ └── about-me.vue
├── jsconfig.json
├── .prettierrc
├── lib
├── modes.js
├── formatters
│ └── formatPoints.js
├── handling
│ └── debounce.js
├── visitor
│ └── getVisitorId.js
├── adapters
│ └── DevToArticleAdapter.js
└── md
│ ├── copy-code.js
│ └── index.js
├── .editorconfig
├── .env.example
├── eslint.config.js
├── netlify.toml
├── postcss.config.cjs
├── .gitignore
├── .stylelintrc.json
├── README.md
├── .github
└── dependabot.yml
├── index.html
├── scripts
└── create-draft.js
├── package.json
├── auto-imports.d.ts
├── vite.config.js
├── content
├── articles
│ └── how-to-keep-up.md
└── projects
│ └── projects.json
└── components.d.ts
/.prettierignore:
--------------------------------------------------------------------------------
1 | *.md
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | nodejs 23.10.0
2 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-Agent: *
2 | Allow: /
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | strict-peer-dependencies=false
2 | shamefully-hoist=true
3 |
--------------------------------------------------------------------------------
/src/layouts/simple.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["./src/**/*", "./lib/**/*", "./content/**/*"]
3 | }
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/public/favicon.png
--------------------------------------------------------------------------------
/public/_headers:
--------------------------------------------------------------------------------
1 | /assets/*
2 | cache-control: max-age=31536000
3 | cache-control: immutable
4 |
--------------------------------------------------------------------------------
/src/assets/clip.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/src/assets/clip.mp4
--------------------------------------------------------------------------------
/src/assets/img/me.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/src/assets/img/me.png
--------------------------------------------------------------------------------
/src/assets/img/meme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/src/assets/img/meme.png
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/public/mstile-150x150.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/src/assets/clips/flicker.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/src/assets/clips/flicker.mp4
--------------------------------------------------------------------------------
/src/assets/img/profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/src/assets/img/profile.jpg
--------------------------------------------------------------------------------
/src/assets/img/water_drop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/src/assets/img/water_drop.png
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/src/assets/posts/book_cover.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeferson-sb/portfolio/HEAD/src/assets/posts/book_cover.jpeg
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": false,
4 | "trailingComma": "es5",
5 | "bracketSpacing": true
6 | }
7 |
--------------------------------------------------------------------------------
/lib/modes.js:
--------------------------------------------------------------------------------
1 | export const isProd = import.meta.env.PROD
2 | export const isDev = import.meta.env.DEV
3 | export const isSSR = import.meta.env.SSR
--------------------------------------------------------------------------------
/lib/formatters/formatPoints.js:
--------------------------------------------------------------------------------
1 | export const formatPoints = (n) =>
2 | Intl.NumberFormat('en-US', {
3 | notation: 'compact',
4 | compactDisplay: 'short',
5 | }).format(n)
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | DEVTO_API_KEY=
2 | FIREBASE_API_KEY=
3 | FIREBASE_APP_ID=
4 | FIREBASE_MEASUREMENT_ID=
5 | FIREBASE_MESSAGE_SENDER=
6 | FIREBASE_PROJECT_ID=
7 | GITHUB_TOKEN=
8 | GOOGLE_ANALYTICS_ID=
9 | GOOGLE_SEARCH_CONSOLE_TOKEN=
10 |
--------------------------------------------------------------------------------
/lib/handling/debounce.js:
--------------------------------------------------------------------------------
1 | const debounce = (fn, ms = 0) => {
2 | let timeoutId;
3 | return function (...args) {
4 | clearTimeout(timeoutId);
5 | timeoutId = setTimeout(() => fn.apply(this, args), ms);
6 | };
7 | };
8 |
9 | export default debounce
--------------------------------------------------------------------------------
/lib/visitor/getVisitorId.js:
--------------------------------------------------------------------------------
1 | import { load as fingerprint } from '@fingerprintjs/fingerprintjs'
2 |
3 | export const getVisitorId = async () => {
4 | const value = await fingerprint()
5 | const { visitorId } = await value.get()
6 | return visitorId
7 | }
--------------------------------------------------------------------------------
/src/modules/pwa.js:
--------------------------------------------------------------------------------
1 | export const install = ({ isClient, router }) => {
2 | if (!isClient) return
3 |
4 | router.isReady().then(async () => {
5 | const { registerSW } = await import('virtual:pwa-register')
6 | registerSW({ immediate: true })
7 | })
8 | }
9 |
--------------------------------------------------------------------------------
/src/config/siteconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "siteName": "Software Enginner",
3 | "siteUrl": "https://jefersonsilva.me",
4 | "description": "I am Software Engineer that crafts things for the Web, heavy focused on front-end development enthusiastic and fascinated on UI design"
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/svg/CircleFillSVG.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/modules/gtag.js:
--------------------------------------------------------------------------------
1 | import { createGtag } from 'vue-gtag'
2 |
3 | const gtag = createGtag({
4 | tagId: import.meta.env.VITE_GOOGLE_ANALYTICS_ID,
5 | })
6 |
7 | export const install = ({ isClient, app }) => {
8 | if (!isClient) return
9 |
10 | app.use(gtag)
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/demos/TwoCols.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import pluginVue from 'eslint-plugin-vue'
2 | import eslintConfigPrettier from 'eslint-config-prettier';
3 |
4 | export default [
5 | ...pluginVue.configs['flat/recommended'],
6 | {
7 | rules: {
8 | ...eslintConfigPrettier.rules,
9 | }
10 | }
11 | ]
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | command = "npm run build"
3 | publish = "dist"
4 | functions = "src/api"
5 |
6 | [[redirects]]
7 | from = "/*"
8 | to = "/index.html"
9 | status = 200
10 |
11 | [[redirects]]
12 | from = '/api/*'
13 | to = '/.netlify/functions/:splat'
14 | status = 200
15 |
--------------------------------------------------------------------------------
/public/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #f4f4f4
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/components/demos/DemoWrapper.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | const postcssPresetEnv = require('postcss-preset-env');
2 | const postcssGlobalData = require('@csstools/postcss-global-data');
3 |
4 | module.exports = {
5 | plugins: [
6 | postcssGlobalData({
7 | files: [
8 | './src/assets/styles/global.css'
9 | ]
10 | }),
11 | postcssPresetEnv()
12 | ],
13 | }
14 |
--------------------------------------------------------------------------------
/.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 | .cache
11 | .DS_Store
12 | src/.temp
13 | node_modules
14 | dist
15 |
16 | .env
17 | .idea
18 | .vscode
19 | # Local Netlify folder
20 | .netlify
21 | draft/
22 | draft/*
23 | *:Zone.Identifier
24 |
25 | .vite-ssg-temp
26 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/components/article/CustomList.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
25 |
--------------------------------------------------------------------------------
/src/components/svg/BinaryTextSVG.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 0 1 1 0 1 0 0 0
5 | 0 1 1 0 0 1 0 1
6 | 0 1 1 0 1 1 0 0
7 | 0 1 1 0 1 1 0 0
8 | 0 1 1 0 1 1 1 1
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "stylelint-config-standard",
4 | "stylelint-config-recommended-vue"
5 | ],
6 | "rules": {
7 | "declaration-block-no-redundant-longhand-properties": null,
8 | "custom-property-empty-line-before": null,
9 | "selector-class-pattern": null,
10 | "media-feature-range-notation": "prefix",
11 | "comment-empty-line-before": "always",
12 | "declaration-no-important": true,
13 | "max-nesting-depth": 3
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/ui/TagGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
18 |
19 |
26 |
--------------------------------------------------------------------------------
/src/components/ui/SkipLink.vue:
--------------------------------------------------------------------------------
1 |
2 | Skip to main content
3 |
4 |
5 |
22 |
--------------------------------------------------------------------------------
/src/modules/urql.js:
--------------------------------------------------------------------------------
1 | import * as urql from '@urql/vue'
2 | import { cacheExchange, fetchExchange } from '@urql/core'
3 |
4 | export const install = ({ isClient, app }) => {
5 | // if (!isClient) return
6 |
7 | app.use(urql, {
8 | url: 'https://api.github.com/graphql',
9 | fetchOptions: () => {
10 | const token = import.meta.env.VITE_GITHUB_TOKEN
11 | return {
12 | headers: { authorization: token ? `Bearer ${token}` : '' },
13 | }
14 | },
15 | exchanges: [cacheExchange, fetchExchange],
16 | })
17 | }
18 |
--------------------------------------------------------------------------------
/src/api/jokes/jokes.mjs:
--------------------------------------------------------------------------------
1 | export default async (request, context) => {
2 | try {
3 | const response = await fetch('https://v2.jokeapi.dev/joke/programming')
4 | const data = await response.json()
5 | const ip =
6 | context.ip ||
7 | req.headers['x-forwarded-for'] ||
8 | req.socket.remoteAddress ||
9 | null
10 |
11 | return Response.json({ joke: data.joke || data.delivery, ip }, { status: 200 })
12 | } catch (error) {
13 | return {
14 | statusCode: 500,
15 | body: { error: 'Oof.. Something went bad' },
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/forms/BaseSelect.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ opt }}
5 |
6 |
7 |
8 |
9 |
26 |
--------------------------------------------------------------------------------
/lib/adapters/DevToArticleAdapter.js:
--------------------------------------------------------------------------------
1 | class DevToArticleAdapter {
2 | static transformFrontMatter(source) {
3 | return {
4 | id: source.id,
5 | title: source.title,
6 | slug: source.slug,
7 | published_at: source.published_at,
8 | tags: source.tag_list,
9 | excerpt: source.description,
10 | crosspostedOn: 'Dev.to',
11 | crosspostLink: source.url,
12 | url: source.url,
13 | path: `/articles/${source.slug}`,
14 | canonicalUrl: source.canonical_url,
15 | cover_image: source.cover_image,
16 | }
17 | }
18 | }
19 |
20 | module.exports = DevToArticleAdapter
21 |
--------------------------------------------------------------------------------
/src/components/svg/PagesSVG.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/assets/styles/utility.css:
--------------------------------------------------------------------------------
1 | @layer utility {
2 | .container {
3 | --max-w: 1110px;
4 |
5 | margin-inline: auto;
6 | width: calc(100% - 2rem);
7 | max-width: var(--max-w);
8 | display: flex;
9 | }
10 |
11 | .overflow--hidden {
12 | overflow: hidden;
13 | }
14 |
15 | .sr-only:not(:focus, :active) {
16 | clip: rect(0 0 0 0);
17 | clip-path: inset(50%);
18 | height: 1px;
19 | overflow: hidden;
20 | position: absolute;
21 | white-space: nowrap;
22 | width: 1px;
23 | }
24 |
25 | @media screen and (min-width: 26.625em) {
26 | .is-mobile-only {
27 | display: none;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/demos/AnimatedBook.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/github/GithubCardEmpty.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
No recent contributions just yet.
4 |
Working on in... 🛠️
5 |
6 |
7 |
8 |
28 |
--------------------------------------------------------------------------------
/src/components/ui/Clock.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/components/demos/AnimatedButton.vue:
--------------------------------------------------------------------------------
1 |
2 | Press me
3 |
4 |
5 |
38 |
--------------------------------------------------------------------------------
/src/components/ui/HamburgerMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
11 |
12 |
35 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { ViteSSG } from 'vite-ssg'
2 | import generatedRoutes from 'virtual:generated-pages'
3 | import { setupLayouts } from 'virtual:generated-layouts'
4 | import App from './App.vue'
5 |
6 | import '@/assets/styles/reset.css'
7 | import '@/assets/styles/global.css'
8 | import '@/assets/styles/markdown.css'
9 | import '@/assets/styles/utility.css'
10 |
11 | const routes = setupLayouts(generatedRoutes)
12 |
13 | console.log(
14 | `%c⚠️ I hope you know what you're doing! :p`,
15 | 'color: #c9a43e; background: #303030; border-radius: 4px; padding: 0.25rem; font-family: cursive; font-style: italic; font-weight: 700;'
16 | )
17 |
18 | export const createApp = ViteSSG(
19 | App,
20 | { routes, base: import.meta.env.BASE_URL },
21 | (ctx) => {
22 | // install all modules under `modules/`
23 | Object.values(import.meta.glob('./modules/*.js', { eager: true })).forEach(
24 | (i) => i.install?.(ctx)
25 | )
26 | }
27 | )
28 |
--------------------------------------------------------------------------------
/public/app.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "lang": "en-US",
3 | "name": "Jeferson's Portfolio",
4 | "short_name": "JB",
5 | "start_url": "/",
6 | "display": "standalone",
7 | "description": "I'm a Software Engineer that crafts things for the Web, heavy focused on front-end development enthusiastic and fascinated on UI design",
8 | "icons": [
9 | {
10 | "src": "./android-chrome-192x192.png",
11 | "sizes": "192x192",
12 | "type": "image/png"
13 | },
14 | {
15 | "src": "./android-chrome-512x512.png",
16 | "sizes": "512x512",
17 | "type": "image/png"
18 | }
19 | ],
20 | "orientation": "landscape",
21 | "theme_color": "#1A1C1F",
22 | "background_color": "#1ABBD1",
23 | "shortcuts": [
24 | {
25 | "name": "Read articles",
26 | "url": "/articles",
27 | "icons": [
28 | {
29 | "src": "./android-chrome-192x192.png",
30 | "sizes": "192x192"
31 | }
32 | ]
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/posts/PostsList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Latest Posts
4 |
7 |
View
8 | all
9 |
10 |
11 |
12 |
19 |
20 |
33 |
--------------------------------------------------------------------------------
/src/components/svg/VueSVG.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 | Vue
9 |
10 |
11 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/components/projects/ProjectsGrid.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
23 |
24 |
38 |
--------------------------------------------------------------------------------
/src/components/demos/AnimatedBlocks.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
51 |
--------------------------------------------------------------------------------
/src/components/demos/ColorBox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Unsupported by your browser
4 |
5 | {{ color }}
6 |
7 |
8 |
22 |
23 |
47 |
--------------------------------------------------------------------------------
/src/components/ui/ScrollIndicator.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
My Portfolio
4 | Developed using Vue + Vite ♥
5 |
6 |
7 | [](https://app.netlify.com/sites/jefersonsilva/deploys)
8 |
9 | ## Getting started
10 |
11 | > [!IMPORTANT]
12 | > I appreciate your interest in adapting or modifying this project to fit your needs. Just a gentle reminder, if you do choose to fork or make changes, please ensure proper credit is given for the original work. Thank you!
13 |
14 | ### Clone repository
15 |
16 | ```bash
17 | git clone https://github.com/jeferson-sb/portfolio.git
18 | ```
19 |
20 | ### Install dependencies
21 |
22 | ```sh
23 | cd portfolio
24 | pnpm install
25 | ```
26 |
27 | ### Start local dev server
28 |
29 | ```sh
30 | pnpm dev
31 | ```
32 |
33 | ### Linting
34 |
35 | ```sh
36 | pnpm lint:js
37 | pnpm lint:css
38 | ```
39 |
40 | ### Formatting
41 |
42 | ```sh
43 | pnpm format
44 | ```
45 |
--------------------------------------------------------------------------------
/src/modules/firebase.js:
--------------------------------------------------------------------------------
1 | import { VueFire, VueFireAppCheck } from 'vuefire'
2 | import { initializeApp } from 'firebase/app'
3 | import { getFirestore } from 'firebase/firestore'
4 | import { ReCaptchaV3Provider } from 'firebase/app-check'
5 |
6 | // Initialize Firebase
7 | export const firebaseApp = initializeApp({
8 | appId: "1:545804876347:web:22693f5fb3da8f3ebbba1e",
9 | apiKey: "AIzaSyBmwcdM6SlakmUiBIf_hGrKXDv-5U_WbSU",
10 | authDomain: "portfolio-d3c7c.firebaseapp.com",
11 | projectId: "portfolio-d3c7c",
12 | storageBucket: "portfolio-d3c7c.appspot.com",
13 | databaseURL: 'https://portfolio-d3c7c-default-rtdb.firebaseio.com/',
14 | })
15 |
16 | export const db = getFirestore(firebaseApp)
17 |
18 | export const install = ({ isClient, app }) => {
19 | app.use(VueFire, {
20 | firebaseApp,
21 | modules: [
22 | VueFireAppCheck({
23 | provider: new ReCaptchaV3Provider(import.meta.env.VITE_RECAPTCHA),
24 | debug: process.env.NODE_ENV !== 'production',
25 | isTokenAutoRefreshEnabled: true,
26 | }),
27 | ]
28 | })
29 | }
30 |
--------------------------------------------------------------------------------
/src/composables/useCoffeeStore.js:
--------------------------------------------------------------------------------
1 | import { useCollection } from 'vuefire'
2 | import { collection, doc, setDoc, Timestamp } from 'firebase/firestore'
3 |
4 | import { db } from '@/modules/firebase'
5 |
6 | export function useCoffeeStore() {
7 | const coffeeRef = collection(db, 'coffee')
8 | const coffee = useCollection(coffeeRef, { once: true })
9 |
10 | const addCoffeePoints = async ({ articleId, points, visitorId }) => {
11 | if (!articleId || !visitorId) return
12 |
13 | try {
14 | const userDoc = doc(coffeeRef, visitorId)
15 | const userCups = coffee.value.find((cup) => cup.id === visitorId)
16 | const articles = userCups?.articles || {}
17 |
18 | await setDoc(userDoc, {
19 | articles: {
20 | ...articles,
21 | [articleId]: {
22 | points,
23 | date: Timestamp.fromDate(new Date()),
24 | },
25 | },
26 | })
27 | } catch (error) {
28 | console.error('Failed to set coffee points for visitor', error)
29 | }
30 | }
31 |
32 | return { coffee, addCoffeePoints }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/md/copy-code.js:
--------------------------------------------------------------------------------
1 | const wrap = (render) => (...args) => {
2 | return render.apply(this, args)
3 | .replace(
4 | '',
5 | `
6 |
7 |
8 |
9 | `
10 | )
11 | }
12 |
13 | const copyPlugin = (md) => {
14 | md.renderer.rules.code_block = wrap(md.renderer.rules.code_block);
15 | md.renderer.rules.fence = wrap(md.renderer.rules.fence);
16 | };
17 |
18 | export { copyPlugin }
19 |
--------------------------------------------------------------------------------
/src/components/article/ArticlesHero.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Articles
5 |
6 | Here you can find all of my published articles, tutorials and guides,
7 | going from little css tips to software architecture, open source, user
8 | experience and all sort of things. I hope you enjoy the reading
9 | 📖👋
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/svg/LampOffSVG.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
15 |
19 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/pages/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
47 |
--------------------------------------------------------------------------------
/src/components/ui/InteractiveWrapper.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ title }}
4 |
5 |
6 |
7 |
8 |
29 |
30 |
51 |
--------------------------------------------------------------------------------
/src/components/ui/ShareButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 | Share
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
40 |
--------------------------------------------------------------------------------
/src/layouts/default.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
31 |
32 |
50 |
--------------------------------------------------------------------------------
/src/components/article/MeteorBackground.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
53 |
--------------------------------------------------------------------------------
/src/components/ui/ButtonGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
30 |
31 |
64 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: weekly
7 | open-pull-requests-limit: 10
8 | ignore:
9 | - dependency-name: eslint
10 | versions:
11 | - 7.18.0
12 | - 7.19.0
13 | - 7.20.0
14 | - 7.21.0
15 | - 7.22.0
16 | - 7.23.0
17 | - 7.24.0
18 | - dependency-name: eslint-plugin-vue
19 | versions:
20 | - 7.5.0
21 | - 7.6.0
22 | - 7.7.0
23 | - 7.8.0
24 | - dependency-name: eslint-config-prettier
25 | versions:
26 | - 7.2.0
27 | - 8.0.0
28 | - 8.1.0
29 | - dependency-name: stylelint-config-standard
30 | versions:
31 | - 21.0.0
32 | - dependency-name: stylelint
33 | versions:
34 | - 13.10.0
35 | - 13.11.0
36 | - 13.12.0
37 | - dependency-name: elliptic
38 | versions:
39 | - 6.5.4
40 | - dependency-name: eslint-plugin-prettier
41 | versions:
42 | - 3.3.1
43 | - dependency-name: "@fortawesome/free-brands-svg-icons"
44 | versions:
45 | - 5.15.2
46 | - dependency-name: "@fortawesome/free-solid-svg-icons"
47 | versions:
48 | - 5.15.2
49 | - dependency-name: "@fortawesome/fontawesome-svg-core"
50 | versions:
51 | - 1.2.34
52 | - dependency-name: "@fortawesome/vue-fontawesome"
53 | versions:
54 | - 2.0.2
55 | - dependency-name: vue-i18n
56 | versions:
57 | - 8.22.3
58 |
--------------------------------------------------------------------------------
/src/composables/useArticles.js:
--------------------------------------------------------------------------------
1 | import { computed } from 'vue'
2 | import { useRouter } from 'vue-router'
3 |
4 | export function useArticles(options) {
5 | const router = useRouter()
6 | const tag = options?.tag
7 |
8 | const articles = computed(() => {
9 | return router
10 | .getRoutes()
11 | .filter((route) => {
12 | const isArticle = route.path.startsWith('/articles/') && route.name
13 | const tags = route.meta?.frontmatter?.tags
14 |
15 | if (tag) {
16 | const normalizedTags = tags ? tags.toLowerCase() : ''
17 | const matchTag = normalizedTags.indexOf(tag.toLowerCase()) !== -1
18 |
19 | return isArticle && matchTag
20 | }
21 |
22 | return isArticle
23 | })
24 | .sort((a, b) =>
25 | a.meta.frontmatter.published_at < b.meta.frontmatter.published_at
26 | ? 1
27 | : -1
28 | )
29 | .map((article) => ({
30 | id: article.meta?.frontmatter?.id,
31 | articleId: article.name,
32 | title: article.meta?.frontmatter?.title,
33 | date: article.meta?.frontmatter?.published_at,
34 | path: article.path,
35 | excerpt: article.meta?.frontmatter?.excerpt,
36 | tags: article.meta?.frontmatter?.tags,
37 | }))
38 | })
39 |
40 | if (tag && !articles.value.length) router.push('/404')
41 |
42 | return articles
43 | }
44 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
15 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/components/ui/Tag.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ tagName }}
3 | {{ tagName }}
4 |
5 |
6 |
29 |
30 |
62 |
--------------------------------------------------------------------------------
/lib/md/index.js:
--------------------------------------------------------------------------------
1 | const { promises: fs } = require('fs')
2 |
3 | async function writeFileOn(filePath, content) {
4 | try {
5 | const fileStats = async (p) => {
6 | try {
7 | const stats = await fs.stat(p)
8 | return stats
9 | } catch (error) {
10 | return
11 | }
12 | }
13 |
14 | const stats = await fileStats(filePath)
15 | const fileExists = !!stats && Object.keys(stats).length > 0
16 |
17 | if (!fileExists) {
18 | fs.writeFile(filePath, content)
19 | console.log('📰 Writing file...', filePath)
20 | } else {
21 | console.error('⚠ File already exists: ', filePath)
22 | }
23 | } catch (error) {
24 | throw new Error('Unable to write markdown file', error)
25 | }
26 | }
27 |
28 | async function transformMarkdown(payload, adapter) {
29 | let transformedMd = ''
30 | const md = payload.body_markdown
31 | const content = md.replace(/^---$.*^---$/ms, '').trim()
32 | const delimiter = '---'
33 | const frontmatter = Object.entries(adapter.transformFrontMatter(payload))
34 |
35 | transformedMd += `${delimiter}\n`
36 |
37 | // eslint-disable-next-line no-restricted-syntax
38 | for (const [k, v] of frontmatter) {
39 | transformedMd += `${k}: ${v}\n`
40 | }
41 |
42 | transformedMd += `${delimiter}\n`
43 | transformedMd += content
44 | return transformedMd
45 | }
46 |
47 | module.exports = { writeFileOn, transformMarkdown }
48 |
--------------------------------------------------------------------------------
/src/components/github/GithubProjects.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Open Source
4 | Latest contributions (monthly updated)
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
25 |
26 |
54 |
--------------------------------------------------------------------------------
/src/pages/tags/[tag].vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{ tag }} Articles
6 |
7 |
8 |
13 |
14 |
15 |
23 |
24 |
56 |
--------------------------------------------------------------------------------
/scripts/create-draft.js:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs/promises'
2 | import path from 'node:path'
3 | import readline from 'node:readline/promises'
4 |
5 | const defaults = {
6 | id: Math.floor(Math.random() * 1e15).toString(36),
7 | published_at: new Date().toISOString(),
8 | excerpt: '',
9 | crosspostedOn: '',
10 | crosspostLink: '',
11 | }
12 |
13 | const slugify = (str) =>
14 | str
15 | .toLowerCase()
16 | .trim()
17 | .replace(/[^\w\s-]/g, '')
18 | .replace(/[\s_-]+/g, '-')
19 | .replace(/^-+|-+$/g, '')
20 |
21 | const rl = readline.createInterface({
22 | input: process.stdin,
23 | output: process.stdout,
24 | })
25 |
26 | const title = await rl.question("What's the title of your post? ")
27 | const tags = await rl.question(
28 | 'Add the tags you would like? (separated by comma) '
29 | )
30 |
31 | rl.close()
32 |
33 | const slug = slugify(title)
34 |
35 | const dir = path.resolve('.')
36 | const filename = `${slug}.md`
37 | const output = path.join(dir, 'content/articles', filename)
38 |
39 | console.log('\n✍️ Drafting new doc...\n')
40 |
41 | try {
42 | const post = {
43 | ...defaults,
44 | title,
45 | tags,
46 | slug,
47 | }
48 | let frontmatter = '---\n'
49 |
50 | for (const [k, v] of Object.entries(post)) {
51 | frontmatter += `${k}: ${v}\n`
52 | }
53 |
54 | frontmatter += '---\n'
55 |
56 | await fs.writeFile(output, frontmatter, {
57 | encoding: 'utf-8',
58 | })
59 | console.log('✅ Open: ', output)
60 | } catch (error) {
61 | console.error(error)
62 | }
63 |
--------------------------------------------------------------------------------
/src/composables/useContributions.js:
--------------------------------------------------------------------------------
1 | import { computed } from 'vue'
2 | import { useQuery } from '@urql/vue'
3 |
4 | const pullRequestQuery = `
5 | query {
6 | user(login: "jeferson-sb") {
7 | pullRequests(first: 100, orderBy: { field: CREATED_AT, direction: DESC }, states: [OPEN, MERGED]) {
8 | edges {
9 | node {
10 | id
11 | number
12 | url
13 | createdAt
14 | isCrossRepository
15 | repository {
16 | id
17 | isPrivate
18 | nameWithOwner
19 | url
20 | description
21 | primaryLanguage {
22 | name
23 | }
24 | }
25 | }
26 | }
27 | }
28 | }
29 | }
30 | `
31 |
32 | export function useContributions() {
33 | const { data } = useQuery({
34 | query: pullRequestQuery,
35 | })
36 |
37 | const lastThreeMonths = () => {
38 | const today = new Date()
39 | today.setMonth(today.getMonth() - 3)
40 | return today
41 | }
42 |
43 | const pullRequests = computed(() =>
44 | data.value?.user?.pullRequests?.edges?.filter((pr, index, arr) => {
45 | const isFork = pr.node.isCrossRepository
46 | const isRecent = new Date(pr.node.createdAt) >= lastThreeMonths()
47 | const nonDuplicate =
48 | arr.findIndex((x) => x.node.repository.id === pr.node.repository.id) ===
49 | index
50 | return isFork && isRecent && nonDuplicate
51 | })
52 | )
53 |
54 | return pullRequests
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/ui/ImgLazy.vue:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
51 |
52 |
81 |
--------------------------------------------------------------------------------
/src/pages/articles/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
44 |
45 |
53 |
--------------------------------------------------------------------------------
/src/components/svg/PullRequestSVG.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/composables/useCoffeeControl.js:
--------------------------------------------------------------------------------
1 | import { onMounted, watch } from 'vue'
2 | import { event } from 'vue-gtag'
3 |
4 | import { isProd } from '@lib/modes'
5 | import debounce from '@lib/handling/debounce'
6 | import { getVisitorId } from '@lib/visitor/getVisitorId'
7 | import { useCoffeeStore } from '@/composables/useCoffeeStore'
8 |
9 | export function useCoffeeControl({ articleId, articleUrl }) {
10 | if (!articleId && !articleUrl) {
11 | throw new Error('Article url and id are required')
12 | }
13 |
14 | const { coffee, addCoffeePoints } = useCoffeeStore()
15 | const points = ref(0)
16 | const visitor = ref('')
17 |
18 | const allCupsCoffee = computed(() => {
19 | return coffee.value.reduce((total, coffeeCup) => {
20 | const articlePoints = coffeeCup.articles[articleId]?.points
21 | return articlePoints ? articlePoints + total : total
22 | }, points.value)
23 | })
24 |
25 | onMounted(async () => {
26 | try {
27 | const visitorId = await getVisitorId()
28 | const visitorDoc = coffee.value.find((coff) => coff.id === visitorId)
29 | const visitorGivenPoints = visitorDoc?.articles[articleId]?.points
30 |
31 | visitor.value = visitorId
32 | points.value = visitorGivenPoints || 0
33 | } catch (error) {
34 | console.error(error)
35 | }
36 | })
37 |
38 | const onPointsWatch = debounce(async () => {
39 | if (isProd) {
40 | await addCoffeePoints({
41 | articleId,
42 | points: points.value,
43 | visitorId: visitor.value,
44 | })
45 | event('coffee_click', { article: articleUrl })
46 | }
47 | }, 400)
48 |
49 | watch(points, onPointsWatch)
50 |
51 | return {
52 | points,
53 | allCupsCoffee,
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/base/AppLink.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
40 |
41 |
75 |
--------------------------------------------------------------------------------
/src/components/ui/ThemeSwitcher.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
47 |
48 |
75 |
--------------------------------------------------------------------------------
/src/components/demos/AnimatedBox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
35 |
36 |
84 |
--------------------------------------------------------------------------------
/src/components/ui/RippleButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
21 |
22 |
81 |
--------------------------------------------------------------------------------
/src/components/forms/BaseRange.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
26 |
27 |
72 |
--------------------------------------------------------------------------------
/src/components/article/ArticleControls.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
{{ formatPoints(allCupsCoffee) }}
11 |
12 |
13 |
14 |
32 |
33 |
82 |
--------------------------------------------------------------------------------
/src/components/svg/GridsomeSVG.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Built with Gridsome
4 |
8 |
12 |
16 |
22 |
23 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/components/layout/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
88 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "portfolio",
3 | "version": "2.1.0",
4 | "private": true,
5 | "license": "Apache License 2.0",
6 | "type": "module",
7 | "scripts": {
8 | "build": "vite-ssg build",
9 | "dev": "vite",
10 | "preview": "vite preview",
11 | "optimize": "vite optimize",
12 | "lint:js": "eslint 'src/**/*.{js,vue}'",
13 | "lint:css": "stylelint 'src/**/*.{css,vue}'",
14 | "format": "prettier 'src/**/*.js' -w",
15 | "deps:up": "taze major -I",
16 | "start:draft": "node scripts/create-draft.js",
17 | "bundlesize": "npx vite-bundle-visualizer"
18 | },
19 | "dependencies": {
20 | "@fingerprintjs/fingerprintjs": "^5.0.1",
21 | "@unhead/vue": "^2.0.19",
22 | "@urql/vue": "^1.4.3",
23 | "firebase": "^11.10.0",
24 | "graphql": "^16.12.0",
25 | "lozad": "^1.16.0",
26 | "vue": "^3.5.25",
27 | "vue-gtag": "^3.6.3",
28 | "vue-router": "^4.6.3",
29 | "vuefire": "^3.2.2"
30 | },
31 | "devDependencies": {
32 | "@csstools/postcss-global-data": "^3.1.0",
33 | "@vitejs/plugin-vue": "^6.0.2",
34 | "beasties": "^0.3.5",
35 | "eslint": "^9.39.1",
36 | "eslint-config-prettier": "^10.1.8",
37 | "eslint-plugin-import": "^2.32.0",
38 | "eslint-plugin-prettier": "^5.5.4",
39 | "eslint-plugin-vue": "^10.5.1",
40 | "gray-matter": "^4.0.3",
41 | "markdown-it-anchor": "^9.2.0",
42 | "markdown-it-shiki": "^0.9.0",
43 | "postcss-preset-env": "^10.4.0",
44 | "prettier": "^3.7.3",
45 | "stylelint": "^16.26.1",
46 | "stylelint-config-recommended-vue": "^1.6.1",
47 | "stylelint-config-standard": "^38.0.0",
48 | "taze": "^19.9.2",
49 | "unplugin-auto-import": "^19.3.0",
50 | "unplugin-vue-components": "^28.8.0",
51 | "unplugin-vue-markdown": "^29.2.0",
52 | "vite": "^7.2.6",
53 | "vite-plugin-pages": "^0.33.1",
54 | "vite-plugin-pwa": "^1.1.0",
55 | "vite-plugin-vue-devtools": "^7.7.7",
56 | "vite-plugin-vue-layouts": "^0.11.0",
57 | "vite-ssg": "^28.2.2",
58 | "vite-ssg-sitemap": "^0.9.0"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/demos/ContainerCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Featured
7 |
Modern Design
8 |
9 | Discover the perfect blend of form and function in our latest collection.
10 | Each piece tells a unique story of craftsmanship and innovation.
11 |
12 |
Learn More
13 |
14 |
15 |
16 |
17 |
18 |
86 |
--------------------------------------------------------------------------------
/src/components/hero/HeroAvatar.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
21 |
22 |
92 |
--------------------------------------------------------------------------------
/src/components/demos/BreadcrumbStyle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Electronics
7 | Computers & Accessories
8 | Gaming
9 | Laptops
10 |
11 |
12 |
--variant: arrow
13 |
14 |
15 | Electronics
16 | Computers & Accessories
17 | Gaming
18 | Laptops
19 |
20 |
21 |
--variant: bordered
22 |
23 |
24 | Electronics
25 | Computers & Accessories
26 | Gaming
27 | Laptops
28 |
29 |
30 |
31 |
32 |
33 |
98 |
--------------------------------------------------------------------------------
/src/assets/styles/reset.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable declaration-no-important */
2 | @layer reset {
3 |
4 | /* Box sizing rules */
5 | *,
6 | *::before,
7 | *::after {
8 | box-sizing: border-box;
9 | }
10 |
11 | /* Remove default padding */
12 | ul,
13 | ol {
14 | padding: 0;
15 | }
16 |
17 | /* Remove default margin */
18 | body,
19 | h1,
20 | h2,
21 | h3,
22 | h4,
23 | p,
24 | ul[class],
25 | ol[class],
26 | li,
27 | figure,
28 | figcaption,
29 | blockquote,
30 | dl,
31 | dd {
32 | margin: 0;
33 | }
34 |
35 | html,
36 | body {
37 | min-height: 100%;
38 | }
39 |
40 | #app {
41 | min-height: 100dvh;
42 | display: flex;
43 | flex-direction: column;
44 | justify-content: space-between;
45 | }
46 |
47 | html:focus-within {
48 | scroll-behavior: smooth;
49 | }
50 |
51 | /* A elements that don't have a class get default styles */
52 | a:not([class]) {
53 | text-decoration-skip-ink: auto;
54 | }
55 |
56 | /* Make medias easier to work with */
57 | img,
58 | picture,
59 | video,
60 | canvas,
61 | svg,
62 | iframe,
63 | object {
64 | max-width: 100%;
65 | display: block;
66 | }
67 |
68 | /* Natural flow and rhythm in articles by default */
69 | article>*+* {
70 | margin-block-start: 1em;
71 | }
72 |
73 | /* Avoid text overflows */
74 | p,
75 | h1,
76 | h2,
77 | h3,
78 | h4,
79 | h5,
80 | h6 {
81 | overflow-wrap: break-word;
82 | }
83 |
84 | /* Inherit fonts for inputs and buttons */
85 | input,
86 | button,
87 | textarea,
88 | select {
89 | font: inherit;
90 | }
91 |
92 | button {
93 | border: 0;
94 | cursor: pointer;
95 | }
96 | }
97 |
98 | /* Remove all animations and transitions for people that prefer not to see them */
99 | @media (prefers-reduced-motion: reduce) {
100 | * {
101 | animation-duration: 0.01ms !important;
102 | animation-iteration-count: 1 !important;
103 | transition-duration: 0.01ms !important;
104 | scroll-behavior: auto;
105 | }
106 | }
107 |
108 | /* Reduce transparency to all applicable elements */
109 | @media screen and (prefers-reduced-transparency: reduce) {
110 | * {
111 | backdrop-filter: unset;
112 | }
113 | }
--------------------------------------------------------------------------------
/src/components/demos/ResponsiveProfileCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Follow
6 |
7 |
8 |
Sally Ramos
9 | @sallyramos
10 |
11 |
Product Designer at @company. Love to read, play and meditate in my free time.
12 |
15k Followers • 7k Following • Since April 30, 2017
13 |
Follow
14 |
15 |
16 |
17 |
107 |
--------------------------------------------------------------------------------
/public/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
31 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/components/github/GithubRepoCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ repository.nameWithOwner }}
7 |
8 |
9 |
10 | {{ repository.description }}
11 |
12 |
13 |
14 |
15 |
16 | {{ repository.primaryLanguage.name }}
17 |
18 |
19 |
20 |
21 | Last PR #{{ number }}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
44 |
45 |
106 |
--------------------------------------------------------------------------------
/src/components/demos/ContainerUnits.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ cqi }}cqi
9 |
10 |
11 | = {{ parsed.inline }}px
12 |
13 |
14 |
15 |
16 |
17 | {{ cqb }}cqb
18 |
19 |
20 | = {{ parsed.block }}px
21 |
22 |
23 |
24 |
25 |
26 | {{ cqmax }}cqmax
27 |
28 |
29 | = {{ parsed.larger }}px
30 |
31 |
32 |
33 |
34 |
35 | {{ cqmin }}cqmin
36 |
37 |
38 | = {{ parsed.smaller }}px
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
72 |
73 |
106 |
--------------------------------------------------------------------------------
/src/components/projects/ProjectCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
12 |
19 |
20 |
21 |
22 |
23 |
54 |
55 |
117 |
--------------------------------------------------------------------------------
/src/components/demos/SmartGrid.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 |
11 | Small
12 |
13 |
14 |
15 |
16 |
18 |
19 |
20 | Large
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 | List
31 |
32 |
33 |
--gallery-columns: {{ columns }}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
57 |
58 |
81 |
--------------------------------------------------------------------------------
/src/assets/styles/markdown.css:
--------------------------------------------------------------------------------
1 | @layer markdown {
2 | :root {
3 | --shiki-color-text: hsl(0deg 0% 83%);
4 | --shiki-color-background: hsl(235deg 19% 13%);
5 | --shiki-token-constant: hsl(145deg 46% 51%);
6 | --shiki-token-string: hsl(40deg 67% 51%);
7 | --shiki-token-comment: hsl(229deg 23% 44%);
8 | --shiki-token-keyword: hsl(259deg 59% 59%);
9 | --shiki-token-parameter: hsl(0deg 100% 33%);
10 | --shiki-token-function: hsl(266deg 59% 70%);
11 | --shiki-token-string-expression: hsl(190deg 91% 48%);
12 | --shiki-token-punctuation: hsl(0deg 0% 83%);
13 | --shiki-token-link: hsl(0deg 100% 47%);
14 | --shiki-highlited-line: hsl(258deg 50% 52% / 40%);
15 | }
16 |
17 | .shiki {
18 | position: relative;
19 | border-radius: var(--radius-default);
20 | padding: 0.875rem;
21 | overflow-x: auto;
22 |
23 | *::selection {
24 | background: hsl(0deg 0% 0% / 30%);
25 | }
26 |
27 | & code {
28 | counter-reset: step;
29 | counter-increment: step 0;
30 | }
31 |
32 | & span {
33 | font-family: var(--font-mono);
34 | }
35 |
36 | & .line:empty {
37 | display: none;
38 | }
39 |
40 | & .line.highlighted {
41 | display: inline-block;
42 | background: var(--shiki-highlited-line);
43 | width: 100%;
44 | border-radius: var(--radius-default);
45 | }
46 |
47 | & code:has(.line:nth-child(3n)) .line::before {
48 | display: inline-block;
49 | width: 10px;
50 | content: counter(step);
51 | counter-increment: step;
52 | color: hsl(0deg 0% 60%);
53 | margin-inline-end: 1.5rem;
54 | }
55 |
56 | &::-webkit-scrollbar {
57 | width: 1em;
58 | }
59 |
60 | &::-webkit-scrollbar-track {
61 | background-color: var(--scrollbar-bg);
62 | box-shadow: inset 0 0 6px rgb(0 0 0 / 50%);
63 | border-radius: 0;
64 | }
65 |
66 | &::-webkit-scrollbar-thumb {
67 | border-radius: 10px;
68 | background-color: var(--scrollbar-thumb-color);
69 | border: 5px solid var(--scrollbar-border);
70 | }
71 |
72 | & .copy-to-clipboard {
73 | position: absolute;
74 | top: 20px;
75 | right: 20px;
76 | display: inline-flex;
77 | align-items: center;
78 | justify-content: center;
79 | background: transparent;
80 | opacity: 0.4;
81 | border: 1px solid var(--color-silver);
82 | border-radius: var(--radius-default);
83 | padding: 0.25rem;
84 | transition: opacity 400ms ease-out, scale 600ms ease-in;
85 | }
86 |
87 | & .copy-to-clipboard:active {
88 | scale: 0.4;
89 | }
90 |
91 | & .copy-to-clipboard:hover {
92 | opacity: 0.8;
93 | }
94 |
95 | & .copy-to-clipboard svg {
96 | width: 24px;
97 | height: 24px;
98 | }
99 |
100 | & code:not(:has(.line:nth-child(3n)))+.copy-to-clipboard {
101 | top: 10px;
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/components/article/GalaxyBackground.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
32 |
33 |
--------------------------------------------------------------------------------
/src/components/layout/MobileNavbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
32 |
33 |
138 |
--------------------------------------------------------------------------------
/src/pages/now.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: /now
3 | meta:
4 | - name: description
5 | content: What it is happening now?
6 | - name: og:title
7 | content: /now
8 | - name: twitter:title
9 | content: /now
10 | ---
11 |
12 |
13 |
14 | JUL 2025
15 |
16 | # Current Status
17 |
18 | I'm currently reading: Software Architecture: The Hard Parts by Neal Ford & cia.
19 |
20 | If you have suggestions or wish I write about any other topic, [feel free to submit here](https://forms.gle/ftUPgfqW7ghzQgTY9).
21 |
22 | I'm not really active in social media, but if you want to get in touch:
23 |
24 | 🌍 Socials:
25 |
26 | - Bluesky: [@jeferson-brito.bsky.social](https://bsky.app/profile/jeferson-brito.bsky.social)
27 | - Mastodon: [@jeferson_brito](https://mastodon.social/@jeferson_brito)
28 |
29 | 📺 Watching: Wind Breaker, DanDaDan, Apothecary diaries
30 |
31 | 🎮 Currently playing: Spiderman Remastered, Persona Phantom X .
32 |
33 |
34 |
35 |
36 |
37 | (from Tenor)
38 |
39 |
40 |
136 |
--------------------------------------------------------------------------------
/src/components/demos/CustomThemes.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Warm
5 |
6 |
7 |
Dark
8 |
9 |
16 |
Spacegray
17 |
18 |
19 |
20 |
Headline
21 |
22 |
23 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Repudiandae
24 | inventore neque nostrum sint rerum exercitationem possimus assumenda
25 | doloremque nemo? Quis.
26 |
27 |
28 |
33 |
34 |
35 |
36 |
37 |
38 |
72 |
73 |
123 |
--------------------------------------------------------------------------------
/src/components/svg/SpotLightSVG.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
18 |
22 |
26 |
30 |
34 |
38 |
39 |
43 |
44 |
52 |
53 |
54 |
55 |
56 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/components/demos/ColorMix.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | in
19 |
21 |
22 |
23 | L {{ colorStopLeft }}%
24 |
25 | R {{ colorStopRight }}%
26 |
27 |
28 |
29 |
30 |
31 |
69 |
70 |
124 |
--------------------------------------------------------------------------------
/auto-imports.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by 'unplugin-auto-import'
2 | export {}
3 | declare global {
4 | const EffectScope: typeof import('vue')['EffectScope']
5 | const computed: typeof import('vue')['computed']
6 | const createApp: typeof import('vue')['createApp']
7 | const customRef: typeof import('vue')['customRef']
8 | const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
9 | const defineComponent: typeof import('vue')['defineComponent']
10 | const effectScope: typeof import('vue')['effectScope']
11 | const getCurrentInstance: typeof import('vue')['getCurrentInstance']
12 | const getCurrentScope: typeof import('vue')['getCurrentScope']
13 | const h: typeof import('vue')['h']
14 | const inject: typeof import('vue')['inject']
15 | const isProxy: typeof import('vue')['isProxy']
16 | const isReactive: typeof import('vue')['isReactive']
17 | const isReadonly: typeof import('vue')['isReadonly']
18 | const isRef: typeof import('vue')['isRef']
19 | const markRaw: typeof import('vue')['markRaw']
20 | const nextTick: typeof import('vue')['nextTick']
21 | const onActivated: typeof import('vue')['onActivated']
22 | const onBeforeMount: typeof import('vue')['onBeforeMount']
23 | const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
24 | const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
25 | const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
26 | const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
27 | const onDeactivated: typeof import('vue')['onDeactivated']
28 | const onErrorCaptured: typeof import('vue')['onErrorCaptured']
29 | const onMounted: typeof import('vue')['onMounted']
30 | const onRenderTracked: typeof import('vue')['onRenderTracked']
31 | const onRenderTriggered: typeof import('vue')['onRenderTriggered']
32 | const onScopeDispose: typeof import('vue')['onScopeDispose']
33 | const onServerPrefetch: typeof import('vue')['onServerPrefetch']
34 | const onUnmounted: typeof import('vue')['onUnmounted']
35 | const onUpdated: typeof import('vue')['onUpdated']
36 | const provide: typeof import('vue')['provide']
37 | const reactive: typeof import('vue')['reactive']
38 | const readonly: typeof import('vue')['readonly']
39 | const ref: typeof import('vue')['ref']
40 | const resolveComponent: typeof import('vue')['resolveComponent']
41 | const resolveDirective: typeof import('vue')['resolveDirective']
42 | const shallowReactive: typeof import('vue')['shallowReactive']
43 | const shallowReadonly: typeof import('vue')['shallowReadonly']
44 | const shallowRef: typeof import('vue')['shallowRef']
45 | const toRaw: typeof import('vue')['toRaw']
46 | const toRef: typeof import('vue')['toRef']
47 | const toRefs: typeof import('vue')['toRefs']
48 | const triggerRef: typeof import('vue')['triggerRef']
49 | const unref: typeof import('vue')['unref']
50 | const useAttrs: typeof import('vue')['useAttrs']
51 | const useCssModule: typeof import('vue')['useCssModule']
52 | const useCssVars: typeof import('vue')['useCssVars']
53 | const useHead: typeof import('@unhead/vue')['useHead']
54 | const useLink: typeof import('vue-router')['useLink']
55 | const useRoute: typeof import('vue-router')['useRoute']
56 | const useRouter: typeof import('vue-router')['useRouter']
57 | const useSlots: typeof import('vue')['useSlots']
58 | const watch: typeof import('vue')['watch']
59 | const watchEffect: typeof import('vue')['watchEffect']
60 | const watchPostEffect: typeof import('vue')['watchPostEffect']
61 | const watchSyncEffect: typeof import('vue')['watchSyncEffect']
62 | }
63 |
--------------------------------------------------------------------------------
/src/components/hero/HeroSection.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Hey, I'm Jeferson 🖖
5 |
6 | Software Engineer that crafts things for the Web, heavy focused on
7 | front-end development, enthusiastic and fascinated on user interfaces.
8 |
9 |
10 |
11 |
12 |
13 | web developer
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
135 |
--------------------------------------------------------------------------------
/src/components/svg/LampOnSVG.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
13 |
18 |
23 |
28 |
33 |
38 |
43 |
44 |
45 |
46 |
149 |
--------------------------------------------------------------------------------
/src/components/svg/NetlifyLogoSVG.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hosted on Netlify
4 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import fs from 'node:fs'
3 | import { defineConfig } from 'vite'
4 | import Vue from '@vitejs/plugin-vue'
5 | import Pages from 'vite-plugin-pages'
6 | import Layouts from 'vite-plugin-vue-layouts'
7 | import DevTools from 'vite-plugin-vue-devtools'
8 | import Components from 'unplugin-vue-components/vite'
9 | import AutoImport from 'unplugin-auto-import/vite'
10 | import Markdown from 'unplugin-vue-markdown/vite'
11 | import { VitePWA } from 'vite-plugin-pwa'
12 | import Shiki from 'markdown-it-shiki'
13 | import MarkdownItAnchor from 'markdown-it-anchor'
14 | import matter from 'gray-matter'
15 | import generateSitemap from 'vite-ssg-sitemap'
16 |
17 | import { copyPlugin } from './lib/md/copy-code'
18 |
19 | export default defineConfig({
20 | resolve: {
21 | alias: {
22 | '@/': `${path.resolve(__dirname, 'src')}/`,
23 | '@content': `${path.resolve(__dirname, 'content')}/`,
24 | '@lib': `${path.resolve(__dirname, 'lib')}`
25 | },
26 | },
27 | plugins: [
28 | Vue({
29 | include: [/\.vue$/, /\.md$/],
30 | }),
31 |
32 | Pages({
33 | extensions: ['vue', 'md'],
34 | dirs: ['src/pages', { dir: 'content/articles', baseRoute: 'articles' }],
35 | exclude: ['**/draft/*'],
36 | extendRoute(route, _parent) {
37 | const isArticlePost =
38 | route.path.includes('/articles') && route.path !== '/articles'
39 | const componentPath = path.resolve(__dirname, route.component.slice(1))
40 |
41 | if (isArticlePost) {
42 | const md = fs.readFileSync(componentPath, 'utf-8')
43 | const { data: frontmatter } = matter(md)
44 |
45 | return {
46 | ...route,
47 | meta: { layout: 'article', frontmatter },
48 | }
49 | }
50 |
51 | return route
52 | },
53 | }),
54 |
55 | Layouts(),
56 |
57 | AutoImport({
58 | imports: ['vue', 'vue-router', { '@unhead/vue': ['useHead'] }],
59 | dirs: [
60 | 'src/composables',
61 | 'src/components'
62 | ]
63 | }),
64 |
65 | Components({
66 | extensions: ['vue', 'md'],
67 | include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
68 | }),
69 |
70 | Markdown({
71 | wrapperClasses: 'prose',
72 | headEnabled: true,
73 | markdownItSetup(md) {
74 | md.use(copyPlugin);
75 | md.use(Shiki, {
76 | theme: 'css-variables',
77 | highlightLines: true,
78 | })
79 | md.use(MarkdownItAnchor, {
80 | permalink: MarkdownItAnchor.permalink.linkInsideHeader({
81 | symbol: `
82 | Jump to heading
83 |
84 |
85 |
86 | `,
87 | placement: 'before',
88 | }),
89 | })
90 | },
91 | }),
92 |
93 | VitePWA({
94 | registerType: 'autoUpdate',
95 | workbox: {
96 | globPatterns: ['**/*{js,css,html,txt,ico,png,svg}'],
97 | cleanupOutdatedCaches: true,
98 | },
99 | }),
100 |
101 | DevTools()
102 | ],
103 | ssgOptions: {
104 | script: 'async',
105 | formatting: 'minify',
106 | beastiesOptions: {
107 | reduceInlineStyles: false,
108 | },
109 | onFinished() {
110 | generateSitemap()
111 | },
112 | },
113 | optimizeDeps: {
114 | include: ['vue', 'vue-router'],
115 | },
116 | server: {
117 | port: 3000,
118 | },
119 | preview: {
120 | port: 8080,
121 | },
122 | })
123 |
--------------------------------------------------------------------------------
/src/components/demos/AnimatedSVG.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
14 |
17 |
21 |
25 |
29 |
30 | hover me!
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/components/ui/Button.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
67 |
68 |
184 |
--------------------------------------------------------------------------------
/content/articles/how-to-keep-up.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: mhri2af6i
3 | title: How to keep up with things
4 | published_at: 2024-05-17T22:55:34.127Z
5 | tags: opinion
6 | excerpt: I have a feeling we are on a constant flux of ever-evolving modern world where saying updated is integral to our personal growth...
7 | slug: how-to-keep-up
8 | crosspostedOn: ''
9 | crosspostLink: ''
10 | og_image: https://firebasestorage.googleapis.com/v0/b/portfolio-d3c7c.appspot.com/o/og%2Fhow-to-keep-up.webp?alt=media&token=3236f604-7b3e-41df-b062-c977a1fb633f
11 | ---
12 |
13 | I have a feeling we are in constant flux in the ever-evolving modern world where staying updated is integral to our personal growth. You want to watch every basketball match of your favorite team this season and catch up with new episodes of One Piece but you've stopped on episode 45, watch every talk, follow every new trend, learn every new possible feature, and have a normal life?
14 |
15 | How can you possibly keep up with all of this?
16 | Short answer: **you can't**.
17 |
18 | You could say you were able to do this before, so why suddenly you can't do it anymore? Well, things change all the time, and they change differently .
19 |
20 | Especially in the **programming realm**, take web development for instance, it seems things are being delivered at an incredible pace right now, and this is for good. Just pause and imagine going back 3 or 4 years ago, how your JavaScript or CSS (especially) code was compared to what it can be today (with no extra tools!), it's crazy how many features we have now.
21 |
22 | So, what can you do to still be up to date without being exhausted after being bombarded with information?
23 |
24 | Well, I start by sharing some thoughts I had when I had this feeling.
25 |
26 | ## Don’t get trapped in the FOMO loop
27 |
28 | It is natural to feel excited when new things come out, Gemini, GPT-4o, Blue Origin, and so on. As I started learning programming I wanted to try every new thing that came out that I found interesting which led me from countless hours experimenting to, most of the time, not applying them in real projects. It can feel very frustrating at times.
29 |
30 | These are things that can keep you in a fear of missing out (FOMO) loop to having consume much more content than you needed to not stay "behind" or to not feel like missing something critical, when, most of the time, you're weren't. Remember, technology isn't deterministic, things can still change.
31 |
32 | You see, I needed to let things mature first and move at my own pace while not being completely disconnected from what is happening around the world.
33 |
34 | This led me to prioritizing and filtering what kind of content I consumed frequently.
35 |
36 | ## Filter noise
37 |
38 | What do you mean _noise_ ? You might ask.
39 |
40 | In the context of this post, you can refer to noise as **information that is either redundant or not entirely relevant to you at this moment**. This includes any information that does not align with your interests, goals, or needs at the current time.
41 |
42 | To manage this "noise cancellation", I needed to essentially curate the sources of information I was typically used to consuming on a daily basis. Although this required me some amount of good discipline, once I started to do that, I could see myself having more focus on doing regular tasks and having more spare time in the end.
43 |
44 | In the same way, each person can have a stronger learning by reading instead of watching, each one can have valid sources of information they can rely on. For me, this meant focusing on a more narrow set of authors that I really like and seeing what they were doing on their personal blog posts or podcasts, instead of seeing on social media.
45 |
46 | ## Find your own pace
47 |
48 | Everybody learns at different paces and sometimes in unique ways. Don't just give up what you're doing now to involved in a new project to use the shiny new tool XYZ. Understand the moment you're leading to.
49 |
50 | What helped me in my journey was to always have a clear **main focus** first then a list of things that I could potentially experiment with to solve challenges when needed to. This could be an algorithm, a data structure, a library or a framework.
51 |
52 | Last but not least, make learning a continuous habit. If you feel free enough to experiment with new things you're interested at the moment, then go ahead and try! Otherwise, write them down somewhere to explore later.
53 |
54 | Take breaks, follow your goals, and set your eyes on the future!
55 |
--------------------------------------------------------------------------------
/src/components/demos/GeneratePallete.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Pick a color
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
87 |
88 |
142 |
--------------------------------------------------------------------------------
/src/components/ui/CoffeeButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
53 |
54 |
192 |
--------------------------------------------------------------------------------
/content/projects/projects.json:
--------------------------------------------------------------------------------
1 | {
2 | "aircnc": {
3 | "order": 1,
4 | "thumbnail": "https://firebasestorage.googleapis.com/v0/b/portfolio-d3c7c.appspot.com/o/posts_media%2Faircnc.png?alt=media&token=0e827724-c281-4196-b801-48c0194cdbf0",
5 | "title": "Aircnc",
6 | "tags": "Fullstack",
7 | "demo": "https://aircnc-web.herokuapp.com/",
8 | "github": "https://github.com/jeferson-sb/aircnc"
9 | },
10 | "chatme": {
11 | "order": 2,
12 | "thumbnail": "https://firebasestorage.googleapis.com/v0/b/portfolio-d3c7c.appspot.com/o/posts_media%2Fchatme.png?alt=media&token=f1a2d950-6c59-4fe6-a9ea-e9d11b8ee255",
13 | "title": "ChatMe",
14 | "tags": "Node.js, Vue.js, Socket.io",
15 | "demo": "https://whispering-reaches-62773.herokuapp.com/",
16 | "github": "https://github.com/jeferson-sb/node-chat-app",
17 | "pinned": true,
18 | "description": "Lightweight chat app built with Vue 2, Express and Socket.io"
19 | },
20 | "whatconf": {
21 | "thumbnail": "https://firebasestorage.googleapis.com/v0/b/portfolio-d3c7c.appspot.com/o/posts_media%2Fwhatconf.png?alt=media&token=a34f3155-b326-4944-a767-82fcc108092e",
22 | "title": "WhatConf",
23 | "tags": "Next.js, tRPC, Prisma",
24 | "demo": "https://whatconf.vercel.app/",
25 | "github": "https://github.com/jeferson-sb/whatconf",
26 | "pinned": true,
27 | "description": "Tech conferences feed that users can check its date and details and subscribe to receive web notifications!"
28 | },
29 | "dogAI": {
30 | "order": 1,
31 | "thumbnail": "https://firebasestorage.googleapis.com/v0/b/portfolio-d3c7c.appspot.com/o/posts_media%2FdogAI.jpg?alt=media&token=8a340fd4-749b-41c8-8200-f51db8df27cf",
32 | "title": "Dog AI",
33 | "tags": "Front-end, React, Tensorflow",
34 | "demo": "https://dogai.netlify.com/",
35 | "github": "https://github.com/jeferson-sb/dogAI",
36 | "pinned": true,
37 | "description": "Dog breed image recognition with Teachable Machine & Tensorflow.js"
38 | },
39 | "flashmemo": {
40 | "order": 3,
41 | "thumbnail": "https://firebasestorage.googleapis.com/v0/b/portfolio-d3c7c.appspot.com/o/posts_media%2Fflashmemo.jpg?alt=media&token=355cda40-b248-44fb-92b3-eae0e0bf3a0f",
42 | "title": "Flashmemo",
43 | "tags": "Ruby on Rails",
44 | "github": "https://github.com/jeferson-sb/flashmemo",
45 | "pinned": true,
46 | "description": "REST API with rewards system designed to help users practice their knowledge by answering exams."
47 | },
48 | "devradar": {
49 | "order": 4,
50 | "thumbnail": "https://firebasestorage.googleapis.com/v0/b/portfolio-d3c7c.appspot.com/o/posts_media%2Fdevradar.jpg?alt=media&token=1f23fb7e-23e2-4270-a057-d724b3b880eb",
51 | "title": "DevRadar",
52 | "tags": "Full Stack",
53 | "demo": "https://devradar-api.appspot.com/",
54 | "github": "https://github.com/jeferson-sb/devradar"
55 | },
56 | "gobarber": {
57 | "thumbnail": "https://firebasestorage.googleapis.com/v0/b/portfolio-d3c7c.appspot.com/o/posts_media%2Fgobarber.jpg?alt=media&token=9a733a14-f498-465a-96dd-e034f773513a",
58 | "title": "GoBarber",
59 | "tags": "Node.js, React, TypeScript",
60 | "github": "https://github.com/jeferson-sb/gobarber-web",
61 | "description": "React app to manage haircuts appointments"
62 | },
63 | "ecoleta": {
64 | "thumbnail": "https://firebasestorage.googleapis.com/v0/b/portfolio-d3c7c.appspot.com/o/posts_media%2Fecoleta.png?alt=media&token=43931213-70df-4a66-9cf4-d6bb6fe50889",
65 | "title": "Ecoleta",
66 | "tags": "React, React Native, Node.js, TypeScript",
67 | "demo": "https://ecoleta-nlw.netlify.app/",
68 | "github": "https://github.com/jeferson-sb/ecoleta",
69 | "description": "Marketplace de coleta de resíduos"
70 | },
71 | "tindev": {
72 | "thumbnail": "https://firebasestorage.googleapis.com/v0/b/portfolio-d3c7c.appspot.com/o/posts_media%2Ftindev.png?alt=media&token=f59beae0-3f38-45dc-90ef-8d1ec87b0f43",
73 | "title": "Tindev",
74 | "tags": "React, React Native, Node.js, TypeScript",
75 | "demo": "https://tindev-app.herokuapp.com",
76 | "github": "https://github.com/jeferson-sb/tindev"
77 | },
78 | "bethehero": {
79 | "order": 4,
80 | "thumbnail": "https://firebasestorage.googleapis.com/v0/b/portfolio-d3c7c.appspot.com/o/posts_media%2Fbethehero.png?alt=media&token=5af9bc3e-0e5f-4758-bc24-8775d4fdd10a",
81 | "title": "Be the Hero",
82 | "tags": "Node.js, React, React Native",
83 | "demo": "https://be-thehero.netlify.com/",
84 | "github": "https://github.com/jeferson-sb/be-the-hero",
85 | "pinned": false,
86 | "description": "Donate System for NGOs campaigns"
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/pages/about-me.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | About me
10 |
11 |
12 | 👋 Hey! How are you?
13 | I'm Jeferson. I'm a
14 | software engineer dedicated to
15 | crafting things for the web through coding.
16 |
17 |
18 | I wrote my first lines of code back in 2014 of HTML4, CSS and ES5
19 | using just a notepad in a very low-end machine 😅. Like many people I
20 | graduated a few years later and since then I've been on this adventure
21 | into the world of programming.
22 |
23 |
24 | I don't have "favorites", but I like to code on TypeScript, Elixir, Lua, and I'm starting to tinker with Rust.
25 | I also like to think outside coding, such as prioritizing tasks/features, doing reviews, modeling and
26 | providing the good
27 | practices, fundamentals, and consistency throughout the code.
28 |
29 | I've worked on websites (both front-end, back-end), CLI's, and most recently mobile development (React
30 | Native).
31 | Personally, I'm into computers, ui design, games and anime (of course) :P
32 |
33 |
34 |
35 |
36 |
37 |
38 |
67 |
68 |
69 | meta:
70 | layout: default
71 |
72 |
73 |
195 |
--------------------------------------------------------------------------------
/src/components/demos/Resizable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
35 |
--------------------------------------------------------------------------------
/components.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* prettier-ignore */
3 | // @ts-nocheck
4 | // Generated by unplugin-vue-components
5 | // Read more: https://github.com/vuejs/core/pull/3399
6 | export {}
7 |
8 | declare module 'vue' {
9 | export interface GlobalComponents {
10 | AnimatedBlocks: typeof import('./src/components/demos/AnimatedBlocks.vue')['default']
11 | AnimatedBook: typeof import('./src/components/demos/AnimatedBook.vue')['default']
12 | AnimatedBox: typeof import('./src/components/demos/AnimatedBox.vue')['default']
13 | AnimatedButton: typeof import('./src/components/demos/AnimatedButton.vue')['default']
14 | AnimatedSVG: typeof import('./src/components/demos/AnimatedSVG.vue')['default']
15 | AnimatedSVGs: typeof import('./src/components/demos/AnimatedSVGs.vue')['default']
16 | AppLink: typeof import('./src/components/base/AppLink.vue')['default']
17 | ArticleControls: typeof import('./src/components/article/ArticleControls.vue')['default']
18 | ArticleHeading: typeof import('./src/components/article/ArticleHeading.vue')['default']
19 | ArticlesHero: typeof import('./src/components/article/ArticlesHero.vue')['default']
20 | BaseInput: typeof import('./src/components/forms/BaseInput.vue')['default']
21 | BaseRange: typeof import('./src/components/forms/BaseRange.vue')['default']
22 | BaseSelect: typeof import('./src/components/forms/BaseSelect.vue')['default']
23 | BinaryTextSVG: typeof import('./src/components/svg/BinaryTextSVG.vue')['default']
24 | Button: typeof import('./src/components/ui/Button.vue')['default']
25 | ButtonGroup: typeof import('./src/components/ui/ButtonGroup.vue')['default']
26 | CircleFillSVG: typeof import('./src/components/svg/CircleFillSVG.vue')['default']
27 | Clock: typeof import('./src/components/ui/Clock.vue')['default']
28 | CoffeeButton: typeof import('./src/components/ui/CoffeeButton.vue')['default']
29 | CoffeeSVG: typeof import('./src/components/svg/CoffeeSVG.vue')['default']
30 | ColorBox: typeof import('./src/components/demos/ColorBox.vue')['default']
31 | ColorMix: typeof import('./src/components/demos/ColorMix.vue')['default']
32 | CustomList: typeof import('./src/components/article/CustomList.vue')['default']
33 | CustomThemes: typeof import('./src/components/demos/CustomThemes.vue')['default']
34 | Footer: typeof import('./src/components/layout/Footer.vue')['default']
35 | GalaxyBackground: typeof import('./src/components/article/GalaxyBackground.vue')['default']
36 | GeneratePallete: typeof import('./src/components/demos/GeneratePallete.vue')['default']
37 | GithubCardEmpty: typeof import('./src/components/github/GithubCardEmpty.vue')['default']
38 | GithubProjects: typeof import('./src/components/github/GithubProjects.vue')['default']
39 | GithubRepoCard: typeof import('./src/components/github/GithubRepoCard.vue')['default']
40 | GridsomeSVG: typeof import('./src/components/svg/GridsomeSVG.vue')['default']
41 | HamburgerMenu: typeof import('./src/components/ui/HamburgerMenu.vue')['default']
42 | HeroAvatar: typeof import('./src/components/hero/HeroAvatar.vue')['default']
43 | HeroSection: typeof import('./src/components/hero/HeroSection.vue')['default']
44 | ImgLazy: typeof import('./src/components/ui/ImgLazy.vue')['default']
45 | InteractiveWrapper: typeof import('./src/components/ui/InteractiveWrapper.vue')['default']
46 | LampOffSVG: typeof import('./src/components/svg/LampOffSVG.vue')['default']
47 | LampOnSVG: typeof import('./src/components/svg/LampOnSVG.vue')['default']
48 | MobileNavbar: typeof import('./src/components/layout/MobileNavbar.vue')['default']
49 | Navbar: typeof import('./src/components/layout/Navbar.vue')['default']
50 | NetlifyLogoSVG: typeof import('./src/components/svg/NetlifyLogoSVG.vue')['default']
51 | OctocatSVG: typeof import('./src/components/svg/OctocatSVG.vue')['default']
52 | PagesSVG: typeof import('./src/components/svg/PagesSVG.vue')['default']
53 | PostPreview: typeof import('./src/components/posts/PostPreview.vue')['default']
54 | PostsList: typeof import('./src/components/posts/PostsList.vue')['default']
55 | ProjectCard: typeof import('./src/components/projects/ProjectCard.vue')['default']
56 | ProjectsGrid: typeof import('./src/components/projects/ProjectsGrid.vue')['default']
57 | PullRequestSVG: typeof import('./src/components/svg/PullRequestSVG.vue')['default']
58 | RippleButton: typeof import('./src/components/ui/RippleButton.vue')['default']
59 | RouterLink: typeof import('vue-router')['RouterLink']
60 | RouterView: typeof import('vue-router')['RouterView']
61 | ScrollIndicator: typeof import('./src/components/ui/ScrollIndicator.vue')['default']
62 | ShareButton: typeof import('./src/components/ui/ShareButton.vue')['default']
63 | SkipLink: typeof import('./src/components/ui/SkipLink.vue')['default']
64 | SmartGrid: typeof import('./src/components/demos/SmartGrid.vue')['default']
65 | SocialMediaIcons: typeof import('./src/components/social-media/SocialMediaIcons.vue')['default']
66 | SpotLightSVG: typeof import('./src/components/svg/SpotLightSVG.vue')['default']
67 | Tag: typeof import('./src/components/ui/Tag.vue')['default']
68 | TagGroup: typeof import('./src/components/ui/TagGroup.vue')['default']
69 | ThemeSwitcher: typeof import('./src/components/ui/ThemeSwitcher.vue')['default']
70 | VueSVG: typeof import('./src/components/svg/VueSVG.vue')['default']
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/components/posts/PostPreview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ formattedDate }}
6 |
7 |
8 |
9 |
10 |
11 | {{ title }}
12 |
13 |
14 |
15 |
16 |
17 | {{ excerpt }}
18 |
19 | Read more
20 |
21 |
22 |
23 |
24 |
82 |
83 |
217 |
--------------------------------------------------------------------------------
/src/assets/styles/global.css:
--------------------------------------------------------------------------------
1 | /* Themes and defaults */
2 |
3 | @custom-media --vw-sm (max-width: 26.56em);
4 | @custom-media --vw-md (max-width: 48em);
5 | @custom-media --vw-lg (max-width: 64em);
6 | @custom-media --vw-xl (max-width: 90em);
7 |
8 | @layer global {
9 | :root {
10 |
11 | /* Colors */
12 | --color-primary: oklch(0.7282 0.1222 209.58);
13 | --color-accent: oklch(70.662% 0.15099 54.296);
14 | --color-silver: oklch(56.83% 0.04657 270.021);
15 | --color-silver-600: oklch(66.362% 0.05373 271.257);
16 | --color-default-black: oklch(22.559% 0.00659 258.421);
17 | --color-default-white: oklch(88.531% 0.0001 271.152);
18 | --color-gray-800: oklch(26.721% 0.00866 255.642);
19 | --color-gray-700: oklch(35.827% 0.00663 248.157);
20 | --color-gray-600: oklch(45.747% 0.0172 254.764);
21 | --color-gray-500: oklch(54.52% 0.02174 255.656);
22 | --color-gray-400: oklch(63.336% 0.02095 255.643);
23 | --color-gray-300: oklch(71.914% 0.01538 254.731);
24 | --color-gray-200: oklch(80.047% 0.01089 261.871);
25 | --color-gray-100: oklch(88.108% 0.00648 255.725);
26 | --text-color-default: var(--color-default-white);
27 | --paragraph-color: oklch(84.522% 0.0001 271.152);
28 | --code-bg-color: oklch(29.252% 0.0157 264.319);
29 | --code-highlight-color: oklch(72.72% 0.12217 210.085 / 0.05);
30 |
31 | /* Typography */
32 | --text-xsm: 0.75rem;
33 | --text-sm: 0.875rem;
34 | --text-base: 1rem;
35 | --text-lg: 1.125rem;
36 | --text-xl: 1.25rem;
37 | --text-2xl: 2.5rem;
38 | --text-3xl: 3.75rem;
39 |
40 | --fw-normal: 500;
41 | --fw-bold: 700;
42 |
43 | --font-sans: 'Source Sans Pro', -apple-system, 'Segoe UI',
44 | helvetica sans-serif;
45 | --font-title: 'Raleway', sans-serif;
46 | --font-serif: 'Roboto Slab', palatino, georgia, serif;
47 | --font-mono: 'Source Code Pro', 'SF Mono', consolas, menlo, monospace;
48 |
49 | /* Borders */
50 | --radius-default: 4px;
51 | --radius-full: 9999px;
52 |
53 | --border-width: 1px;
54 | --border-style: solid;
55 | --border-color: oklch(0% 0 0 / 0.1);
56 | --border: var(--border-width) var(--border-style) var(--border-color);
57 |
58 | /* Shadows */
59 | --elevation-1: 0 1px 3px oklch(0% 0 0 / 0.2);
60 | --elevation-2: 0 4px 4px oklch(0% 0 0 / 0.23);
61 | --elevation-3: 0 4px 10px oklch(0% 0 0 / 0.07);
62 | --elevation-4: 0 10px 19px oklch(0% 0 0 / 0.27);
63 | --elevation-5: 0 20px 25px -5px oklch(0% 0 0 / 0.3);
64 |
65 | /* Z-layers */
66 | --z-10: 10;
67 | --z-100: 100;
68 | --z-base: 1;
69 | --z-hide: -1;
70 |
71 | /* Others */
72 | --scrollbar-bg: oklch(26.721% 0.00866 255.642);
73 | --scrollbar-thumb-color: oklch(56.83% 0.04657 270.021);
74 | --scrollbar-border: var(--color-gray-800);
75 | --selection: oklch(71.531% 0.17937 50.596 / 0.5);
76 | }
77 |
78 | body {
79 | --base-font-size: 16px;
80 |
81 | font-family: var(--font-sans);
82 | font-size: var(--base-font-size);
83 | accent-color: var(--color-silver);
84 | background-color: var(--color-default-black);
85 | color: var(--color-default-white);
86 | color-scheme: light dark;
87 | scrollbar-gutter: stable;
88 | text-rendering: optimizelegibility;
89 | color-rendering: optimizespeed;
90 | -webkit-font-smoothing: antialiased;
91 | -moz-osx-font-smoothing: grayscale;
92 | line-height: 1.5;
93 | }
94 |
95 | ::selection {
96 | background: var(--selection);
97 | color: var(--text-color-default);
98 | }
99 |
100 | h1,
101 | h2,
102 | h3,
103 | h4,
104 | h5,
105 | h6 {
106 | text-wrap: balance;
107 | font-family: var(--font-title);
108 | }
109 |
110 | a {
111 | text-decoration: none;
112 | transition: text-decoration-color 400ms ease-out;
113 | color: var(--color-primary);
114 | }
115 |
116 | a:focus-visible {
117 | outline: 2px dashed var(--color-primary);
118 | }
119 |
120 | a:not([href]) {
121 | color: var(--text-color-default);
122 | opacity: 0.8;
123 | cursor: not-allowed;
124 | }
125 |
126 | a:not([class]):hover,
127 | a[class=""]:hover,
128 | abbr {
129 | text-decoration-line: underline;
130 | text-decoration-color: var(--color-accent);
131 | text-decoration-thickness: 2px;
132 | text-decoration-style: solid;
133 | text-underline-offset: 0.3em;
134 | }
135 | }
136 |
137 | @layer theme {
138 | body[data-theme='light'] {
139 | --color-light: oklch(97.352% 0.00553 274.897);
140 | --text-color-default: var(--color-default-black);
141 | --secondary-text-color: var(--color-silver);
142 | --bg-color-primary-lighter: oklch(72.72% 0.12217 210.085 / 0.3);
143 | --bg-color-lighter: oklch(97.614% 0.00011 271.152);
144 | --accent-color-lighter: oklch(95.533% 0.00842 271.332);
145 | --scrollbar-bg: var(--accent-color-lighter);
146 | --scrollbar-thumb-color: var(--color-gray-300);
147 | --scrollbar-border: var(--color-gray-300);
148 |
149 | background-color: var(--color-light);
150 | color: var(--color-default-black);
151 | transition-property: background-color, color;
152 | transition-duration: 500ms;
153 | transition-timing-function: ease-in-out;
154 |
155 | & :is(footer, header) {
156 | border-color: oklch(56.83% 0.04657 270.021 / 0.3);
157 | }
158 | }
159 |
160 | body[data-theme='dark'] {
161 | img {
162 | filter: brightness(0.8);
163 | }
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/components/layout/Navbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
45 |
46 |
47 |
63 |
64 |
209 |
--------------------------------------------------------------------------------
/src/components/demos/CSSPreview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
36 |
37 |
60 |
--------------------------------------------------------------------------------
/src/components/article/ArticleHeading.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ title }}
4 |
5 |
6 | Originally posted on
7 |
8 | {{ crosspostedOn }}
9 |
10 |
11 |
12 | {{ displayDate }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
47 |
48 |
103 |
--------------------------------------------------------------------------------