Files
portfolio/src/components/react/navbar.tsx
cojocaru-david 0c90442415 feat: add post feedback system with like/dislike functionality
feat: implement fingerprint-based voting to prevent duplicate votes
feat: add database setup documentation for likes/dislikes feature
feat: update social icons styling for better mobile responsiveness
feat: add node adapter for standalone server deployment
chore: update dependencies including astro and fingerprintjs
fix: move social icons to top of footer for better visibility
refactor: clean up meta tags in PostHead component
docs: add comprehensive database schema and API documentation

feat(components): add BuyMeCoffee component with animated SVG and hover effects

feat(components): implement BuyMeCoffee donation link with styling and animations

feat(components): create BuyMeCoffee component with responsive design and interactive elements

style: update SVG paths with fill-background class for consistent styling

style: update SVG paths and styling for better visual consistency and hover effects

style: update BuyMeCoffee component with new SVG animations and styling

feat: add hover animations and transitions to BuyMeCoffee component

refactor: reorganize SVG paths and groups in BuyMeCoffee component for better readability

The changes include:
- Adding new SVG animations and styling for the BuyMeCoffee component
- Implementing hover animations and transitions to enhance user interaction
- Refactoring the SVG structure for improved code organization and maintainability

These changes were made to improve the visual appeal and user experience of the BuyMeCoffee component while keeping the codebase clean and maintainable.

refactor(navbar): simplify class names and remove unused comments
feat(navbar): add dark mode text color support and improve mobile menu styling
feat(navbar): enhance footer with copyright, separator, and open-source link
refactor(navbar): streamline mobile menu button styling and transitions

refactor(consts): update social links and icon map
feat(consts): add Instagram and Phone social links
chore(consts): remove LinkedIn and update icon mappings

chore(blog): remove outdated blog posts
feat(blog): clean up content directory by deleting irrelevant posts

chore(content): remove outdated blog posts

The commit removes a large number of outdated blog posts that were no longer relevant or aligned with the current content strategy. This cleanup helps maintain a more focused and up-to-date blog section.

chore: remove outdated blog posts and clean up content directory

Delete multiple outdated blog post files to streamline the content directory and improve maintainability. The removed posts were no longer relevant and cluttered the repository. This cleanup helps focus on current and future content.

chore: remove outdated blog posts and related content

The commit removes a large number of outdated blog posts and related content from the repository. These files were no longer relevant or maintained, and their removal helps clean up the codebase and reduce clutter. The changes include deleting various markdown files under the `src/content/blog/` directory that covered topics like cybersecurity, data analytics, cloud computing, and cryptocurrency regulation. This cleanup aligns with the project's goal to maintain only current and relevant content.

chore(content): remove outdated blog posts

The commit removes a large number of outdated blog posts that were no longer relevant or aligned with the current content strategy. This cleanup helps maintain a focused and up-to-date content repository.

chore: remove outdated blog content

Deleted multiple outdated blog posts to clean up the repository and remove irrelevant content. The posts were no longer aligned with the current focus and direction of the project. This cleanup helps maintain a more organized and relevant codebase.

chore(content): remove outdated blog posts

Deleted multiple outdated blog posts covering various tech topics including development, startups, and certifications. The content was no longer relevant or aligned with current best practices. This cleanup helps maintain a focused and up-to-date content repository.

chore: remove outdated blog posts

The diff shows the deletion of multiple blog post files that appear to be outdated or no longer relevant. This cleanup will help maintain content quality and relevance on the site.

chore(content): remove outdated and irrelevant blog posts

This commit removes a large number of blog posts that were either outdated, irrelevant, or of low quality. The removed posts covered a wide range of topics including quantum computing, machine learning, cloud computing, and various technical tutorials. Many of these posts were auto-generated or contained generic content that didn't provide real value to readers.

The removal of these posts helps:
- Improve overall content quality
- Reduce maintenance burden
- Focus on more relevant and valuable content
- Clean up the repository structure

No existing links or references to these posts were being maintained, so their removal shouldn't impact users. This cleanup aligns with our goal of maintaining a focused, high-quality content repository.

chore(content): remove outdated blog posts

The commit removes a large number of outdated blog posts that were no longer relevant or maintained. This cleanup helps keep the content fresh and focused on current topics.

chore(content): remove outdated blog posts

The commit removes a large number of outdated blog post files that were no longer relevant or needed. This cleanup helps declutter the content directory and removes potentially stale or incorrect information. The files deleted covered a wide range of tech-related topics but were determined to be no longer useful for the current site.

chore(content): remove outdated blog posts

Deleted multiple outdated blog posts covering various tech topics including AI, edge computing, blockchain, and sustainability. These posts were no longer relevant or accurate given recent advancements in technology. The removal helps maintain content quality and ensures readers only access up-to-date information.

