Compare commits
2 Commits
bba507a7e8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bec8b5120f | ||
|
|
6adb3a6979 |
@@ -1,11 +1,16 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { Breadcrumbs } from '@/components/layout/Breadcrumbs'
|
import { Breadcrumbs } from '@/components/layout/Breadcrumbs'
|
||||||
|
import { useTranslations } from 'next-intl'
|
||||||
|
|
||||||
export default function AboutBreadcrumb() {
|
export default function AboutBreadcrumb() {
|
||||||
|
const t = useTranslations('Breadcrumbs')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
label: 'Despre',
|
label: t('about'),
|
||||||
href: '/about',
|
href: '/about',
|
||||||
current: true,
|
current: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Breadcrumbs } from '@/components/layout/Breadcrumbs'
|
import { Breadcrumbs } from '@/components/layout/Breadcrumbs'
|
||||||
import { getPostBySlug } from '@/lib/markdown'
|
import { getPostBySlug } from '@/lib/markdown'
|
||||||
|
import { getTranslations } from 'next-intl/server'
|
||||||
|
|
||||||
interface BreadcrumbItem {
|
interface BreadcrumbItem {
|
||||||
label: string
|
label: string
|
||||||
@@ -7,28 +8,19 @@ interface BreadcrumbItem {
|
|||||||
current?: boolean
|
current?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDirectoryName(name: string): string {
|
|
||||||
const directoryNames: { [key: string]: string } = {
|
|
||||||
tech: 'Tehnologie',
|
|
||||||
design: 'Design',
|
|
||||||
tutorial: 'Tutoriale',
|
|
||||||
}
|
|
||||||
|
|
||||||
return directoryNames[name] || name.charAt(0).toUpperCase() + name.slice(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function BlogPostBreadcrumb({
|
export default async function BlogPostBreadcrumb({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: Promise<{ slug: string[] }>
|
params: Promise<{ slug: string[] }>
|
||||||
}) {
|
}) {
|
||||||
|
const t = await getTranslations('Breadcrumbs')
|
||||||
const { slug } = await params
|
const { slug } = await params
|
||||||
const slugPath = slug.join('/')
|
const slugPath = slug.join('/')
|
||||||
const post = await getPostBySlug(slugPath)
|
const post = await getPostBySlug(slugPath)
|
||||||
|
|
||||||
const items: BreadcrumbItem[] = [
|
const items: BreadcrumbItem[] = [
|
||||||
{
|
{
|
||||||
label: 'Blog',
|
label: t('blog'),
|
||||||
href: '/blog',
|
href: '/blog',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -36,8 +28,9 @@ export default async function BlogPostBreadcrumb({
|
|||||||
if (slug.length > 1) {
|
if (slug.length > 1) {
|
||||||
for (let i = 0; i < slug.length - 1; i++) {
|
for (let i = 0; i < slug.length - 1; i++) {
|
||||||
const segmentPath = slug.slice(0, i + 1).join('/')
|
const segmentPath = slug.slice(0, i + 1).join('/')
|
||||||
|
const dirName = slug[i]
|
||||||
items.push({
|
items.push({
|
||||||
label: formatDirectoryName(slug[i]),
|
label: t(dirName) || dirName.charAt(0).toUpperCase() + dirName.slice(1),
|
||||||
href: `/blog/${segmentPath}`,
|
href: `/blog/${segmentPath}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { Breadcrumbs } from '@/components/layout/Breadcrumbs'
|
import { Breadcrumbs } from '@/components/layout/Breadcrumbs'
|
||||||
|
import { useTranslations } from 'next-intl'
|
||||||
|
|
||||||
export default function BlogBreadcrumb() {
|
export default function BlogBreadcrumb() {
|
||||||
|
const t = useTranslations('Breadcrumbs')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
label: 'Blog',
|
label: t('blog'),
|
||||||
href: '/blog',
|
href: '/blog',
|
||||||
current: true,
|
current: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Breadcrumbs } from '@/components/layout/Breadcrumbs'
|
import { Breadcrumbs } from '@/components/layout/Breadcrumbs'
|
||||||
|
import { getTranslations } from 'next-intl/server'
|
||||||
|
|
||||||
export default async function TagBreadcrumb({ params }: { params: Promise<{ tag: string }> }) {
|
export default async function TagBreadcrumb({ params }: { params: Promise<{ tag: string }> }) {
|
||||||
|
const t = await getTranslations('Breadcrumbs')
|
||||||
const { tag } = await params
|
const { tag } = await params
|
||||||
const tagName = tag
|
const tagName = tag
|
||||||
.split('-')
|
.split('-')
|
||||||
@@ -11,7 +13,7 @@ export default async function TagBreadcrumb({ params }: { params: Promise<{ tag:
|
|||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
label: 'Tag-uri',
|
label: t('tags'),
|
||||||
href: '/tags',
|
href: '/tags',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { Breadcrumbs } from '@/components/layout/Breadcrumbs'
|
import { Breadcrumbs } from '@/components/layout/Breadcrumbs'
|
||||||
|
import { useTranslations } from 'next-intl'
|
||||||
|
|
||||||
export default function TagsBreadcrumb() {
|
export default function TagsBreadcrumb() {
|
||||||
|
const t = useTranslations('Breadcrumbs')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
label: 'Tag-uri',
|
label: t('tags'),
|
||||||
href: '/tags',
|
href: '/tags',
|
||||||
current: true,
|
current: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Link } from '@/src/i18n/navigation'
|
|||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { getAllPosts } from '@/lib/markdown'
|
import { getAllPosts } from '@/lib/markdown'
|
||||||
import { formatDate } from '@/lib/utils'
|
import { formatDate } from '@/lib/utils'
|
||||||
import { ThemeToggle } from '@/components/theme-toggle'
|
import { HeroHeader } from '@/components/layout/hero-header'
|
||||||
import { setRequestLocale, getTranslations } from 'next-intl/server'
|
import { setRequestLocale, getTranslations } from 'next-intl/server'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -13,7 +13,7 @@ export default async function HomePage({ params }: Props) {
|
|||||||
const { locale } = await params
|
const { locale } = await params
|
||||||
setRequestLocale(locale)
|
setRequestLocale(locale)
|
||||||
const t = await getTranslations('Home')
|
const t = await getTranslations('Home')
|
||||||
const tNav = await getTranslations('Navigation')
|
// const tNav = await getTranslations('Navigation')
|
||||||
|
|
||||||
const allPosts = await getAllPosts()
|
const allPosts = await getAllPosts()
|
||||||
const featuredPosts = allPosts.slice(0, 6)
|
const featuredPosts = allPosts.slice(0, 6)
|
||||||
@@ -29,29 +29,7 @@ export default async function HomePage({ params }: Props) {
|
|||||||
<div className="relative z-10 max-w-5xl mx-auto px-6 w-full">
|
<div className="relative z-10 max-w-5xl mx-auto px-6 w-full">
|
||||||
<div className="border-4 border-slate-300 dark:border-slate-700 bg-white/80 dark:bg-slate-900/80 p-8 md:p-12 transition-colors duration-300">
|
<div className="border-4 border-slate-300 dark:border-slate-700 bg-white/80 dark:bg-slate-900/80 p-8 md:p-12 transition-colors duration-300">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<div className="mb-8 flex items-center justify-between border-b-2 border-slate-300 dark:border-slate-800 pb-4">
|
<HeroHeader />
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Image src="/logo.png" alt="Logo" width={32} height={32} className="opacity-80" />
|
|
||||||
<span className="font-mono text-xs text-slate-500 uppercase tracking-widest">
|
|
||||||
{t('terminalVersion')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-4 items-center">
|
|
||||||
<Link
|
|
||||||
href="/blog"
|
|
||||||
className="font-mono text-xs text-slate-600 dark:text-slate-400 uppercase tracking-wider hover:text-cyan-600 dark:hover:text-cyan-400"
|
|
||||||
>
|
|
||||||
[{tNav('blog')}]
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/about"
|
|
||||||
className="font-mono text-xs text-slate-600 dark:text-slate-400 uppercase tracking-wider hover:text-cyan-600 dark:hover:text-cyan-400"
|
|
||||||
>
|
|
||||||
[{tNav('about')}]
|
|
||||||
</Link>
|
|
||||||
<ThemeToggle />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border-l-4 border-cyan-700 dark:border-cyan-900 pl-6 mb-8">
|
<div className="border-l-4 border-cyan-700 dark:border-cyan-900 pl-6 mb-8">
|
||||||
<p className="font-mono text-xs text-slate-500 dark:text-slate-500 uppercase tracking-widest mb-2">
|
<p className="font-mono text-xs text-slate-500 dark:text-slate-500 uppercase tracking-widest mb-2">
|
||||||
|
|||||||
181
app/globals.css
181
app/globals.css
@@ -449,3 +449,184 @@
|
|||||||
margin-bottom: 3rem;
|
margin-bottom: 3rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === MOBILE RESPONSIVE UTILITIES === */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.show-mobile-only {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.hide-mobile {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.show-mobile-only {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.hide-mobile {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === BUTTON GLITCH EFFECT === */
|
||||||
|
@layer utilities {
|
||||||
|
.glitch-btn-cyber {
|
||||||
|
--glitch-shimmy: 5;
|
||||||
|
--glitch-clip-1: polygon(
|
||||||
|
0 2%,
|
||||||
|
100% 2%,
|
||||||
|
100% 95%,
|
||||||
|
95% 95%,
|
||||||
|
95% 90%,
|
||||||
|
85% 90%,
|
||||||
|
85% 95%,
|
||||||
|
8% 95%,
|
||||||
|
0 70%
|
||||||
|
);
|
||||||
|
--glitch-clip-2: polygon(
|
||||||
|
0 78%,
|
||||||
|
100% 78%,
|
||||||
|
100% 100%,
|
||||||
|
95% 100%,
|
||||||
|
95% 90%,
|
||||||
|
85% 90%,
|
||||||
|
85% 100%,
|
||||||
|
8% 100%,
|
||||||
|
0 78%
|
||||||
|
);
|
||||||
|
--glitch-clip-3: polygon(
|
||||||
|
0 44%,
|
||||||
|
100% 44%,
|
||||||
|
100% 54%,
|
||||||
|
95% 54%,
|
||||||
|
95% 54%,
|
||||||
|
85% 54%,
|
||||||
|
85% 54%,
|
||||||
|
8% 54%,
|
||||||
|
0 54%
|
||||||
|
);
|
||||||
|
--glitch-clip-4: polygon(0 0, 100% 0, 100% 0, 95% 0, 95% 0, 85% 0, 85% 0, 8% 0, 0 0);
|
||||||
|
--glitch-clip-5: polygon(
|
||||||
|
0 40%,
|
||||||
|
100% 40%,
|
||||||
|
100% 85%,
|
||||||
|
95% 85%,
|
||||||
|
95% 85%,
|
||||||
|
85% 85%,
|
||||||
|
85% 85%,
|
||||||
|
8% 85%,
|
||||||
|
0 70%
|
||||||
|
);
|
||||||
|
--glitch-clip-6: polygon(
|
||||||
|
0 63%,
|
||||||
|
100% 63%,
|
||||||
|
100% 80%,
|
||||||
|
95% 80%,
|
||||||
|
95% 80%,
|
||||||
|
85% 80%,
|
||||||
|
85% 80%,
|
||||||
|
8% 80%,
|
||||||
|
0 70%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glitch-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
color: var(--neon-cyan);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glitch-btn-cyber:is(:hover, :focus-visible) .glitch-overlay {
|
||||||
|
display: flex;
|
||||||
|
animation: glitch-btn-animate 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glitch-btn-animate {
|
||||||
|
0% {
|
||||||
|
clip-path: var(--glitch-clip-1);
|
||||||
|
}
|
||||||
|
2%,
|
||||||
|
8% {
|
||||||
|
clip-path: var(--glitch-clip-2);
|
||||||
|
transform: translate(calc(var(--glitch-shimmy) * -1%), 0);
|
||||||
|
}
|
||||||
|
6% {
|
||||||
|
clip-path: var(--glitch-clip-2);
|
||||||
|
transform: translate(calc(var(--glitch-shimmy) * 1%), 0);
|
||||||
|
}
|
||||||
|
9% {
|
||||||
|
clip-path: var(--glitch-clip-2);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
clip-path: var(--glitch-clip-3);
|
||||||
|
transform: translate(calc(var(--glitch-shimmy) * 1%), 0);
|
||||||
|
}
|
||||||
|
13% {
|
||||||
|
clip-path: var(--glitch-clip-3);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
14%,
|
||||||
|
21% {
|
||||||
|
clip-path: var(--glitch-clip-4);
|
||||||
|
transform: translate(calc(var(--glitch-shimmy) * 1%), 0);
|
||||||
|
}
|
||||||
|
25%,
|
||||||
|
30% {
|
||||||
|
clip-path: var(--glitch-clip-5);
|
||||||
|
transform: translate(calc(var(--glitch-shimmy) * -1%), 0);
|
||||||
|
}
|
||||||
|
35%,
|
||||||
|
45% {
|
||||||
|
clip-path: var(--glitch-clip-6);
|
||||||
|
transform: translate(calc(var(--glitch-shimmy) * -1%), 0);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
clip-path: var(--glitch-clip-6);
|
||||||
|
transform: translate(calc(var(--glitch-shimmy) * 1%), 0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
clip-path: var(--glitch-clip-6);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
55% {
|
||||||
|
clip-path: var(--glitch-clip-3);
|
||||||
|
transform: translate(calc(var(--glitch-shimmy) * 1%), 0);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
clip-path: var(--glitch-clip-3);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
61%,
|
||||||
|
100% {
|
||||||
|
clip-path: var(--glitch-clip-4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.glitch-btn-subtle {
|
||||||
|
--glitch-shimmy: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glitch-overlay-pink {
|
||||||
|
color: var(--neon-pink);
|
||||||
|
}
|
||||||
|
.glitch-overlay-purple {
|
||||||
|
color: var(--neon-purple);
|
||||||
|
}
|
||||||
|
.glitch-overlay-magenta {
|
||||||
|
color: var(--neon-magenta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.glitch-btn-cyber:is(:hover, :focus-visible) .glitch-overlay {
|
||||||
|
animation: none;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import { useTranslations } from 'next-intl'
|
|||||||
import { Link } from '@/i18n/navigation'
|
import { Link } from '@/i18n/navigation'
|
||||||
import { ThemeToggle } from '@/components/theme-toggle'
|
import { ThemeToggle } from '@/components/theme-toggle'
|
||||||
import LanguageSwitcher from '@/components/layout/LanguageSwitcher'
|
import LanguageSwitcher from '@/components/layout/LanguageSwitcher'
|
||||||
|
import { GlitchButton } from '@/components/effects/glitch-button'
|
||||||
|
|
||||||
export function Navbar() {
|
export function Navbar() {
|
||||||
const t = useTranslations('Navigation')
|
const t = useTranslations('Navigation')
|
||||||
const [isVisible, setIsVisible] = useState(true)
|
const [isVisible, setIsVisible] = useState(true)
|
||||||
const [lastScrollY, setLastScrollY] = useState(0)
|
const [lastScrollY, setLastScrollY] = useState(0)
|
||||||
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
@@ -19,6 +21,7 @@ export function Navbar() {
|
|||||||
setIsVisible(true)
|
setIsVisible(true)
|
||||||
} else if (currentScrollY > lastScrollY) {
|
} else if (currentScrollY > lastScrollY) {
|
||||||
setIsVisible(false)
|
setIsVisible(false)
|
||||||
|
setIsMobileMenuOpen(false)
|
||||||
} else {
|
} else {
|
||||||
setIsVisible(true)
|
setIsVisible(true)
|
||||||
}
|
}
|
||||||
@@ -32,7 +35,7 @@ export function Navbar() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
className={`border-b-4 border-slate-700 bg-slate-900 dark:bg-zinc-950 sticky top-0 z-50 ${isVisible ? 'navbar-visible' : 'navbar-hidden'}`}
|
className={`border-b-4 border-slate-700 bg-slate-900 dark:bg-zinc-950 top-0 z-50 ${isVisible ? 'navbar-visible' : 'navbar-hidden'}`}
|
||||||
>
|
>
|
||||||
<div className="max-w-7xl mx-auto px-6 py-4">
|
<div className="max-w-7xl mx-auto px-6 py-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@@ -44,11 +47,12 @@ export function Navbar() {
|
|||||||
>
|
>
|
||||||
< {t('home')}
|
< {t('home')}
|
||||||
</Link>
|
</Link>
|
||||||
<span className="font-mono text-sm text-zinc-100 dark:text-zinc-300 uppercase tracking-wider">
|
<span className="font-mono text-sm text-zinc-100 dark:text-zinc-300 uppercase tracking-wider hidden md:block">
|
||||||
// <span style={{ color: 'var(--neon-pink)' }}>{t('blog')}</span> ARCHIVE
|
// <span style={{ color: 'var(--neon-pink)' }}>{t('blog')}</span> ARCHIVE
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-6">
|
|
||||||
|
<div className="hidden md:flex items-center gap-6">
|
||||||
<Link
|
<Link
|
||||||
href="/about"
|
href="/about"
|
||||||
className="font-mono text-sm text-zinc-400 dark:text-zinc-500 uppercase tracking-wider hover:text-cyan-400 dark:hover:text-cyan-300 transition-colors cursor-pointer"
|
className="font-mono text-sm text-zinc-400 dark:text-zinc-500 uppercase tracking-wider hover:text-cyan-400 dark:hover:text-cyan-300 transition-colors cursor-pointer"
|
||||||
@@ -64,8 +68,46 @@ export function Navbar() {
|
|||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="md:hidden flex items-center gap-4">
|
||||||
|
<GlitchButton
|
||||||
|
variant="subtle"
|
||||||
|
glitchColor="cyan"
|
||||||
|
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||||
|
className="font-mono text-sm uppercase tracking-wider px-4 py-2 border-4 border-slate-700 bg-slate-800 dark:bg-zinc-900 text-zinc-100 dark:text-zinc-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-500"
|
||||||
|
aria-label="Toggle mobile menu"
|
||||||
|
aria-expanded={isMobileMenuOpen}
|
||||||
|
>
|
||||||
|
// {isMobileMenuOpen ? 'CLOSE' : 'MENU'}
|
||||||
|
</GlitchButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isMobileMenuOpen && (
|
||||||
|
<div className="md:hidden mt-4 pt-4 border-t-4 border-slate-700">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Link
|
||||||
|
href="/about"
|
||||||
|
className="font-mono text-sm text-zinc-400 dark:text-zinc-500 uppercase tracking-wider hover:text-cyan-400 dark:hover:text-cyan-300 transition-colors cursor-pointer px-4 py-2 border-2 border-slate-700"
|
||||||
|
onClick={() => setIsMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
[{t('about')}]
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/blog"
|
||||||
|
className="font-mono text-sm text-zinc-400 dark:text-zinc-500 uppercase tracking-wider hover:text-cyan-400 dark:hover:text-cyan-300 transition-colors cursor-pointer px-4 py-2 border-2 border-slate-700"
|
||||||
|
onClick={() => setIsMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
[{t('blog')}]
|
||||||
|
</Link>
|
||||||
|
<div className="flex items-center gap-4 px-4 py-2">
|
||||||
|
<ThemeToggle />
|
||||||
|
<LanguageSwitcher />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
43
components/effects/glitch-button.tsx
Normal file
43
components/effects/glitch-button.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
interface GlitchButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
children: React.ReactNode
|
||||||
|
variant?: 'default' | 'subtle'
|
||||||
|
glitchColor?: 'cyan' | 'pink' | 'purple' | 'magenta'
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GlitchButton({
|
||||||
|
children,
|
||||||
|
variant = 'default',
|
||||||
|
glitchColor = 'cyan',
|
||||||
|
disabled = false,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: GlitchButtonProps) {
|
||||||
|
const glitchClasses = !disabled
|
||||||
|
? cn('glitch-btn-cyber', variant === 'subtle' && 'glitch-btn-subtle', 'relative')
|
||||||
|
: ''
|
||||||
|
|
||||||
|
const overlayColorClass = {
|
||||||
|
cyan: '',
|
||||||
|
pink: 'glitch-overlay-pink',
|
||||||
|
purple: 'glitch-overlay-purple',
|
||||||
|
magenta: 'glitch-overlay-magenta',
|
||||||
|
}[glitchColor]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className={cn(glitchClasses, className)} disabled={disabled} {...props}>
|
||||||
|
{children}
|
||||||
|
|
||||||
|
{!disabled && (
|
||||||
|
<div className={cn('glitch-overlay', overlayColorClass)} aria-hidden="true">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
99
components/layout/hero-header.tsx
Normal file
99
components/layout/hero-header.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { Link } from '@/i18n/navigation'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { ThemeToggle } from '@/components/theme-toggle'
|
||||||
|
import { GlitchButton } from '@/components/effects/glitch-button'
|
||||||
|
import LanguageSwitcher from './LanguageSwitcher'
|
||||||
|
import { useLocale, useTranslations } from 'next-intl'
|
||||||
|
|
||||||
|
export function HeroHeader() {
|
||||||
|
const locale = useLocale()
|
||||||
|
const t = useTranslations('Home')
|
||||||
|
const tNav = useTranslations('Navigation')
|
||||||
|
|
||||||
|
const terminalVersion = t('terminalVersion')
|
||||||
|
const blogLabel = tNav('blog')
|
||||||
|
const aboutLabel = tNav('about')
|
||||||
|
|
||||||
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
||||||
|
const [isMobile, setIsMobile] = useState(false)
|
||||||
|
useEffect(() => {
|
||||||
|
const checkMobile = () => setIsMobile(window.innerWidth < 768)
|
||||||
|
checkMobile()
|
||||||
|
window.addEventListener('resize', checkMobile)
|
||||||
|
return () => window.removeEventListener('resize', checkMobile)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-8 border-b-2 border-slate-300 dark:border-slate-800 pb-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Image src="/logo.png" alt="Logo" width={32} height={32} className="opacity-80" />
|
||||||
|
<span className="font-mono text-xs text-slate-500 uppercase tracking-widest">
|
||||||
|
{terminalVersion}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isMobile && (
|
||||||
|
<div className="flex gap-4 items-center">
|
||||||
|
<Link
|
||||||
|
href="/blog"
|
||||||
|
className="font-mono text-xs text-slate-600 dark:text-slate-400 uppercase tracking-wider hover:text-cyan-600 dark:hover:text-cyan-400"
|
||||||
|
>
|
||||||
|
[{blogLabel}]
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/about"
|
||||||
|
className="font-mono text-xs text-slate-600 dark:text-slate-400 uppercase tracking-wider hover:text-cyan-600 dark:hover:text-cyan-400"
|
||||||
|
>
|
||||||
|
[{aboutLabel}]
|
||||||
|
</Link>
|
||||||
|
<ThemeToggle />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isMobile && (
|
||||||
|
<div>
|
||||||
|
<GlitchButton
|
||||||
|
variant="subtle"
|
||||||
|
glitchColor="cyan"
|
||||||
|
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||||
|
className="font-mono text-xs uppercase tracking-wider px-3 py-2 border-2 border-slate-400 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300"
|
||||||
|
aria-label="Toggle menu"
|
||||||
|
aria-expanded={isMobileMenuOpen}
|
||||||
|
>
|
||||||
|
// {isMobileMenuOpen ? 'X' : 'MENU'}
|
||||||
|
</GlitchButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isMobileMenuOpen && isMobile && (
|
||||||
|
<div className="mt-4 pt-4 border-t-2 border-slate-300 dark:border-slate-800">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Link
|
||||||
|
href="/blog"
|
||||||
|
className="font-mono text-xs text-slate-600 dark:text-slate-400 uppercase tracking-wider hover:text-cyan-600 dark:hover:text-cyan-400 px-3 py-2 border-2 border-slate-300 dark:border-slate-700"
|
||||||
|
onClick={() => setIsMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
[{blogLabel}]
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/about"
|
||||||
|
className="font-mono text-xs text-slate-600 dark:text-slate-400 uppercase tracking-wider hover:text-cyan-600 dark:hover:text-cyan-400 px-3 py-2 border-2 border-slate-300 dark:border-slate-700"
|
||||||
|
onClick={() => setIsMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
[{aboutLabel}]
|
||||||
|
</Link>
|
||||||
|
<div className="flex items-center gap-4 px-4 py-2">
|
||||||
|
<ThemeToggle />
|
||||||
|
<LanguageSwitcher />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -79,3 +79,7 @@ export function generateSlug(title: string): string {
|
|||||||
.replace(/[^a-z0-9]+/g, '-')
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
.replace(/^-+|-+$/g, '')
|
.replace(/^-+|-+$/g, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function cn(...inputs: (string | undefined | null | false)[]): string {
|
||||||
|
return inputs.filter(Boolean).join(' ')
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,21 +3,21 @@
|
|||||||
"siteTitle": "Personal Blog",
|
"siteTitle": "Personal Blog",
|
||||||
"siteDescription": "Thoughts on technology and development"
|
"siteDescription": "Thoughts on technology and development"
|
||||||
},
|
},
|
||||||
|
|
||||||
"Navigation": {
|
"Navigation": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"blog": "Blog",
|
"blog": "Blog",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
"about": "About"
|
"about": "About"
|
||||||
},
|
},
|
||||||
|
|
||||||
"Breadcrumbs": {
|
"Breadcrumbs": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"blog": "Blog",
|
"blog": "Blog",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
"about": "About"
|
"about": "About",
|
||||||
|
"tech": "Technology",
|
||||||
|
"design": "Design",
|
||||||
|
"tutorial": "Tutorials"
|
||||||
},
|
},
|
||||||
|
|
||||||
"Home": {
|
"Home": {
|
||||||
"terminalVersion": "TERMINAL:// V2.0",
|
"terminalVersion": "TERMINAL:// V2.0",
|
||||||
"documentLevel": "DOCUMENT LEVEL-1 //",
|
"documentLevel": "DOCUMENT LEVEL-1 //",
|
||||||
@@ -32,7 +32,6 @@
|
|||||||
"seePostsButton": "[SEE POSTS] >>",
|
"seePostsButton": "[SEE POSTS] >>",
|
||||||
"seeAllTagsButton": "[SEE ALL TAGS] >>"
|
"seeAllTagsButton": "[SEE ALL TAGS] >>"
|
||||||
},
|
},
|
||||||
|
|
||||||
"BlogListing": {
|
"BlogListing": {
|
||||||
"title": "Blog",
|
"title": "Blog",
|
||||||
"subtitle": "Latest articles and thoughts",
|
"subtitle": "Latest articles and thoughts",
|
||||||
@@ -48,7 +47,6 @@
|
|||||||
"prev": "< PREV",
|
"prev": "< PREV",
|
||||||
"next": "NEXT >"
|
"next": "NEXT >"
|
||||||
},
|
},
|
||||||
|
|
||||||
"BlogPost": {
|
"BlogPost": {
|
||||||
"readMore": "Read more",
|
"readMore": "Read more",
|
||||||
"readingTime": "{minutes} min read",
|
"readingTime": "{minutes} min read",
|
||||||
@@ -58,7 +56,6 @@
|
|||||||
"relatedPosts": "Related Posts",
|
"relatedPosts": "Related Posts",
|
||||||
"sharePost": "Share this post"
|
"sharePost": "Share this post"
|
||||||
},
|
},
|
||||||
|
|
||||||
"Tags": {
|
"Tags": {
|
||||||
"title": "Tags",
|
"title": "Tags",
|
||||||
"subtitle": "Browse by topic",
|
"subtitle": "Browse by topic",
|
||||||
@@ -67,7 +64,6 @@
|
|||||||
"relatedTags": "Related tags",
|
"relatedTags": "Related tags",
|
||||||
"quickNav": "Quick navigation"
|
"quickNav": "Quick navigation"
|
||||||
},
|
},
|
||||||
|
|
||||||
"About": {
|
"About": {
|
||||||
"title": "About",
|
"title": "About",
|
||||||
"subtitle": "Learn more about me",
|
"subtitle": "Learn more about me",
|
||||||
@@ -119,13 +115,11 @@
|
|||||||
"techStackSelfHostingText": "Home lab, privacy-focused services, full control, Git server",
|
"techStackSelfHostingText": "Home lab, privacy-focused services, full control, Git server",
|
||||||
"contactTitle": "> CONTACT"
|
"contactTitle": "> CONTACT"
|
||||||
},
|
},
|
||||||
|
|
||||||
"NotFound": {
|
"NotFound": {
|
||||||
"title": "Page Not Found",
|
"title": "Page Not Found",
|
||||||
"description": "The page you're looking for doesn't exist",
|
"description": "The page you're looking for doesn't exist",
|
||||||
"goHome": "Go to homepage"
|
"goHome": "Go to homepage"
|
||||||
},
|
},
|
||||||
|
|
||||||
"LanguageSwitcher": {
|
"LanguageSwitcher": {
|
||||||
"switchLanguage": "Switch language",
|
"switchLanguage": "Switch language",
|
||||||
"currentLanguage": "Current language"
|
"currentLanguage": "Current language"
|
||||||
|
|||||||
@@ -13,15 +13,18 @@
|
|||||||
"home": "Acasă",
|
"home": "Acasă",
|
||||||
"blog": "Blog",
|
"blog": "Blog",
|
||||||
"tags": "Etichete",
|
"tags": "Etichete",
|
||||||
"about": "Despre"
|
"about": "Despre",
|
||||||
|
"tech": "Tehnologie",
|
||||||
|
"design": "Design",
|
||||||
|
"tutorial": "Tutoriale"
|
||||||
},
|
},
|
||||||
"Home": {
|
"Home": {
|
||||||
"terminalVersion": "TERMINAL:// V2.0",
|
"terminalVersion": "TERMINAL:// V2.0",
|
||||||
"documentLevel": "DOCUMENT LEVEL-1 //",
|
"documentLevel": "DOCUMENT LEVEL-1 //",
|
||||||
"heroTitle": "BUILD. WRITE. SHARE.",
|
"heroTitle": "BUILD. WRITE. SHARE.",
|
||||||
"heroSubtitle": "> Explore ideas",
|
"heroSubtitle": "> Explore ideas",
|
||||||
"checkPostsButton": "[CHECK POSTS]",
|
"checkPostsButton": "[POSTĂRI]",
|
||||||
"aboutMeButton": "[ABOUT ME]",
|
"aboutMeButton": "[DESPRE]",
|
||||||
"recentEntriesLabel": "ARCHIVE ACCESS // RECENT ENTRIES",
|
"recentEntriesLabel": "ARCHIVE ACCESS // RECENT ENTRIES",
|
||||||
"recentEntriesTitle": "> RECENT ENTRIES",
|
"recentEntriesTitle": "> RECENT ENTRIES",
|
||||||
"fileLabel": "FILE#{number} // {category}",
|
"fileLabel": "FILE#{number} // {category}",
|
||||||
|
|||||||
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/types/routes.d.ts";
|
import "./.next/dev/types/routes.d.ts";
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
Reference in New Issue
Block a user