From bda5f86a793e752db7ea351aea7a8d4068dafc30 Mon Sep 17 00:00:00 2001 From: mahdiarghyani Date: Sat, 13 Dec 2025 12:59:24 +0330 Subject: [PATCH] feat: Implement initial portfolio website structure with components for various sections, i18n, and GitHub integration. --- .env.example | 5 + app/assets/css/main.css | 87 ++++++++ app/components/LanguageSwitcher.vue | 2 +- app/components/common/FooterCopyright.vue | 4 +- app/components/portfolio/AIStack.vue | 10 +- app/components/portfolio/EducationList.vue | 6 +- app/components/portfolio/ExperienceList.vue | 6 +- app/components/portfolio/GitHubActivity.vue | 211 ++++++++++++++++++ app/components/portfolio/Hero.vue | 6 +- app/components/portfolio/LanguageSkills.vue | 6 +- app/components/portfolio/ProjectsList.vue | 8 +- .../portfolio/RecommendationsCarousel.vue | 8 +- app/components/portfolio/SkillFilters.vue | 2 +- app/components/portfolio/Skills.vue | 80 +++---- app/components/portfolio/SoftSkills.vue | 6 +- app/components/portfolio/WorkExperience.vue | 6 +- app/composables/useGitHubContributions.ts | 33 +++ app/data/portfolio.fa.ts | 4 +- app/data/portfolio.ts | 4 +- app/pages/index.vue | 4 +- app/types/github.ts | 32 +++ i18n/locales/en.json | 10 +- i18n/locales/fa.json | 10 +- nuxt.config.ts | 3 +- public/favicon/logo-nobg.svg | 1 + server/api/github/contributions.ts | 75 +++++++ 26 files changed, 547 insertions(+), 82 deletions(-) create mode 100644 app/components/portfolio/GitHubActivity.vue create mode 100644 app/composables/useGitHubContributions.ts create mode 100644 app/types/github.ts create mode 100644 public/favicon/logo-nobg.svg create mode 100644 server/api/github/contributions.ts diff --git a/.env.example b/.env.example index 4b85f25..450d9b4 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,6 @@ NUXT_PUBLIC_LOAD_PLAUSIBLE="no" + +# GitHub API Token for contribution graph +# Create a token at: https://github.com/settings/tokens +# Required scopes: read:user (optional but recommended for higher rate limits) +NUXT_PUBLIC_GITHUB_TOKEN="" diff --git a/app/assets/css/main.css b/app/assets/css/main.css index d147ba6..9cc724c 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -145,6 +145,44 @@ font-family: var(--font-sans); } + :root { + --scrollbar-track: color-mix(in oklch, #e0e7ff 35%, transparent); + --scrollbar-thumb: color-mix(in oklch, #7c3aed 65%, #6d28d9 35%); + --scrollbar-thumb-hover: color-mix(in oklch, #7c3aed 80%, #a855f7 20%); + } + + :root.dark { + --scrollbar-track: color-mix(in oklch, #0b1020 80%, #7c3aed 20%); + --scrollbar-thumb: color-mix(in oklch, #a855f7 65%, #7c3aed 35%); + --scrollbar-thumb-hover: color-mix(in oklch, #c084fc 75%, #8b5cf6 25%); + } + + /* Global scrollbar styling */ + * { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); + } + + *::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + *::-webkit-scrollbar-track { + background: var(--scrollbar-track); + border-radius: 9999px; + } + + *::-webkit-scrollbar-thumb { + background: linear-gradient(135deg, var(--scrollbar-thumb), color-mix(in oklch, var(--scrollbar-thumb), black 10%)); + border-radius: 9999px; + border: 2px solid color-mix(in oklch, var(--scrollbar-track), transparent 50%); + } + + *::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); + } + /* In Persian/RTL, prefer Vazirmatn for both body and headings via variables */ html[dir="rtl"], html[lang^="fa"] { @@ -199,6 +237,18 @@ @apply text-violet-600 dark:text-violet-400; } + .section-spacing { + @apply pt-5 pb-8 sm:pt-8 sm:pb-12; + } + + .section-title { + @apply text-xl sm:text-2xl font-semibold leading-tight text-gray-900 dark:text-gray-100; + } + + .section-header { + @apply flex flex-wrap items-center gap-3 mb-5 sm:mb-6; + } + .primary-text-muted { @apply text-zinc-500 dark:text-zinc-400; } @@ -287,6 +337,43 @@ .chip-button { @apply inline-flex items-center justify-center h-12 w-12 rounded-2xl ring-1 ring-violet-200/70 dark:ring-violet-800/50 bg-gradient-to-br from-white/90 to-violet-50/50 dark:from-white/10 dark:to-violet-950/30 text-violet-700 dark:text-violet-200 shadow-lg shadow-violet-500/10 backdrop-blur-md transition-all duration-300 ease-out hover:scale-110 hover:shadow-xl hover:shadow-violet-500/20 hover:from-violet-50 hover:to-violet-100 dark:hover:from-white/15 dark:hover:to-violet-900/40; } + + /* GitHub Activity Contribution Graph - Dynamic Primary Color */ + /* Level 0: No contributions - very neutral/muted color */ + .contrib-level-0 { + background-color: var(--ui-color-neutral-100); + } + /* Levels 1-4: More contributions = darker/more vibrant primary */ + .contrib-level-1 { + background-color: var(--ui-color-primary-200); + } + .contrib-level-2 { + background-color: var(--ui-color-primary-400); + } + .contrib-level-3 { + background-color: var(--ui-color-primary-600); + } + .contrib-level-4 { + background-color: var(--ui-color-primary-800); + } + + /* Dark mode overrides for contribution graph */ + /* In dark mode: brighter = more contributions (same visual logic as light) */ + .dark .contrib-level-0 { + background-color: var(--ui-color-neutral-800); + } + .dark .contrib-level-1 { + background-color: var(--ui-color-primary-950); + } + .dark .contrib-level-2 { + background-color: var(--ui-color-primary-800); + } + .dark .contrib-level-3 { + background-color: var(--ui-color-primary-600); + } + .dark .contrib-level-4 { + background-color: var(--ui-color-primary-400); + } } /* View Transitions ripple for theme switch (best-practice) */ diff --git a/app/components/LanguageSwitcher.vue b/app/components/LanguageSwitcher.vue index 958a4a6..0f2fcd1 100644 --- a/app/components/LanguageSwitcher.vue +++ b/app/components/LanguageSwitcher.vue @@ -3,7 +3,7 @@ +