chore(content): remove all blog posts to clean up repository

This commit removes all existing blog post content files from the repository. The files were deleted to clean up the content directory and prepare for new content to be added in the future. The removal includes a wide range of blog posts covering various tech topics, indicating a complete content refresh is planned.

chore(content): remove outdated blog posts and articles

The commit removes a large number of outdated blog posts and articles from the content directory. These files were likely stale content that was no longer relevant or useful. The removal helps clean up the repository and maintain only current, valuable content.

 *::before,
   *::after {
     @apply border-border;
   }
+
   body {
     @apply bg-background text-foreground font-sans;
     font-feature-settings:
       'rlig' 1,
       'calt' 1;
   }
+
   h1,
   h2,
   h3,
   h4,
   h5,
   h6 {
-    @apply font-custom;
+    @apply font-custom scroll-mt-20;
   }
+
+  h1 {
+    @apply text-4xl font-bold;
+  }
+
+  h2 {
+    @apply text-3xl font-bold;
+  }
+
+  h3 {
+    @apply text-2xl font-bold;
+  }
+
+  h4 {
+    @apply text-xl font-bold;
+  }
+
+  h5 {
+    @apply text-lg font-bold;
+  }
+
+  h6 {
+    @apply text-base font-bold;
+  }
+
+  p {
+    @apply text-base;
+  }
+
+  a {
+    @apply text-primary hover:text-primary-foreground transition-colors;
+  }
+
+  code {
+    @apply font-mono text-sm bg-muted px-1 py-0.5 rounded;
+  }
+
+  pre {
+    @apply font-mono text-sm bg-muted p-4 rounded overflow-x-auto;
+  }
+
+  blockquote {
+    @apply border-l-4 border-primary pl-4 italic;
+  }
+
+  ul {
+    @apply list-disc pl-5;
+  }
+
+  ol {
+    @apply list-decimal pl-5;
+  }
+
+  li {
+    @apply mb-1;
+  }
+
+  table {
+    @apply w-full border-collapse;
+  }
+
+  th {
+    @apply bg-muted text-left p-2 border;
+  }
+
+  td {
+    @apply p-2 border;
+  }
+
+  img {
+    @apply max-w-full h-auto;
+  }
+
+  hr {
+    @apply border-t border-border my-4;
+  }
 }
2025-05-01 01:40:16 +03:00

254 lines
8.6 KiB
TypeScript

