Sync upstream improvements: new components, layout overhaul, and portfolio enhancements

- Add Services, HireCTA, and ReactExpansion components
- Overhaul Hero with grid layout, badges, CTA buttons, email dialog, and profile card
- Refactor TopNav to use computed navItems loop
- Enhance ProjectsList with nested category accordions and case study details
- Restructure Skills with purpose-grouped stackGroups and row-sync accordion logic
- Add new types: Service, ReactExpansion, CTA, and Project case-study fields
- Update portfolio data with adapted services, stackGroups, and project details
- Add OG/Twitter meta tags for SEO
- Update i18n with new keys for services, hero, buttons, and project labels
- Fix langDir path, update engine constraints, add volta config
- Change default color scheme to lime
- Rename Rider to JetBrains Rider in AI stack
This commit is contained in:
2026-07-06 18:28:26 +03:30
parent ef7ef8ecc3
commit 2fc75eeab1
18 changed files with 1049 additions and 296 deletions
+61 -75
View File
@@ -4,80 +4,37 @@
<div
class="backdrop-blur-md bg-white/80 dark:bg-slate-900/70 shadow-md rounded-2xl border border-white/30 dark:border-slate-700/50 pointer-events-auto transition-all duration-300">
<div class="flex items-center justify-between px-2 py-2">
<div class="flex items-center gap-3">
<!-- Home -->
<div class="flex items-center gap-1.5">
<UTooltip :text="t('nav.home')">
<UButton class="cursor-pointer transition-all duration-200"
:class="[isActive('hero') ? activeClass : inactiveClass]" variant="soft" square icon="i-twemoji-house"
:aria-label="t('nav.home')" @click="goTo('hero')" />
</UTooltip>
<button type="button"
class="hidden lg:inline-flex cursor-pointer text-sm font-medium transition-colors duration-200"
:class="[isActive('hero') ? labelActiveClass : labelInactiveClass]" @click="goTo('hero')">
{{ t('nav.home') }}
</button>
</div>
<div class="flex items-center gap-2">
<UTooltip v-for="item in navItems" :key="item.value" :text="item.label">
<UButton
type="button"
variant="soft"
class="nav-link-button cursor-pointer gap-1.5 px-2 transition-all duration-200 lg:px-2.5"
:class="[item.active ? activeClass : inactiveClass]"
:aria-label="item.label"
@click="goTo(item.target)"
>
<template #leading>
<UIcon :name="item.icon" class="text-xl" />
</template>
<span class="hidden lg:inline text-sm font-medium">{{ item.label }}</span>
</UButton>
</UTooltip>
<!-- Skills -->
<div class="flex items-center gap-1.5">
<UTooltip :text="t('sections.skills')">
<UButton class="cursor-pointer transition-all duration-200"
:class="[isActive('skills') ? activeClass : inactiveClass]" variant="soft" square
icon="i-twemoji-hammer-and-wrench" :aria-label="t('sections.skills')" @click="goTo('skills')" />
</UTooltip>
<button type="button"
class="hidden lg:inline-flex cursor-pointer text-sm font-medium transition-colors duration-200"
:class="[isActive('skills') ? labelActiveClass : labelInactiveClass]" @click="goTo('skills')">
{{ t('sections.skills') }}
</button>
</div>
<!-- Work -->
<div class="flex items-center gap-1.5">
<UTooltip :text="t('sections.work')">
<UButton class="cursor-pointer transition-all duration-200"
:class="[isActive('work') ? activeClass : inactiveClass]" variant="soft" square
icon="i-twemoji-briefcase" :aria-label="t('sections.work')" @click="goTo('work')" />
</UTooltip>
<button type="button"
class="hidden lg:inline-flex cursor-pointer text-sm font-medium transition-colors duration-200"
:class="[isActive('work') ? labelActiveClass : labelInactiveClass]" @click="goTo('work')">
{{ t('sections.work') }}
</button>
</div>
<!-- Projects -->
<div class="flex items-center gap-1.5">
<UTooltip :text="t('sections.projects')">
<UButton class="cursor-pointer transition-all duration-200"
:class="[isActive('projects') ? activeClass : inactiveClass]" variant="soft" square
icon="i-twemoji-rocket" :aria-label="t('sections.projects')" @click="goTo('projects')" />
</UTooltip>
<button type="button"
class="hidden lg:inline-flex cursor-pointer text-sm font-medium transition-colors duration-200"
:class="[isActive('projects') ? labelActiveClass : labelInactiveClass]" @click="goTo('projects')">
{{ t('sections.projects') }}
</button>
</div>
<!-- Blog -->
<div class="flex items-center gap-1.5">
<UTooltip :text="t('sections.blog')">
<NuxtLink :to="localePath('/blog')" class="cursor-pointer">
<UButton class="cursor-pointer transition-all duration-200"
:class="[isBlogActive ? activeClass : inactiveClass]" variant="soft" square icon="i-twemoji-memo"
:aria-label="t('sections.blog')" />
</NuxtLink>
</UTooltip>
<NuxtLink :to="localePath('/blog')" class="cursor-pointer">
<button type="button"
class="hidden lg:inline-flex cursor-pointer text-sm font-medium transition-colors duration-200"
:class="[isBlogActive ? labelActiveClass : labelInactiveClass]">
{{ t('sections.blog') }}
</button>
</NuxtLink>
</div>
<UTooltip :text="t('sections.blog')">
<UButton
:to="localePath('/blog')"
variant="soft"
class="nav-link-button cursor-pointer gap-1.5 px-2 transition-all duration-200 lg:px-2.5"
:class="[isBlogActive ? activeClass : inactiveClass]"
:aria-label="t('sections.blog')"
>
<template #leading>
<UIcon name="i-twemoji-memo" class="text-xl" />
</template>
<span class="hidden lg:inline text-sm font-medium">{{ t('sections.blog') }}</span>
</UButton>
</UTooltip>
</div>
<div class="flex items-center gap-2">
@@ -101,8 +58,6 @@ const localePath = useLocalePath()
const activeClass = 'ring-1 ring-primary-400/40 bg-primary-500/15 text-primary-600 dark:text-primary-400 transform scale-105'
const inactiveClass = 'text-gray-500 dark:text-gray-300 hover:text-primary-400'
const labelActiveClass = 'text-primary-700 dark:text-primary-400'
const labelInactiveClass = 'text-gray-600 dark:text-gray-300 hover:text-primary-400'
const sectionIds = ['hero', 'skills', 'work', 'projects'] as const
type Target = typeof sectionIds[number]
@@ -157,6 +112,37 @@ const isActive = (id: Target) => {
return activeSection.value === id
}
const navItems = computed(() => [
{
value: 'hero',
target: 'hero' as Target,
label: t('nav.home'),
icon: 'i-twemoji-house',
active: isActive('hero')
},
{
value: 'skills',
target: 'skills' as Target,
label: t('sections.skills'),
icon: 'i-twemoji-hammer-and-wrench',
active: isActive('skills')
},
{
value: 'work',
target: 'work' as Target,
label: t('sections.work'),
icon: 'i-twemoji-briefcase',
active: isActive('work')
},
{
value: 'projects',
target: 'projects' as Target,
label: t('sections.projects'),
icon: 'i-twemoji-rocket',
active: isActive('projects')
}
])
function scrollToSection(id: Target) {
if (typeof window === 'undefined') return