import { useState, useEffect } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import Link from './link'
import ThemeToggle from './theme-toggle'
import { NAV_LINKS, SITE } from '../../consts'
import { cn } from '@/lib/utils'
import debounce from 'lodash.debounce'
import Logo from '../ui/logo'
import { Button } from '@/components/ui/button'
import { Menu, X } from 'lucide-react'
import { Separator } from '../ui/separator'
const Navbar = () => {
const [scrollLevel, setScrollLevel] = useState(0)
const [isScrolled, setIsScrolled] = useState(false)
const [isMobile, setIsMobile] = useState(false)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const [activePath, setActivePath] = useState("/")
useEffect(() => {
setActivePath(window.location.pathname)
const handleRouteChange = () => {
setActivePath(window.location.pathname)
}
window.addEventListener('popstate', handleRouteChange)
return () => {
window.removeEventListener('popstate', handleRouteChange)
}
}, [])
useEffect(() => {
const handleResize = debounce(() => {
const isMobileView = window.matchMedia('(max-width: 768px)').matches
setIsMobile(isMobileView)
if (!isMobileView && mobileMenuOpen) {
setMobileMenuOpen(false)
}
}, 100)
handleResize()
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [mobileMenuOpen])
useEffect(() => {
const handleScroll = debounce(() => {
const scrollY = window.scrollY
setScrollLevel(
scrollY > 500 ? 4 : scrollY > 300 ? 3 : scrollY > 150 ? 2 : scrollY > 0 ? 1 : 0
)
setIsScrolled(scrollY > 0)
}, 50)
window.addEventListener('scroll', handleScroll)
return () => {
window.removeEventListener('scroll', handleScroll)
}
}, [])
useEffect(() => {
if (mobileMenuOpen) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = ''
}
return () => {
document.body.style.overflow = ''
}
}, [mobileMenuOpen])
const sizeVariants: Record<number, { width: string }> = {
0: { width: '100%' },
1: { width: '90%' },
2: { width: '80%' },
3: { width: '70%' },
4: { width: '50%' },
}
const menuVariants = {
closed: {
opacity: 0,
y: "-100%",
transition: {
duration: 0.5,
ease: [0.22, 1, 0.36, 1],
}
},
open: {
opacity: 1,
y: "0%",
transition: {
duration: 0.5,
ease: [0.22, 1, 0.36, 1],
}
}
}
return (
<>
<motion.header
aria-label="Navigation"
role="navigation"
layout={!isMobile}
initial={sizeVariants[0]}
animate={isMobile ? sizeVariants[0] : sizeVariants[scrollLevel]}
className={cn(
'fixed left-1/2 z-30 -translate-x-1/2 transform backdrop-blur-lg',
'bg-background/80 border-0',
'rounded-none shadow-none transition-all duration-300 ease-in-out',
'border border-transparent w-full',
isScrolled && !isMobile && 'rounded-full',
isScrolled && !isMobile && 'backdrop-blur-md',
isScrolled && !isMobile && 'border-foreground/10',
isScrolled && !isMobile && 'border',
isScrolled && !isMobile && 'bg-background/80',
isScrolled && !isMobile && 'max-w-[calc(100vw-5rem)]',
!isMobile && 'top-2 lg:top-4 xl:top-6',
isMobile && 'top-0',
isMobile && 'rounded-none',
isMobile && 'border-0',
isMobile && 'shadow-none',
isMobile && 'border-0'
)}
>
<div className="mx-auto flex max-w-7xl items-center justify-between gap-4 p-4">
<Link
href="/"
className="font-custom flex shrink-0 items-center gap-2 text-xl font-bold"
aria-label="Home"
title="Home"
navigation="true"
>
<Logo className="h-8 w-8" />
<span className={
'transition-opacity duration-200 ease-in-out text-foreground/90 dark:text-white'}>
{SITE.title}
</span>
</Link>
<div className="flex items-center gap-2 md:gap-4">
<nav className="hidden items-center gap-6 md:flex" aria-label="Main navigation" role="navigation">
{NAV_LINKS.map((item) => {
const isActive = activePath.startsWith(item.href) && item.href !== "/";
return (
<motion.div
key={item.href}
whileHover={{ scale: 1.05 }}
className="relative"
>
<Link
href={item.href}
className={cn(
"text-sm font-medium capitalize transition-colors duration-200",
"relative py-1 px-1",
"after:absolute after:bottom-0 after:left-0 after:h-[2px] after:w-0 after:bg-primary after:transition-all after:duration-300",
"hover:after:w-full hover:text-foreground",
isActive
? "text-foreground after:w-full after:bg-primary"
: "text-foreground/70"
)}
onClick={() => setActivePath(item.href)}
>
{item.label}
</Link>
</motion.div>
);
})}
</nav>
<ThemeToggle />
{isMobile && (
<Button
variant="ghost"
size="icon"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
aria-label={mobileMenuOpen ? "Close menu" : "Open menu"}
className={
"ml-1 h-9 w-9 rounded-full p-0 transition-colors duration-200 ease-in-out"
}
>
{mobileMenuOpen ? (
<X className="h-5 w-5" />
) : (
<Menu className="h-5 w-5" />
)}
</Button>
)}
</div>
</div>
</motion.header>
<AnimatePresence>
{mobileMenuOpen && (
<motion.div
key="mobile-menu"
initial="closed"
animate="open"
exit="closed"
variants={menuVariants}
className="fixed inset-0 z-20 flex flex-col items-center justify-start bg-background border-0 shadow-none"
>
<div className="flex flex-col items-center justify-start h-full pt-24 w-full p-6">
<nav className="flex flex-col items-center justify-start gap-1 w-full">
{NAV_LINKS.map((item, i) => (
<motion.div
key={item.href}
custom={i}
className="w-full text-start"
>
<Link
href={item.href}
onClick={() => setMobileMenuOpen(false)}
className="dark:text-white text-lg font-bold font-custom capitalize dark:hover:text-white/80 transition-colors inline-block py-2 relative group"
>
{item.label}
<span className="absolute left-0 bottom-0 w-0 h-0.5 bg-neutral-900 dark:bg-white group-hover:w-full transition-all duration-300 ease-in-out"></span>
</Link>
</motion.div>
))}
</nav>
<motion.div
custom={NAV_LINKS.length + 1}
className="mt-auto flex flex-col items-center gap-6"
>
<div className="flex flex-wrap items-center justify-center gap-x-2 text-center">
<span className="text-muted-foreground text-sm" aria-label="copyright">
2020 - {new Date().getFullYear()} &copy; All rights reserved.
</span>
<Separator orientation="vertical" className="hidden h-4! sm:block" />
<p className="text-muted-foreground text-sm" aria-label="open-source description">
<Link
href="https://github.com/cojocaru-david/portfolio"
class="text-foreground"
external
underline>Open-source</Link
> under MIT license
</p>
</div>
</motion.div>
</div>
</motion.div>
)}
</AnimatePresence>
</>
)
}
export default Navbar