Compare commits
8 Commits
73cbc8f731
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bec8b5120f | ||
|
|
6adb3a6979 | ||
|
|
bba507a7e8 | ||
|
|
101624c4d5 | ||
|
|
b68325123b | ||
|
|
087bccbb13 | ||
|
|
d349c1a957 | ||
|
|
0e0c21449b |
46
.vscode/launch.json
vendored
Normal file
46
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Next.js: Full Stack",
|
||||||
|
"type": "node-terminal",
|
||||||
|
"request": "launch",
|
||||||
|
"command": "npm run dev",
|
||||||
|
"serverReadyAction": {
|
||||||
|
"pattern": "started server on .+, url: (https?://.+)",
|
||||||
|
"uriFormat": "%s",
|
||||||
|
"action": "debugWithChrome"
|
||||||
|
},
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Next.js: Frontend",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "http://localhost:3030",
|
||||||
|
"webRoot": "${workspaceFolder}",
|
||||||
|
"sourceMapPathOverrides": {
|
||||||
|
"webpack://_N_E/*": "${webRoot}/*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Next.js: Backend",
|
||||||
|
"type": "node",
|
||||||
|
"request": "attach",
|
||||||
|
"port": 9229,
|
||||||
|
"restart": true,
|
||||||
|
"sourceMaps": true,
|
||||||
|
"sourceMapPathOverrides": {
|
||||||
|
"webpack:///*": "${workspaceFolder}/*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "Next.js: Debug Full Stack",
|
||||||
|
"configurations": ["Next.js: Backend", "Next.js: Frontend"],
|
||||||
|
"stopAll": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Metadata } from 'next'
|
import { Metadata } from 'next'
|
||||||
import { Navbar } from '@/components/blog/navbar'
|
import { Navbar } from '@/components/blog/navbar'
|
||||||
import {setRequestLocale} from 'next-intl/server'
|
import { setRequestLocale, getTranslations } from 'next-intl/server'
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'About',
|
title: 'About',
|
||||||
@@ -8,12 +8,13 @@ export const metadata: Metadata = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
params: Promise<{locale: string}>
|
params: Promise<{ locale: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function AboutPage({params}: Props) {
|
export default async function AboutPage({ params }: Props) {
|
||||||
const {locale} = await params
|
const { locale } = await params
|
||||||
setRequestLocale(locale)
|
setRequestLocale(locale)
|
||||||
|
const t = await getTranslations('About')
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
@@ -22,10 +23,10 @@ export default async function AboutPage({params}: Props) {
|
|||||||
{/* Classification Header */}
|
{/* Classification Header */}
|
||||||
<div className="border-2 border-[rgb(var(--border-primary))] p-8 mb-10">
|
<div className="border-2 border-[rgb(var(--border-primary))] p-8 mb-10">
|
||||||
<p className="text-[rgb(var(--text-muted))] font-mono text-xs uppercase tracking-widest mb-4">
|
<p className="text-[rgb(var(--text-muted))] font-mono text-xs uppercase tracking-widest mb-4">
|
||||||
>> _DOC://PUBLIC_ACCESS
|
{t('classificationHeader')}
|
||||||
</p>
|
</p>
|
||||||
<h1 className="text-4xl md:text-5xl font-mono font-bold uppercase text-[rgb(var(--text-primary))] tracking-tight">
|
<h1 className="text-4xl md:text-5xl font-mono font-bold uppercase text-[rgb(var(--text-primary))] tracking-tight">
|
||||||
ABOUT ME_
|
{t('mainTitle')}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -35,12 +36,10 @@ export default async function AboutPage({params}: Props) {
|
|||||||
<section className="border-2 border-[rgb(var(--border-primary))] p-8">
|
<section className="border-2 border-[rgb(var(--border-primary))] p-8">
|
||||||
<div className="border-l-4 border-[var(--neon-cyan)] pl-6">
|
<div className="border-l-4 border-[var(--neon-cyan)] pl-6">
|
||||||
<p className="font-mono text-base text-[rgb(var(--text-primary))] leading-relaxed mb-4">
|
<p className="font-mono text-base text-[rgb(var(--text-primary))] leading-relaxed mb-4">
|
||||||
Welcome to my corner of the internet! This is where I share my thoughts, opinions,
|
{t('introParagraph1')}
|
||||||
and experiences - from tech adventures to life as a family man. Yes, I love
|
|
||||||
technology, but there's so much more to life than just code and servers.
|
|
||||||
</p>
|
</p>
|
||||||
<p className="font-mono text-sm text-[rgb(var(--text-muted))] uppercase tracking-wider">
|
<p className="font-mono text-sm text-[rgb(var(--text-muted))] uppercase tracking-wider">
|
||||||
STATUS: ACTIVE // ROLE: DAD + DEV + LIFE ENTHUSIAST
|
{t('introLabel')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -48,46 +47,39 @@ export default async function AboutPage({params}: Props) {
|
|||||||
{/* Life & Values Section */}
|
{/* Life & Values Section */}
|
||||||
<section className="border-2 border-[rgb(var(--border-primary))] p-8">
|
<section className="border-2 border-[rgb(var(--border-primary))] p-8">
|
||||||
<h2 className="text-2xl font-mono font-bold uppercase text-[rgb(var(--text-primary))] mb-6 pb-3 border-b-2 border-[rgb(var(--border-primary))]">
|
<h2 className="text-2xl font-mono font-bold uppercase text-[rgb(var(--text-primary))] mb-6 pb-3 border-b-2 border-[rgb(var(--border-primary))]">
|
||||||
> LIFE & VALUES
|
{t('lifeValuesTitle')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="border-l-4 border-[var(--neon-pink)] pl-6">
|
<div className="border-l-4 border-[var(--neon-pink)] pl-6">
|
||||||
<h3 className="font-mono text-sm font-bold text-[var(--neon-pink)] uppercase mb-2">
|
<h3 className="font-mono text-sm font-bold text-[var(--neon-pink)] uppercase mb-2">
|
||||||
[FAMILY FIRST]
|
{t('familyFirstTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
<p className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
Being a dad to an amazing toddler is my most important role. Family time is
|
{t('familyFirstText')}
|
||||||
sacred - whether it's building block towers, exploring parks, or just
|
|
||||||
enjoying the chaos of everyday life together. Tech can wait; these moments
|
|
||||||
can't.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-l-4 border-[var(--neon-cyan)] pl-6">
|
<div className="border-l-4 border-[var(--neon-cyan)] pl-6">
|
||||||
<h3 className="font-mono text-sm font-bold text-[var(--neon-cyan)] uppercase mb-2">
|
<h3 className="font-mono text-sm font-bold text-[var(--neon-cyan)] uppercase mb-2">
|
||||||
[ACTIVE LIFESTYLE]
|
{t('activeLifestyleTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
<p className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
I believe in keeping the body active. Whether it's hitting the gym, playing
|
{t('activeLifestyleText')}
|
||||||
sports, or just staying on the move - physical activity keeps me sharp,
|
|
||||||
balanced, and ready for whatever life throws my way.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-l-4 border-[var(--neon-green)] pl-6">
|
<div className="border-l-4 border-[var(--neon-green)] pl-6">
|
||||||
<h3 className="font-mono text-sm font-bold text-[var(--neon-green)] uppercase mb-2">
|
<h3 className="font-mono text-sm font-bold text-[var(--neon-green)] uppercase mb-2">
|
||||||
[ENJOYING THE SIMPLE THINGS]
|
{t('simpleThingsTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
<p className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
Life's too short not to enjoy it. A good drink, a relaxing
|
{t('simpleThingsText')}
|
||||||
evening after a long day, or just not doing anything a blowing some steam off.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-l-4 border-[var(--neon-orange)] pl-6">
|
<div className="border-l-4 border-[var(--neon-orange)] pl-6">
|
||||||
<h3 className="font-mono text-sm font-bold text-[var(--neon-orange)] uppercase mb-2">
|
<h3 className="font-mono text-sm font-bold text-[var(--neon-orange)] uppercase mb-2">
|
||||||
[TECH WITH PURPOSE]
|
{t('techPurposeTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
<p className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
Yes, I love tech - self-hosting, privacy, tinkering with hardware. But it's
|
{t('techPurposeText')}
|
||||||
a tool, not a lifestyle. Tech should serve life, not the other way around.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,51 +88,46 @@ export default async function AboutPage({params}: Props) {
|
|||||||
{/* Content Section */}
|
{/* Content Section */}
|
||||||
<section className="border-2 border-[rgb(var(--border-primary))] p-8">
|
<section className="border-2 border-[rgb(var(--border-primary))] p-8">
|
||||||
<h2 className="text-2xl font-mono font-bold uppercase text-[rgb(var(--text-primary))] mb-6 pb-3 border-b-2 border-[rgb(var(--border-primary))]">
|
<h2 className="text-2xl font-mono font-bold uppercase text-[rgb(var(--text-primary))] mb-6 pb-3 border-b-2 border-[rgb(var(--border-primary))]">
|
||||||
> WHAT YOU'LL FIND HERE
|
{t('contentTitle')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="font-mono text-sm text-[rgb(var(--text-muted))] uppercase tracking-wider mb-6">
|
<p className="font-mono text-sm text-[rgb(var(--text-muted))] uppercase tracking-wider mb-6">
|
||||||
CONTENT SCOPE // EVERYTHING FROM TECH TO LIFE
|
{t('contentSubtitle')}
|
||||||
</p>
|
</p>
|
||||||
<ul className="space-y-3">
|
<ul className="space-y-3">
|
||||||
<li className="flex items-start gap-3">
|
<li className="flex items-start gap-3">
|
||||||
<span className="text-[var(--neon-pink)] font-mono font-bold">></span>
|
<span className="text-[var(--neon-pink)] font-mono font-bold">></span>
|
||||||
<span className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
<span className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
<strong>Thoughts & Opinions</strong> - My take on life, work, and everything in
|
<strong>{t('contentThoughts')}</strong> - {t('contentThoughtsDesc')}
|
||||||
between
|
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-3">
|
<li className="flex items-start gap-3">
|
||||||
<span className="text-[var(--neon-pink)] font-mono font-bold">></span>
|
<span className="text-[var(--neon-pink)] font-mono font-bold">></span>
|
||||||
<span className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
<span className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
<strong>Life & Family</strong> - Adventures in parenting, sports, and enjoying
|
<strong>{t('contentLifeFamily')}</strong> - {t('contentLifeFamilyDesc')}
|
||||||
the simple things
|
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-3">
|
<li className="flex items-start gap-3">
|
||||||
<span className="text-[var(--neon-cyan)] font-mono font-bold">></span>
|
<span className="text-[var(--neon-cyan)] font-mono font-bold">></span>
|
||||||
<span className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
<span className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
<strong>Tech Research</strong> - When I dive into interesting technologies and
|
<strong>{t('contentTechResearch')}</strong> - {t('contentTechResearchDesc')}
|
||||||
experiments
|
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-3">
|
<li className="flex items-start gap-3">
|
||||||
<span className="text-[var(--neon-cyan)] font-mono font-bold">></span>
|
<span className="text-[var(--neon-cyan)] font-mono font-bold">></span>
|
||||||
<span className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
<span className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
<strong>System Administration</strong> - Self-hosting, infrastructure, and
|
<strong>{t('contentSysAdmin')}</strong> - {t('contentSysAdminDesc')}
|
||||||
DevOps adventures
|
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-3">
|
<li className="flex items-start gap-3">
|
||||||
<span className="text-[var(--neon-cyan)] font-mono font-bold">></span>
|
<span className="text-[var(--neon-cyan)] font-mono font-bold">></span>
|
||||||
<span className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
<span className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
<strong>Development Insights</strong> - Lessons learned from building software
|
<strong>{t('contentDevelopment')}</strong> - {t('contentDevelopmentDesc')}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-3">
|
<li className="flex items-start gap-3">
|
||||||
<span className="text-[var(--neon-green)] font-mono font-bold">></span>
|
<span className="text-[var(--neon-green)] font-mono font-bold">></span>
|
||||||
<span className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
<span className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
<strong>Random Stuff</strong> - Because life doesn't fit into neat
|
<strong>{t('contentRandom')}</strong> - {t('contentRandomDesc')}
|
||||||
categories!
|
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -149,40 +136,39 @@ export default async function AboutPage({params}: Props) {
|
|||||||
{/* Areas of Focus Section */}
|
{/* Areas of Focus Section */}
|
||||||
<section className="border-2 border-[rgb(var(--border-primary))] p-8">
|
<section className="border-2 border-[rgb(var(--border-primary))] p-8">
|
||||||
<h2 className="text-2xl font-mono font-bold uppercase text-[rgb(var(--text-primary))] mb-6 pb-3 border-b-2 border-[rgb(var(--border-primary))]">
|
<h2 className="text-2xl font-mono font-bold uppercase text-[rgb(var(--text-primary))] mb-6 pb-3 border-b-2 border-[rgb(var(--border-primary))]">
|
||||||
> AREAS OF FOCUS
|
{t('focusTitle')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid md:grid-cols-2 gap-4">
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
<div className="border border-[rgb(var(--border-primary))] p-4">
|
<div className="border border-[rgb(var(--border-primary))] p-4">
|
||||||
<h3 className="font-mono text-xs font-bold text-[var(--neon-pink)] uppercase mb-2">
|
<h3 className="font-mono text-xs font-bold text-[var(--neon-pink)] uppercase mb-2">
|
||||||
[BEING A DAD]
|
{t('focusBeingDadTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
Playing with my boy, teaching moments, watching him grow, building memories
|
{t('focusBeingDadText')}
|
||||||
together
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border border-[rgb(var(--border-primary))] p-4">
|
<div className="border border-[rgb(var(--border-primary))] p-4">
|
||||||
<h3 className="font-mono text-xs font-bold text-[var(--neon-cyan)] uppercase mb-2">
|
<h3 className="font-mono text-xs font-bold text-[var(--neon-cyan)] uppercase mb-2">
|
||||||
[STAYING ACTIVE]
|
{t('focusStayingActiveTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
Gym sessions, sports, keeping fit, maintaining energy for life's demands
|
{t('focusStayingActiveText')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border border-[rgb(var(--border-primary))] p-4">
|
<div className="border border-[rgb(var(--border-primary))] p-4">
|
||||||
<h3 className="font-mono text-xs font-bold text-[var(--neon-green)] uppercase mb-2">
|
<h3 className="font-mono text-xs font-bold text-[var(--neon-green)] uppercase mb-2">
|
||||||
[TECHNOLOGY & SYSTEMS]
|
{t('focusTechnologyTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
Software development, infrastructure, DevOps, self-hosting adventures
|
{t('focusTechnologyText')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border border-[rgb(var(--border-primary))] p-4">
|
<div className="border border-[rgb(var(--border-primary))] p-4">
|
||||||
<h3 className="font-mono text-xs font-bold text-[var(--neon-orange)] uppercase mb-2">
|
<h3 className="font-mono text-xs font-bold text-[var(--neon-orange)] uppercase mb-2">
|
||||||
[LIFE BALANCE]
|
{t('focusLifeBalanceTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
Relaxing with good company, enjoying downtime, appreciating the simple moments
|
{t('focusLifeBalanceText')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -190,42 +176,42 @@ export default async function AboutPage({params}: Props) {
|
|||||||
{/* Tech Stack Section */}
|
{/* Tech Stack Section */}
|
||||||
<section className="border-2 border-[rgb(var(--border-primary))] p-8">
|
<section className="border-2 border-[rgb(var(--border-primary))] p-8">
|
||||||
<h2 className="text-2xl font-mono font-bold uppercase text-[rgb(var(--text-primary))] mb-6 pb-3 border-b-2 border-[rgb(var(--border-primary))]">
|
<h2 className="text-2xl font-mono font-bold uppercase text-[rgb(var(--text-primary))] mb-6 pb-3 border-b-2 border-[rgb(var(--border-primary))]">
|
||||||
> TECH STACK
|
{t('techStackTitle')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="font-mono text-sm text-[rgb(var(--text-muted))] uppercase tracking-wider mb-6">
|
<p className="font-mono text-sm text-[rgb(var(--text-muted))] uppercase tracking-wider mb-6">
|
||||||
TOOLS I USE // WHEN NEEDED
|
{t('techStackSubtitle')}
|
||||||
</p>
|
</p>
|
||||||
<div className="grid md:grid-cols-2 gap-4">
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
<div className="border border-[rgb(var(--border-primary))] p-4">
|
<div className="border border-[rgb(var(--border-primary))] p-4">
|
||||||
<h3 className="font-mono text-xs font-bold text-[var(--neon-cyan)] uppercase mb-2">
|
<h3 className="font-mono text-xs font-bold text-[var(--neon-cyan)] uppercase mb-2">
|
||||||
[DEVELOPMENT]
|
{t('techStackDevelopmentTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
.NET, Golang, TypeScript, Next.js, React
|
{t('techStackDevelopmentText')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border border-[rgb(var(--border-primary))] p-4">
|
<div className="border border-[rgb(var(--border-primary))] p-4">
|
||||||
<h3 className="font-mono text-xs font-bold text-[var(--neon-cyan)] uppercase mb-2">
|
<h3 className="font-mono text-xs font-bold text-[var(--neon-cyan)] uppercase mb-2">
|
||||||
[INFRASTRUCTURE]
|
{t('techStackInfrastructureTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
Windows Server, Linux, Docker, Hyper-V
|
{t('techStackInfrastructureText')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border border-[rgb(var(--border-primary))] p-4">
|
<div className="border border-[rgb(var(--border-primary))] p-4">
|
||||||
<h3 className="font-mono text-xs font-bold text-[var(--neon-cyan)] uppercase mb-2">
|
<h3 className="font-mono text-xs font-bold text-[var(--neon-cyan)] uppercase mb-2">
|
||||||
[DESIGN]
|
{t('techStackDesignTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
Tailwind CSS, Markdown, Terminal aesthetics
|
{t('techStackDesignText')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border border-[rgb(var(--border-primary))] p-4">
|
<div className="border border-[rgb(var(--border-primary))] p-4">
|
||||||
<h3 className="font-mono text-xs font-bold text-[var(--neon-cyan)] uppercase mb-2">
|
<h3 className="font-mono text-xs font-bold text-[var(--neon-cyan)] uppercase mb-2">
|
||||||
[SELF-HOSTING]
|
{t('techStackSelfHostingTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
<p className="font-mono text-xs text-[rgb(var(--text-primary))] leading-relaxed">
|
||||||
Home lab, privacy-focused services, full control, Git server
|
{t('techStackSelfHostingText')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -233,7 +219,7 @@ export default async function AboutPage({params}: Props) {
|
|||||||
{/* Contact Section */}
|
{/* Contact Section */}
|
||||||
<section className="border-2 border-[rgb(var(--border-primary))] p-8">
|
<section className="border-2 border-[rgb(var(--border-primary))] p-8">
|
||||||
<h2 className="text-2xl font-mono font-bold uppercase text-[rgb(var(--text-primary))] mb-6 pb-3 border-b-2 border-[rgb(var(--border-primary))]">
|
<h2 className="text-2xl font-mono font-bold uppercase text-[rgb(var(--text-primary))] mb-6 pb-3 border-b-2 border-[rgb(var(--border-primary))]">
|
||||||
> CONTACT
|
{t('contactTitle')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="border-l-4 border-[var(--neon-pink)] pl-6">
|
<div className="border-l-4 border-[var(--neon-pink)] pl-6">
|
||||||
{/* <p className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed mb-4">
|
{/* <p className="font-mono text-sm text-[rgb(var(--text-primary))] leading-relaxed mb-4">
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ export default function NotFound() {
|
|||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h1 className="text-6xl font-bold text-gray-300 dark:text-gray-700 mb-4">404</h1>
|
<h1 className="text-6xl font-bold text-gray-300 dark:text-gray-700 mb-4">404</h1>
|
||||||
<h2 className="text-2xl font-semibold mb-4">{t('title')}</h2>
|
<h2 className="text-2xl font-semibold mb-4">{t('title')}</h2>
|
||||||
<p className="text-gray-600 dark:text-gray-400 mb-8">
|
<p className="text-gray-600 dark:text-gray-400 mb-8">{t('description')}</p>
|
||||||
{t('description')}
|
|
||||||
</p>
|
|
||||||
<div className="space-x-4">
|
<div className="space-x-4">
|
||||||
<Link
|
<Link
|
||||||
href="/blog"
|
href="/blog"
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ import { Metadata } from 'next'
|
|||||||
import { notFound } from 'next/navigation'
|
import { notFound } from 'next/navigation'
|
||||||
import { Link } from '@/src/i18n/navigation'
|
import { Link } from '@/src/i18n/navigation'
|
||||||
import { getAllPosts, getPostBySlug, getRelatedPosts } from '@/lib/markdown'
|
import { getAllPosts, getPostBySlug, getRelatedPosts } from '@/lib/markdown'
|
||||||
import { formatDate, formatRelativeDate } from '@/lib/utils'
|
import { formatDate } from '@/lib/utils'
|
||||||
import { TableOfContents } from '@/components/blog/table-of-contents'
|
import { TableOfContents } from '@/components/blog/table-of-contents'
|
||||||
import { ReadingProgress } from '@/components/blog/reading-progress'
|
import { ReadingProgress } from '@/components/blog/reading-progress'
|
||||||
import { StickyFooter } from '@/components/blog/sticky-footer'
|
import { StickyFooter } from '@/components/blog/sticky-footer'
|
||||||
import MarkdownRenderer from '@/components/blog/markdown-renderer'
|
import MarkdownRenderer from '@/components/blog/markdown-renderer'
|
||||||
import { setRequestLocale } from 'next-intl/server'
|
|
||||||
import { routing } from '@/src/i18n/routing'
|
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams() {
|
||||||
const locales = ['en', 'ro']
|
const locales = ['en', 'ro']
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { BlogCard } from '@/components/blog/blog-card'
|
|||||||
import { SearchBar } from '@/components/blog/search-bar'
|
import { SearchBar } from '@/components/blog/search-bar'
|
||||||
import { SortDropdown } from '@/components/blog/sort-dropdown'
|
import { SortDropdown } from '@/components/blog/sort-dropdown'
|
||||||
import { TagFilter } from '@/components/blog/tag-filter'
|
import { TagFilter } from '@/components/blog/tag-filter'
|
||||||
import { Navbar } from '@/components/blog/navbar'
|
|
||||||
|
|
||||||
interface BlogPageClientProps {
|
interface BlogPageClientProps {
|
||||||
posts: Post[]
|
posts: Post[]
|
||||||
@@ -69,10 +68,10 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps)
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="border-l border-[var(--neon-cyan)] pl-6 mb-12">
|
<div className="border-l border-[var(--neon-cyan)] pl-6 mb-12">
|
||||||
<p className="font-mono text-xs text-[rgb(var(--text-muted))] uppercase tracking-widest mb-2">
|
<p className="font-mono text-xs text-[rgb(var(--text-muted))] uppercase tracking-widest mb-2">
|
||||||
{t("subtitle")}
|
{t('subtitle')}
|
||||||
</p>
|
</p>
|
||||||
<h1 className="text-4xl md:text-6xl font-mono font-bold text-[rgb(var(--text-primary))] uppercase tracking-tight">
|
<h1 className="text-4xl md:text-6xl font-mono font-bold text-[rgb(var(--text-primary))] uppercase tracking-tight">
|
||||||
> {t("title")}_
|
> {t('title')}_
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -104,8 +103,7 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps)
|
|||||||
{/* Results Count */}
|
{/* Results Count */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<p className="font-mono text-sm text-[rgb(var(--text-muted))] uppercase">
|
<p className="font-mono text-sm text-[rgb(var(--text-muted))] uppercase">
|
||||||
{t("foundPosts", {count: filteredAndSortedPosts.length})}{' '}
|
{t('foundPosts', { count: filteredAndSortedPosts.length })}{' '}
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -128,7 +126,7 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps)
|
|||||||
) : (
|
) : (
|
||||||
<div className="border border-[rgb(var(--border-primary))] bg-[rgb(var(--bg-secondary))] p-12 text-center">
|
<div className="border border-[rgb(var(--border-primary))] bg-[rgb(var(--bg-secondary))] p-12 text-center">
|
||||||
<p className="font-mono text-lg text-[rgb(var(--text-muted))] uppercase">
|
<p className="font-mono text-lg text-[rgb(var(--text-muted))] uppercase">
|
||||||
{t("noPosts")}
|
{t('noPosts')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -142,7 +140,7 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps)
|
|||||||
disabled={currentPage === 1}
|
disabled={currentPage === 1}
|
||||||
className="px-6 py-3 font-mono text-sm uppercase border border-[rgb(var(--border-primary))] text-[rgb(var(--text-primary))] disabled:opacity-30 disabled:cursor-not-allowed hover:border-[var(--neon-cyan)] hover:text-[var(--neon-cyan)] transition-colors cursor-pointer"
|
className="px-6 py-3 font-mono text-sm uppercase border border-[rgb(var(--border-primary))] text-[rgb(var(--text-primary))] disabled:opacity-30 disabled:cursor-not-allowed hover:border-[var(--neon-cyan)] hover:text-[var(--neon-cyan)] transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
< PREV
|
{t('prev')}
|
||||||
</button>
|
</button>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
|
{Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
|
||||||
@@ -164,7 +162,7 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps)
|
|||||||
disabled={currentPage === totalPages}
|
disabled={currentPage === totalPages}
|
||||||
className="px-6 py-3 font-mono text-sm uppercase border border-[rgb(var(--border-primary))] text-[rgb(var(--text-primary))] disabled:opacity-30 disabled:cursor-not-allowed hover:border-[var(--neon-cyan)] hover:text-[var(--neon-cyan)] transition-colors cursor-pointer"
|
className="px-6 py-3 font-mono text-sm uppercase border border-[rgb(var(--border-primary))] text-[rgb(var(--text-primary))] disabled:opacity-30 disabled:cursor-not-allowed hover:border-[var(--neon-cyan)] hover:text-[var(--neon-cyan)] transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
NEXT >
|
{t('next')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { getAllPosts } from '@/lib/markdown'
|
import { getAllPosts } from '@/lib/markdown'
|
||||||
import BlogPageClient from './blog-client'
|
import BlogPageClient from './blog-client'
|
||||||
import {setRequestLocale} from 'next-intl/server'
|
import { setRequestLocale } from 'next-intl/server'
|
||||||
|
|
||||||
export default async function BlogPage() {
|
export default async function BlogPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||||
const posts = await getAllPosts()
|
const { locale } = await params
|
||||||
|
await setRequestLocale(locale)
|
||||||
|
const posts = await getAllPosts(locale)
|
||||||
const allTags = Array.from(new Set(posts.flatMap(post => post.frontmatter.tags))).sort()
|
const allTags = Array.from(new Set(posts.flatMap(post => post.frontmatter.tags))).sort()
|
||||||
|
|
||||||
return <BlogPageClient posts={posts} allTags={allTags} />
|
return <BlogPageClient posts={posts} allTags={allTags} />
|
||||||
|
|||||||
@@ -1,24 +1,20 @@
|
|||||||
import {notFound} from 'next/navigation'
|
import { notFound } from 'next/navigation'
|
||||||
import {setRequestLocale} from 'next-intl/server'
|
import { setRequestLocale } from 'next-intl/server'
|
||||||
import {routing} from '@/src/i18n/routing'
|
import { routing } from '@/src/i18n/routing'
|
||||||
import {ReactNode} from 'react'
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
breadcrumbs: ReactNode
|
breadcrumbs: ReactNode
|
||||||
params: Promise<{locale: string}>
|
params: Promise<{ locale: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return routing.locales.map((locale) => ({locale}))
|
return routing.locales.map(locale => ({ locale }))
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function LocaleLayout({
|
export default async function LocaleLayout({ children, breadcrumbs, params }: Props) {
|
||||||
children,
|
const { locale } = await params
|
||||||
breadcrumbs,
|
|
||||||
params
|
|
||||||
}: Props) {
|
|
||||||
const {locale} = await params
|
|
||||||
|
|
||||||
if (!routing.locales.includes(locale as any)) {
|
if (!routing.locales.includes(locale as any)) {
|
||||||
notFound()
|
notFound()
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
import {Link} from '@/src/i18n/navigation'
|
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} from 'next-intl/server'
|
import { setRequestLocale, getTranslations } from 'next-intl/server'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
params: Promise<{ locale: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function HomePage({ params }: Props) {
|
||||||
|
const { locale } = await params
|
||||||
|
setRequestLocale(locale)
|
||||||
|
const t = await getTranslations('Home')
|
||||||
|
// const tNav = await getTranslations('Navigation')
|
||||||
|
|
||||||
export default async function HomePage() {
|
|
||||||
const allPosts = await getAllPosts()
|
const allPosts = await getAllPosts()
|
||||||
const featuredPosts = allPosts.slice(0, 6)
|
const featuredPosts = allPosts.slice(0, 6)
|
||||||
|
|
||||||
@@ -20,41 +29,17 @@ export default async function HomePage() {
|
|||||||
<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">
|
|
||||||
TERMINAL:// V2.0
|
|
||||||
</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"
|
|
||||||
>
|
|
||||||
[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"
|
|
||||||
>
|
|
||||||
[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">
|
||||||
DOCUMENT LEVEL-1 //
|
{t('documentLevel')}
|
||||||
</p>
|
</p>
|
||||||
<h1 className="text-4xl md:text-6xl lg:text-7xl font-mono font-bold text-slate-900 dark:text-slate-100 uppercase tracking-tight mb-6">
|
<h1 className="text-4xl md:text-6xl lg:text-7xl font-mono font-bold text-slate-900 dark:text-slate-100 uppercase tracking-tight mb-6">
|
||||||
BUILD. WRITE.
|
{t('heroTitle')}
|
||||||
<br />
|
|
||||||
SHARE.
|
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-base md:text-lg lg:text-xl text-slate-700 dark:text-slate-400 font-mono leading-relaxed max-w-2xl">
|
<p className="text-base md:text-lg lg:text-xl text-slate-700 dark:text-slate-400 font-mono leading-relaxed max-w-2xl">
|
||||||
> Explore ideas
|
{t('heroSubtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -63,13 +48,13 @@ export default async function HomePage() {
|
|||||||
href="/blog"
|
href="/blog"
|
||||||
className="px-6 md:px-8 py-3 md:py-4 bg-cyan-700 dark:bg-cyan-900 text-white dark:text-slate-100 border-2 border-cyan-600 dark:border-cyan-700 font-mono font-bold uppercase text-xs md:text-sm tracking-wider hover:bg-cyan-600 dark:hover:bg-cyan-800 hover:border-cyan-500 dark:hover:border-cyan-600 rounded-none transition-colors duration-200"
|
className="px-6 md:px-8 py-3 md:py-4 bg-cyan-700 dark:bg-cyan-900 text-white dark:text-slate-100 border-2 border-cyan-600 dark:border-cyan-700 font-mono font-bold uppercase text-xs md:text-sm tracking-wider hover:bg-cyan-600 dark:hover:bg-cyan-800 hover:border-cyan-500 dark:hover:border-cyan-600 rounded-none transition-colors duration-200"
|
||||||
>
|
>
|
||||||
[CHECK POSTS]
|
{t('checkPostsButton')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/about"
|
href="/about"
|
||||||
className="px-6 md:px-8 py-3 md:py-4 bg-transparent text-slate-700 dark:text-slate-300 border-2 border-slate-400 dark:border-slate-700 font-mono font-bold uppercase text-xs md:text-sm tracking-wider hover:bg-slate-200 dark:hover:bg-slate-800 hover:border-slate-500 dark:hover:border-slate-600 rounded-none transition-colors duration-200"
|
className="px-6 md:px-8 py-3 md:py-4 bg-transparent text-slate-700 dark:text-slate-300 border-2 border-slate-400 dark:border-slate-700 font-mono font-bold uppercase text-xs md:text-sm tracking-wider hover:bg-slate-200 dark:hover:bg-slate-800 hover:border-slate-500 dark:hover:border-slate-600 rounded-none transition-colors duration-200"
|
||||||
>
|
>
|
||||||
[ABOUT ME]
|
{t('aboutMeButton')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,10 +66,10 @@ export default async function HomePage() {
|
|||||||
<div className="max-w-7xl mx-auto px-6">
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
<div className="border-l-4 border-emerald-700 dark:border-emerald-900 pl-6 mb-12">
|
<div className="border-l-4 border-emerald-700 dark:border-emerald-900 pl-6 mb-12">
|
||||||
<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">
|
||||||
ARCHIVE ACCESS // RECENT ENTRIES
|
{t('recentEntriesLabel')}
|
||||||
</p>
|
</p>
|
||||||
<h2 className="text-3xl md:text-5xl font-mono font-bold text-slate-900 dark:text-slate-100 uppercase tracking-tight">
|
<h2 className="text-3xl md:text-5xl font-mono font-bold text-slate-900 dark:text-slate-100 uppercase tracking-tight">
|
||||||
> RECENT ENTRIES
|
{t('recentEntriesTitle')}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -134,7 +119,7 @@ export default async function HomePage() {
|
|||||||
href={`/blog/${post.slug}`}
|
href={`/blog/${post.slug}`}
|
||||||
className="inline-flex items-center text-cyan-600 dark:text-cyan-400 font-mono text-xs font-bold uppercase tracking-wider hover:text-cyan-500 dark:hover:text-cyan-300 border-2 border-slate-400 dark:border-slate-700 px-4 py-2 hover:border-cyan-700 dark:hover:border-cyan-900 transition-colors duration-200"
|
className="inline-flex items-center text-cyan-600 dark:text-cyan-400 font-mono text-xs font-bold uppercase tracking-wider hover:text-cyan-500 dark:hover:text-cyan-300 border-2 border-slate-400 dark:border-slate-700 px-4 py-2 hover:border-cyan-700 dark:hover:border-cyan-900 transition-colors duration-200"
|
||||||
>
|
>
|
||||||
[ACCESEAZĂ] >>
|
{t('accessButton')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -147,14 +132,14 @@ export default async function HomePage() {
|
|||||||
href="/blog"
|
href="/blog"
|
||||||
className="inline-flex items-center px-8 py-4 bg-transparent text-slate-700 dark:text-slate-300 border-2 border-slate-400 dark:border-slate-700 font-mono font-bold uppercase text-sm tracking-wider hover:bg-slate-200 dark:hover:bg-slate-800 hover:border-slate-500 dark:hover:border-slate-600 transition-colors duration-200"
|
className="inline-flex items-center px-8 py-4 bg-transparent text-slate-700 dark:text-slate-300 border-2 border-slate-400 dark:border-slate-700 font-mono font-bold uppercase text-sm tracking-wider hover:bg-slate-200 dark:hover:bg-slate-800 hover:border-slate-500 dark:hover:border-slate-600 transition-colors duration-200"
|
||||||
>
|
>
|
||||||
[SEE POSTS] >>
|
{t('seePostsButton')}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
<Link
|
<Link
|
||||||
href="/tags"
|
href="/tags"
|
||||||
className="inline-flex items-center px-8 py-4 bg-transparent text-slate-700 dark:text-slate-300 border-2 border-slate-400 dark:border-slate-700 font-mono font-bold uppercase text-sm tracking-wider hover:bg-slate-200 dark:hover:bg-slate-800 hover:border-slate-500 dark:hover:border-slate-600 transition-colors duration-200"
|
className="inline-flex items-center px-8 py-4 bg-transparent text-slate-700 dark:text-slate-300 border-2 border-slate-400 dark:border-slate-700 font-mono font-bold uppercase text-sm tracking-wider hover:bg-slate-200 dark:hover:bg-slate-800 hover:border-slate-500 dark:hover:border-slate-600 transition-colors duration-200"
|
||||||
>
|
>
|
||||||
[SEE ALL TAGS] >>
|
{t('seeAllTagsButton')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { Metadata } from 'next'
|
import { Metadata } from 'next'
|
||||||
import { notFound } from 'next/navigation'
|
import { notFound } from 'next/navigation'
|
||||||
import {Link} from '@/src/i18n/navigation'
|
import { Link } from '@/src/i18n/navigation'
|
||||||
import { getAllTags, getPostsByTag, getTagInfo, getRelatedTags } from '@/lib/tags'
|
import { getAllTags, getPostsByTag, getTagInfo, getRelatedTags } from '@/lib/tags'
|
||||||
import { TagList } from '@/components/blog/tag-list'
|
import { TagList } from '@/components/blog/tag-list'
|
||||||
import { formatDate } from '@/lib/utils'
|
import { formatDate } from '@/lib/utils'
|
||||||
import {setRequestLocale} from 'next-intl/server'
|
|
||||||
import {routing} from '@/src/i18n/routing'
|
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams() {
|
||||||
const tags = await getAllTags()
|
const tags = await getAllTags()
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Metadata } from 'next'
|
import { Metadata } from 'next'
|
||||||
import {Link} from '@/src/i18n/navigation'
|
import { Link } from '@/src/i18n/navigation'
|
||||||
import { getAllTags, getTagCloud } from '@/lib/tags'
|
import { getAllTags, getTagCloud } from '@/lib/tags'
|
||||||
import { TagCloud } from '@/components/blog/tag-cloud'
|
import { TagCloud } from '@/components/blog/tag-cloud'
|
||||||
import { TagBadge } from '@/components/blog/tag-badge'
|
import { TagBadge } from '@/components/blog/tag-badge'
|
||||||
import {setRequestLocale} from 'next-intl/server'
|
import { setRequestLocale } from 'next-intl/server'
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Tag-uri',
|
title: 'Tag-uri',
|
||||||
@@ -11,11 +11,11 @@ export const metadata: Metadata = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
params: Promise<{locale: string}>
|
params: Promise<{ locale: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function TagsPage({params}: Props) {
|
export default async function TagsPage({ params }: Props) {
|
||||||
const {locale} = await params
|
const { locale } = await params
|
||||||
setRequestLocale(locale)
|
setRequestLocale(locale)
|
||||||
const allTags = await getAllTags()
|
const allTags = await getAllTags()
|
||||||
const tagCloud = await getTagCloud()
|
const tagCloud = await getTagCloud()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { NextResponse } from 'next/server'
|
|||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3030'
|
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3030'
|
||||||
const posts = await getAllPosts("en", false)
|
const posts = await getAllPosts('en', false)
|
||||||
|
|
||||||
const rss = `<?xml version="1.0" encoding="UTF-8"?>
|
const rss = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { JetBrains_Mono } from 'next/font/google'
|
|||||||
import './globals.css'
|
import './globals.css'
|
||||||
import { ThemeProvider } from '@/providers/providers'
|
import { ThemeProvider } from '@/providers/providers'
|
||||||
import '@/lib/env-validation'
|
import '@/lib/env-validation'
|
||||||
import {NextIntlClientProvider} from 'next-intl'
|
import { NextIntlClientProvider } from 'next-intl'
|
||||||
import {getMessages} from 'next-intl/server'
|
import { getMessages } from 'next-intl/server'
|
||||||
|
|
||||||
const jetbrainsMono = JetBrains_Mono({ subsets: ['latin'], variable: '--font-mono' })
|
const jetbrainsMono = JetBrains_Mono({ subsets: ['latin'], variable: '--font-mono' })
|
||||||
|
|
||||||
@@ -30,11 +30,7 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function RootLayout({
|
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
children
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode
|
|
||||||
}) {
|
|
||||||
const messages = await getMessages()
|
const messages = await getMessages()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3030'
|
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3030'
|
||||||
|
|
||||||
// Get all blog posts
|
// Get all blog posts
|
||||||
const posts = await getAllPosts("en", false)
|
const posts = await getAllPosts('en', false)
|
||||||
|
|
||||||
// Generate sitemap entries for blog posts
|
// Generate sitemap entries for blog posts
|
||||||
const blogPosts: MetadataRoute.Sitemap = posts.map(post => ({
|
const blogPosts: MetadataRoute.Sitemap = posts.map(post => ({
|
||||||
|
|||||||
@@ -62,9 +62,7 @@ export function OptimizedImage({
|
|||||||
return (
|
return (
|
||||||
<span className={`block my-8 ${className}`}>
|
<span className={`block my-8 ${className}`}>
|
||||||
{imageElement}
|
{imageElement}
|
||||||
{caption && (
|
{caption && <span className="block mt-3 text-center text-sm text-zinc-400">{caption}</span>}
|
||||||
<span className="block mt-3 text-center text-sm text-zinc-400">{caption}</span>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function BlogCard({ post, variant }: BlogCardProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<span className="inline-flex items-center font-mono text-xs uppercase text-cyan-400 hover:text-cyan-300 transition-colors">
|
<span className="inline-flex items-center font-mono text-xs uppercase text-cyan-400 hover:text-cyan-300 transition-colors">
|
||||||
> {t('readingTime', {minutes: post.readingTime})}
|
> {t('readingTime', { minutes: post.readingTime })}
|
||||||
</span>
|
</span>
|
||||||
</article>
|
</article>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -84,7 +84,7 @@ export function BlogCard({ post, variant }: BlogCardProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<span className="inline-flex items-center font-mono text-xs uppercase text-cyan-400 hover:text-cyan-300 transition-colors">
|
<span className="inline-flex items-center font-mono text-xs uppercase text-cyan-400 hover:text-cyan-300 transition-colors">
|
||||||
> {t('readingTime', {minutes: post.readingTime})}
|
> {t('readingTime', { minutes: post.readingTime })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -129,7 +129,7 @@ export function BlogCard({ post, variant }: BlogCardProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<span className="inline-flex items-center font-mono text-xs uppercase text-cyan-400 hover:text-cyan-300 transition-colors">
|
<span className="inline-flex items-center font-mono text-xs uppercase text-cyan-400 hover:text-cyan-300 transition-colors">
|
||||||
> {t('readingTime', {minutes: post.readingTime})}
|
> {t('readingTime', { minutes: post.readingTime })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ interface CodeBlockProps {
|
|||||||
code: string
|
code: string
|
||||||
language: string
|
language: string
|
||||||
filename?: string
|
filename?: string
|
||||||
showLineNumbers?: boolean
|
_showLineNumbers?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CodeBlock({ code, language, filename, showLineNumbers = true }: CodeBlockProps) {
|
export function CodeBlock({ code, language, filename, _showLineNumbers = true }: CodeBlockProps) {
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
|
|
||||||
const handleCopy = async () => {
|
const handleCopy = async () => {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ interface MarkdownRendererProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function MarkdownRenderer({ content, className = '' }: MarkdownRendererProps) {
|
export default function MarkdownRenderer({ content, className = '' }: MarkdownRendererProps) {
|
||||||
const locale = useLocale()
|
const _locale = useLocale()
|
||||||
return (
|
return (
|
||||||
<div className={`prose prose-invert prose-zinc max-w-none ${className}`}>
|
<div className={`prose prose-invert prose-zinc max-w-none ${className}`}>
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getPopularTags } from '@/lib/tags'
|
|||||||
import { TagBadge } from './tag-badge'
|
import { TagBadge } from './tag-badge'
|
||||||
|
|
||||||
export async function PopularTags({ limit = 5 }: { limit?: number }) {
|
export async function PopularTags({ limit = 5 }: { limit?: number }) {
|
||||||
const tags = await getPopularTags("en", limit)
|
const tags = await getPopularTags('en', limit)
|
||||||
|
|
||||||
if (tags.length === 0) return null
|
if (tags.length === 0) return null
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function TagCloud({ tags }: TagCloudProps) {
|
|||||||
hover:text-cyan-400
|
hover:text-cyan-400
|
||||||
transition-colors
|
transition-colors
|
||||||
`}
|
`}
|
||||||
title={t('postsWithTag', {count: tag.count, tag: tag.name})}
|
title={t('postsWithTag', { count: tag.count, tag: tag.name })}
|
||||||
>
|
>
|
||||||
#{tag.name}
|
#{tag.name}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import {Link} from '@/i18n/navigation'
|
import { Link } from '@/i18n/navigation'
|
||||||
import { usePathname } from 'next/navigation'
|
import { usePathname } from 'next/navigation'
|
||||||
import { useLocale, useTranslations } from 'next-intl'
|
import { useLocale, useTranslations } from 'next-intl'
|
||||||
import { Fragment } from 'react'
|
import { Fragment } from 'react'
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
'use client';
|
'use client'
|
||||||
|
|
||||||
import {useLocale} from 'next-intl';
|
import { useLocale } from 'next-intl'
|
||||||
import {useRouter, usePathname} from '@/i18n/navigation';
|
import { useRouter, usePathname } from '@/i18n/navigation'
|
||||||
import {routing} from '@/i18n/routing';
|
import { routing } from '@/i18n/routing'
|
||||||
import {useState} from 'react';
|
import { useState } from 'react'
|
||||||
|
|
||||||
export default function LanguageSwitcher() {
|
export default function LanguageSwitcher() {
|
||||||
const locale = useLocale();
|
const locale = useLocale()
|
||||||
const router = useRouter();
|
const router = useRouter()
|
||||||
const pathname = usePathname();
|
const pathname = usePathname()
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
const handleLocaleChange = (newLocale: string) => {
|
const handleLocaleChange = (newLocale: string) => {
|
||||||
router.replace(pathname, {locale: newLocale});
|
router.replace(pathname, { locale: newLocale })
|
||||||
router.refresh();
|
router.refresh()
|
||||||
setIsOpen(false);
|
setIsOpen(false)
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative z-[100]">
|
<div className="relative z-[100]">
|
||||||
@@ -36,9 +36,8 @@ export default function LanguageSwitcher() {
|
|||||||
className={`
|
className={`
|
||||||
w-full text-left px-4 py-2 font-mono uppercase text-xs
|
w-full text-left px-4 py-2 font-mono uppercase text-xs
|
||||||
border-b border-slate-700 last:border-b-0
|
border-b border-slate-700 last:border-b-0
|
||||||
${locale === loc
|
${
|
||||||
? 'bg-cyan-900 text-cyan-300'
|
locale === loc ? 'bg-cyan-900 text-cyan-300' : 'text-slate-400 hover:bg-slate-800'
|
||||||
: 'text-slate-400 hover:bg-slate-800'
|
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
@@ -48,12 +47,7 @@ export default function LanguageSwitcher() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && <div className="fixed inset-0 z-40" onClick={() => setIsOpen(false)} />}
|
||||||
<div
|
|
||||||
className="fixed inset-0 z-40"
|
|
||||||
onClick={() => setIsOpen(false)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: 'Why I created this page'
|
title: 'First post'
|
||||||
description: 'First post'
|
description: 'First post'
|
||||||
date: '2025-12-02'
|
date: '2025-12-02'
|
||||||
author: 'Rares'
|
author: 'Rares'
|
||||||
@@ -24,7 +24,7 @@ Well, yes, there are. But I believe that sharing some of my opinions and experie
|
|||||||
|
|
||||||
## Why self-host?
|
## Why self-host?
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Now, let's talk about why I chose to self-host this blog. In a nutshell, self-hosting gave me:
|
Now, let's talk about why I chose to self-host this blog. In a nutshell, self-hosting gave me:
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 'Why I created this page'
|
title: 'Primul post'
|
||||||
description: 'First post'
|
description: 'Primul post'
|
||||||
date: '2025-12-02'
|
date: '2025-12-02'
|
||||||
author: 'Rares'
|
author: 'Rares'
|
||||||
category: 'Opinion'
|
category: 'Opinion'
|
||||||
@@ -13,29 +13,28 @@ draft: false
|
|||||||
|
|
||||||
Daca te intrebi de ce aceata pagina? Pentru ca vreau sa jurnalizez lucrurile la care lucrez, sau gandurile pe care vreua sa le impartesesc.
|
Daca te intrebi de ce aceata pagina? Pentru ca vreau sa jurnalizez lucrurile la care lucrez, sau gandurile pe care vreua sa le impartesesc.
|
||||||
|
|
||||||
## Why a blog?
|
## De ce blog?
|
||||||
|
|
||||||
You might be thinking, "Why create another tech blog? There are plenty out there."
|
Dacă te gândești de ce să mai creezi inca un blog cand sunt atea pe net, pai ideea este ca este si ca un jurnal, unde postez lucruri si ma ajuta sa revin la ceea ce am investigat.
|
||||||
Well, yes, there are. But I believe that sharing some of my opinions and experiences will eventually act out as a journal:
|
|
||||||
|
|
||||||
1. **Personal touch**: Even though i've been working corporate all my career, this webpage won't contain that sugar coated language 😅. It's a place where you'll get to know me – my thoughts, my mistakes, and my victories. I believe that this personal touch makes the content more engaging and relatable.
|
1. **Este personal**: Nu este un lucru formal, chiar daca am lucrat in corporate, unde toti se asteapta sa fii prietenos si zambaret mereu, aici o sa fie mai sincere opiniile.
|
||||||
2. **Beyond tech**: While I'll be writing about technology, I also want to explore other topics that interest me, such as mental health, productivity and so on.... I think a well-rounded approach can help create a more engaging and informative space.
|
2. **Mai mult decat tech**: O sa scriu despre tech dar, nu ăsta e focusul aici
|
||||||
3. **Self-hosting adventure**: As you might have guessed from the title, this blog is self-hosted. This was an exciting journey for me, and I'll be sharing my experiences, challenges, and learnings along the way. If you're interested in self-hosting or just want to understand what it's all about, you might find what worked for me or didn't.
|
3. **Cum fac selfhost**: Fac selfhost, la cateva servicii utile: git, webpage-ul acesta. O sa incerc sa povestesc si cum fac mentenanta sau ce probleme am intampinat pe parcursul acestor deploymenturi.
|
||||||
|
|
||||||
## Why self-host?
|
## De ce selfhost?
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Now, let's talk about why I chose to self-host this blog. In a nutshell, self-hosting gave me:
|
Am inceput sa fac hosting acasa din cateva motive:
|
||||||
|
|
||||||
- **Full control**: By hosting my own website, I have complete control over my content and how it's displayed. No more compromises or limitations imposed by third-party platforms.
|
- **Detin controlul**: Nu depind de cloud providers sau alte 3rd parties (inafara de VPS).
|
||||||
- **Owning my data**: It's just, that I can have control over my data without others snooping around.
|
- **Nu exista scurgeri de informatii**: Sunt unele lucruri pe care nu as vrea sa le impartasesc cu marii provideri de servicii cloud.
|
||||||
- **It's fun**: Started looking into sysadmin/devops for a long time, after a burnout I stepped into selfhosting more convincingly.
|
- **E destul de smecher**: Este destul de tare sa vezi cum datele ruleaza pe hardwareul de la tine din casa.
|
||||||
|
|
||||||
## What to expect
|
## Ce este aici de fapt
|
||||||
|
|
||||||
As I mentioned earlier, this blog will be a mix of tech tutorials, personal thoughts, and everything in between. Here's what you can look forward to:
|
E un blog, o jurnalizare e ceea ce fac eu, ma ajuta sa tin evidenta cand explica lucruri.
|
||||||
|
|
||||||
- **Tech how-tos**: Step-by-step guides on various topics, from setting up your own development environment to configuring your server.
|
- **Resurse tehnice**: Ghiduri pas cu pas despre diverse subiecte, de la configurarea propriului mediului de dezvoltare până la ajustarea serverului.
|
||||||
- **Self-hosting adventures**: My experiences, learnings, and tips on self-hosting, including challenges faced and solutions implemented.
|
- **Experiențe personale cu selfhosting**: Ce realizat, cum am solutionat, provocari ...
|
||||||
- **Random musings**: Thoughts on productivity, mental health, and other interests of mine that might not be directly related to tech.
|
- **Gânduri aleatorii**: Gânduri despre eficiența profesională, sănătatea mintală și alte interese personale care nu sunt direct legate de tehnologie.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
This guide documents the configuration for build-time environment variables in the Next.js CI/CD pipeline.
|
This guide documents the configuration for build-time environment variables in the Next.js CI/CD pipeline.
|
||||||
|
|
||||||
**Problem Solved:** Next.js 16 requires `NEXT_PUBLIC_*` variables available during `npm run build` for:
|
**Problem Solved:** Next.js 16 requires `NEXT_PUBLIC_*` variables available during `npm run build` for:
|
||||||
|
|
||||||
- SEO metadata (`metadataBase`)
|
- SEO metadata (`metadataBase`)
|
||||||
- Sitemap generation
|
- Sitemap generation
|
||||||
- OpenGraph URLs
|
- OpenGraph URLs
|
||||||
@@ -19,6 +20,7 @@ This guide documents the configuration for build-time environment variables in t
|
|||||||
### 1. `.gitea/workflows/main.yml`
|
### 1. `.gitea/workflows/main.yml`
|
||||||
|
|
||||||
**Changes:**
|
**Changes:**
|
||||||
|
|
||||||
- Added step to create `.env` from Gitea secrets (after checkout)
|
- Added step to create `.env` from Gitea secrets (after checkout)
|
||||||
- Added cleanup step to remove `.env` after Docker push
|
- Added cleanup step to remove `.env` after Docker push
|
||||||
|
|
||||||
@@ -55,6 +57,7 @@ This guide documents the configuration for build-time environment variables in t
|
|||||||
### 2. `Dockerfile.nextjs`
|
### 2. `Dockerfile.nextjs`
|
||||||
|
|
||||||
**Changes:**
|
**Changes:**
|
||||||
|
|
||||||
- Added `COPY .env* ./` in builder stage (after copying node_modules, before copying source code)
|
- Added `COPY .env* ./` in builder stage (after copying node_modules, before copying source code)
|
||||||
|
|
||||||
**Added Section:**
|
**Added Section:**
|
||||||
@@ -73,6 +76,7 @@ COPY .env* ./
|
|||||||
### 3. `.dockerignore`
|
### 3. `.dockerignore`
|
||||||
|
|
||||||
**Changes:**
|
**Changes:**
|
||||||
|
|
||||||
- Modified to allow `.env` file (created by CI/CD) while excluding other `.env*` files
|
- Modified to allow `.env` file (created by CI/CD) while excluding other `.env*` files
|
||||||
|
|
||||||
**Updated Section:**
|
**Updated Section:**
|
||||||
@@ -85,6 +89,7 @@ COPY .env* ./
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Explanation:**
|
**Explanation:**
|
||||||
|
|
||||||
- `.env*` excludes all environment files
|
- `.env*` excludes all environment files
|
||||||
- `!.env` creates exception for main `.env` (from CI/CD)
|
- `!.env` creates exception for main `.env` (from CI/CD)
|
||||||
- `.env.local`, `.env.development`, `.env.production.local` remain excluded
|
- `.env.local`, `.env.development`, `.env.production.local` remain excluded
|
||||||
@@ -100,10 +105,11 @@ Navigate to: **Repository Settings → Secrets**
|
|||||||
Add the following secret:
|
Add the following secret:
|
||||||
|
|
||||||
| Secret Name | Value | Type | Description |
|
| Secret Name | Value | Type | Description |
|
||||||
|------------|-------|------|-------------|
|
| ---------------------- | ------------------------ | ------------------ | ------------------- |
|
||||||
| `NEXT_PUBLIC_SITE_URL` | `https://yourdomain.com` | Secret or Variable | Production site URL |
|
| `NEXT_PUBLIC_SITE_URL` | `https://yourdomain.com` | Secret or Variable | Production site URL |
|
||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
|
|
||||||
- Can be configured as **Secret** (masked in logs) or **Variable** (visible in logs)
|
- Can be configured as **Secret** (masked in logs) or **Variable** (visible in logs)
|
||||||
- Recommended: Use **Variable** since it's a public URL
|
- Recommended: Use **Variable** since it's a public URL
|
||||||
- For sensitive values (API keys), always use **Secret**
|
- For sensitive values (API keys), always use **Secret**
|
||||||
@@ -113,12 +119,14 @@ Add the following secret:
|
|||||||
To add more build-time variables:
|
To add more build-time variables:
|
||||||
|
|
||||||
1. **Add to Gitea Secrets/Variables:**
|
1. **Add to Gitea Secrets/Variables:**
|
||||||
|
|
||||||
```
|
```
|
||||||
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
|
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
|
||||||
NEXT_PUBLIC_API_URL=https://api.example.com
|
NEXT_PUBLIC_API_URL=https://api.example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Update workflow `.env` creation step:**
|
2. **Update workflow `.env` creation step:**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
cat > .env << EOF
|
cat > .env << EOF
|
||||||
NEXT_PUBLIC_SITE_URL=${{ secrets.NEXT_PUBLIC_SITE_URL }}
|
NEXT_PUBLIC_SITE_URL=${{ secrets.NEXT_PUBLIC_SITE_URL }}
|
||||||
@@ -138,6 +146,7 @@ To add more build-time variables:
|
|||||||
### Local Testing
|
### Local Testing
|
||||||
|
|
||||||
1. **Create test `.env` file:**
|
1. **Create test `.env` file:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cat > .env << EOF
|
cat > .env << EOF
|
||||||
NEXT_PUBLIC_SITE_URL=http://localhost:3030
|
NEXT_PUBLIC_SITE_URL=http://localhost:3030
|
||||||
@@ -147,11 +156,13 @@ To add more build-time variables:
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. **Build Docker image:**
|
2. **Build Docker image:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t mypage:test -f Dockerfile.nextjs .
|
docker build -t mypage:test -f Dockerfile.nextjs .
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Verify variable is embedded (should show "NOT FOUND" - correct behavior):**
|
3. **Verify variable is embedded (should show "NOT FOUND" - correct behavior):**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run --rm mypage:test node -e "console.log(process.env.NEXT_PUBLIC_SITE_URL || 'NOT FOUND')"
|
docker run --rm mypage:test node -e "console.log(process.env.NEXT_PUBLIC_SITE_URL || 'NOT FOUND')"
|
||||||
```
|
```
|
||||||
@@ -161,6 +172,7 @@ To add more build-time variables:
|
|||||||
**Why?** `NEXT_PUBLIC_*` variables are embedded in JavaScript bundle during build, NOT available as runtime environment variables.
|
**Why?** `NEXT_PUBLIC_*` variables are embedded in JavaScript bundle during build, NOT available as runtime environment variables.
|
||||||
|
|
||||||
4. **Test application starts:**
|
4. **Test application starts:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run --rm -p 3030:3030 mypage:test
|
docker run --rm -p 3030:3030 mypage:test
|
||||||
```
|
```
|
||||||
@@ -215,7 +227,7 @@ To add more build-time variables:
|
|||||||
### 🔒 Sensitive Data Guidelines
|
### 🔒 Sensitive Data Guidelines
|
||||||
|
|
||||||
| Type | Use For | Access |
|
| Type | Use For | Access |
|
||||||
|------|---------|--------|
|
| --------------- | -------------------------------------------- | ------------------------------ |
|
||||||
| `NEXT_PUBLIC_*` | Client-side config (URLs, feature flags) | Public (embedded in JS bundle) |
|
| `NEXT_PUBLIC_*` | Client-side config (URLs, feature flags) | Public (embedded in JS bundle) |
|
||||||
| `SECRET_*` | Server-side secrets (API keys, DB passwords) | Private (runtime only) |
|
| `SECRET_*` | Server-side secrets (API keys, DB passwords) | Private (runtime only) |
|
||||||
|
|
||||||
@@ -226,10 +238,12 @@ To add more build-time variables:
|
|||||||
### Issue: Variables not available during build
|
### Issue: Variables not available during build
|
||||||
|
|
||||||
**Symptoms:**
|
**Symptoms:**
|
||||||
|
|
||||||
- Next.js build errors about missing `NEXT_PUBLIC_SITE_URL`
|
- Next.js build errors about missing `NEXT_PUBLIC_SITE_URL`
|
||||||
- Metadata/sitemap generation fails
|
- Metadata/sitemap generation fails
|
||||||
|
|
||||||
**Solution:**
|
**Solution:**
|
||||||
|
|
||||||
- Verify `NEXT_PUBLIC_SITE_URL` secret exists in Gitea
|
- Verify `NEXT_PUBLIC_SITE_URL` secret exists in Gitea
|
||||||
- Check workflow logs for `.env` creation step
|
- Check workflow logs for `.env` creation step
|
||||||
- Ensure `.env` file is created BEFORE Docker build
|
- Ensure `.env` file is created BEFORE Docker build
|
||||||
@@ -237,9 +251,11 @@ To add more build-time variables:
|
|||||||
### Issue: Variables not working in application
|
### Issue: Variables not working in application
|
||||||
|
|
||||||
**Symptoms:**
|
**Symptoms:**
|
||||||
|
|
||||||
- URLs show as `undefined` or `null` in production
|
- URLs show as `undefined` or `null` in production
|
||||||
|
|
||||||
**Diagnosis:**
|
**Diagnosis:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check if variable is in bundle (should work):
|
# Check if variable is in bundle (should work):
|
||||||
curl https://yourdomain.com | grep -o 'NEXT_PUBLIC_SITE_URL'
|
curl https://yourdomain.com | grep -o 'NEXT_PUBLIC_SITE_URL'
|
||||||
@@ -249,6 +265,7 @@ docker exec mypage-prod node -e "console.log(process.env.NEXT_PUBLIC_SITE_URL)"
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Solution:**
|
**Solution:**
|
||||||
|
|
||||||
- Verify `.env` was copied during Docker build
|
- Verify `.env` was copied during Docker build
|
||||||
- Check Dockerfile logs for `COPY .env* ./` step
|
- Check Dockerfile logs for `COPY .env* ./` step
|
||||||
- Rebuild with `--no-cache` if needed
|
- Rebuild with `--no-cache` if needed
|
||||||
@@ -256,9 +273,11 @@ docker exec mypage-prod node -e "console.log(process.env.NEXT_PUBLIC_SITE_URL)"
|
|||||||
### Issue: `.env` file not found during Docker build
|
### Issue: `.env` file not found during Docker build
|
||||||
|
|
||||||
**Symptoms:**
|
**Symptoms:**
|
||||||
|
|
||||||
- Docker build warning: `COPY .env* ./` - no files matched
|
- Docker build warning: `COPY .env* ./` - no files matched
|
||||||
|
|
||||||
**Solution:**
|
**Solution:**
|
||||||
|
|
||||||
- Check `.dockerignore` allows `.env` file
|
- Check `.dockerignore` allows `.env` file
|
||||||
- Verify workflow creates `.env` BEFORE Docker build
|
- Verify workflow creates `.env` BEFORE Docker build
|
||||||
- Check file exists: `ls -la .env` in workflow
|
- Check file exists: `ls -la .env` in workflow
|
||||||
@@ -289,6 +308,7 @@ After deploying changes:
|
|||||||
## Support
|
## Support
|
||||||
|
|
||||||
For issues or questions:
|
For issues or questions:
|
||||||
|
|
||||||
1. Check workflow logs in Gitea Actions
|
1. Check workflow logs in Gitea Actions
|
||||||
2. Review Docker build logs
|
2. Review Docker build logs
|
||||||
3. Verify Gitea secrets configuration
|
3. Verify Gitea secrets configuration
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# Production Optimizations Report
|
# Production Optimizations Report
|
||||||
|
|
||||||
Date: 2025-11-24
|
Date: 2025-11-24
|
||||||
Branch: feat/production-improvements
|
Branch: feat/production-improvements
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ Branch: feat/production-improvements
|
|||||||
Successfully implemented 7 categories of production optimizations for Next.js 16 blog application.
|
Successfully implemented 7 categories of production optimizations for Next.js 16 blog application.
|
||||||
|
|
||||||
### Build Status: SUCCESS
|
### Build Status: SUCCESS
|
||||||
|
|
||||||
- Build Time: ~3.9s compilation + ~1.5s static generation
|
- Build Time: ~3.9s compilation + ~1.5s static generation
|
||||||
- Static Pages Generated: 19 pages
|
- Static Pages Generated: 19 pages
|
||||||
- Bundle Size: 1.2MB (static assets)
|
- Bundle Size: 1.2MB (static assets)
|
||||||
@@ -17,10 +19,12 @@ Successfully implemented 7 categories of production optimizations for Next.js 16
|
|||||||
## 1. Bundle Size Optimization - Remove Unused Dependencies
|
## 1. Bundle Size Optimization - Remove Unused Dependencies
|
||||||
|
|
||||||
### Actions Taken:
|
### Actions Taken:
|
||||||
|
|
||||||
- Removed `react-syntax-highlighter` (11 packages eliminated)
|
- Removed `react-syntax-highlighter` (11 packages eliminated)
|
||||||
- Removed `@types/react-syntax-highlighter`
|
- Removed `@types/react-syntax-highlighter`
|
||||||
|
|
||||||
### Impact:
|
### Impact:
|
||||||
|
|
||||||
- **11 packages removed** from dependency tree
|
- **11 packages removed** from dependency tree
|
||||||
- Cleaner bundle, faster npm installs
|
- Cleaner bundle, faster npm installs
|
||||||
- All remaining dependencies verified as actively used
|
- All remaining dependencies verified as actively used
|
||||||
@@ -30,11 +34,13 @@ Successfully implemented 7 categories of production optimizations for Next.js 16
|
|||||||
## 2. Lazy Loading for Heavy Components
|
## 2. Lazy Loading for Heavy Components
|
||||||
|
|
||||||
### Status:
|
### Status:
|
||||||
|
|
||||||
- Attempted to implement dynamic imports for CodeBlock component
|
- Attempted to implement dynamic imports for CodeBlock component
|
||||||
- Tool limitations prevented full implementation
|
- Tool limitations prevented full implementation
|
||||||
- Benefit would be minimal (CodeBlock already client-side rendered)
|
- Benefit would be minimal (CodeBlock already client-side rendered)
|
||||||
|
|
||||||
### Recommendation:
|
### Recommendation:
|
||||||
|
|
||||||
- Consider manual lazy loading in future if CodeBlock becomes heavier
|
- Consider manual lazy loading in future if CodeBlock becomes heavier
|
||||||
- Current implementation is already performant
|
- Current implementation is already performant
|
||||||
|
|
||||||
@@ -45,16 +51,19 @@ Successfully implemented 7 categories of production optimizations for Next.js 16
|
|||||||
### Security Enhancements Applied:
|
### Security Enhancements Applied:
|
||||||
|
|
||||||
**Dockerfile.nextjs:**
|
**Dockerfile.nextjs:**
|
||||||
|
|
||||||
- Remove SUID/SGID binaries (prevent privilege escalation)
|
- Remove SUID/SGID binaries (prevent privilege escalation)
|
||||||
- Remove apk package manager after dependencies installed
|
- Remove apk package manager after dependencies installed
|
||||||
- Create proper permissions for /tmp, /.next/cache, /app/logs directories
|
- Create proper permissions for /tmp, /.next/cache, /app/logs directories
|
||||||
|
|
||||||
**docker-compose.prod.yml:**
|
**docker-compose.prod.yml:**
|
||||||
|
|
||||||
- Added `security_opt: no-new-privileges:true`
|
- Added `security_opt: no-new-privileges:true`
|
||||||
- Added commented read-only filesystem option (optional hardening)
|
- Added commented read-only filesystem option (optional hardening)
|
||||||
- Documented tmpfs mounts for extra security
|
- Documented tmpfs mounts for extra security
|
||||||
|
|
||||||
### Security Posture:
|
### Security Posture:
|
||||||
|
|
||||||
- Minimal attack surface in production container
|
- Minimal attack surface in production container
|
||||||
- Non-root user execution enforced
|
- Non-root user execution enforced
|
||||||
- Package manager unavailable at runtime
|
- Package manager unavailable at runtime
|
||||||
@@ -66,22 +75,26 @@ Successfully implemented 7 categories of production optimizations for Next.js 16
|
|||||||
### Files Created:
|
### Files Created:
|
||||||
|
|
||||||
**app/sitemap.ts:**
|
**app/sitemap.ts:**
|
||||||
|
|
||||||
- Dynamic sitemap generation from markdown posts
|
- Dynamic sitemap generation from markdown posts
|
||||||
- Static pages included (/, /blog, /about)
|
- Static pages included (/, /blog, /about)
|
||||||
- Posts include lastModified date from frontmatter
|
- Posts include lastModified date from frontmatter
|
||||||
- Priority and changeFrequency configured
|
- Priority and changeFrequency configured
|
||||||
|
|
||||||
**app/robots.ts:**
|
**app/robots.ts:**
|
||||||
|
|
||||||
- Allows all search engines
|
- Allows all search engines
|
||||||
- Disallows /api/, /_next/, /admin/
|
- Disallows /api/, /\_next/, /admin/
|
||||||
- References sitemap.xml
|
- References sitemap.xml
|
||||||
|
|
||||||
**app/feed.xml/route.ts:**
|
**app/feed.xml/route.ts:**
|
||||||
|
|
||||||
- RSS 2.0 feed for latest 20 posts
|
- RSS 2.0 feed for latest 20 posts
|
||||||
- Includes title, description, author, pubDate
|
- Includes title, description, author, pubDate
|
||||||
- Proper content-type and cache headers
|
- Proper content-type and cache headers
|
||||||
|
|
||||||
### SEO Impact:
|
### SEO Impact:
|
||||||
|
|
||||||
- Search engines can discover all content via sitemap
|
- Search engines can discover all content via sitemap
|
||||||
- RSS feed for blog subscribers
|
- RSS feed for blog subscribers
|
||||||
- Proper robots.txt prevents indexing of internal routes
|
- Proper robots.txt prevents indexing of internal routes
|
||||||
@@ -93,16 +106,19 @@ Successfully implemented 7 categories of production optimizations for Next.js 16
|
|||||||
### Configuration Updates:
|
### Configuration Updates:
|
||||||
|
|
||||||
**Sharp:**
|
**Sharp:**
|
||||||
|
|
||||||
- Already installed (production-grade image optimizer)
|
- Already installed (production-grade image optimizer)
|
||||||
- Faster than default Next.js image optimizer
|
- Faster than default Next.js image optimizer
|
||||||
|
|
||||||
**next.config.js - Image Settings:**
|
**next.config.js - Image Settings:**
|
||||||
|
|
||||||
- Cache optimized images for 30 days (`minimumCacheTTL`)
|
- Cache optimized images for 30 days (`minimumCacheTTL`)
|
||||||
- Support AVIF and WebP formats
|
- Support AVIF and WebP formats
|
||||||
- SVG rendering enabled with security CSP
|
- SVG rendering enabled with security CSP
|
||||||
- Responsive image sizes configured (640px to 3840px)
|
- Responsive image sizes configured (640px to 3840px)
|
||||||
|
|
||||||
### Performance Impact:
|
### Performance Impact:
|
||||||
|
|
||||||
- Faster image processing during builds
|
- Faster image processing during builds
|
||||||
- Smaller image file sizes (AVIF/WebP)
|
- Smaller image file sizes (AVIF/WebP)
|
||||||
- Better Core Web Vitals (LCP, CLS)
|
- Better Core Web Vitals (LCP, CLS)
|
||||||
@@ -113,21 +129,25 @@ Successfully implemented 7 categories of production optimizations for Next.js 16
|
|||||||
|
|
||||||
### Cache Headers Added:
|
### Cache Headers Added:
|
||||||
|
|
||||||
**Static Assets (/_next/static/*):**
|
**Static Assets (/\_next/static/\*):**
|
||||||
|
|
||||||
- `Cache-Control: public, max-age=31536000, immutable`
|
- `Cache-Control: public, max-age=31536000, immutable`
|
||||||
- 1 year cache for versioned assets
|
- 1 year cache for versioned assets
|
||||||
|
|
||||||
**Images (/images/*):**
|
**Images (/images/\*):**
|
||||||
|
|
||||||
- `Cache-Control: public, max-age=31536000, immutable`
|
- `Cache-Control: public, max-age=31536000, immutable`
|
||||||
|
|
||||||
### Experimental Features Enabled:
|
### Experimental Features Enabled:
|
||||||
|
|
||||||
**next.config.js - experimental:**
|
**next.config.js - experimental:**
|
||||||
|
|
||||||
- `staleTimes.dynamic: 30s` (client-side cache for dynamic pages)
|
- `staleTimes.dynamic: 30s` (client-side cache for dynamic pages)
|
||||||
- `staleTimes.static: 180s` (client-side cache for static pages)
|
- `staleTimes.static: 180s` (client-side cache for static pages)
|
||||||
- `optimizePackageImports` for react-markdown ecosystem
|
- `optimizePackageImports` for react-markdown ecosystem
|
||||||
|
|
||||||
### Performance Impact:
|
### Performance Impact:
|
||||||
|
|
||||||
- Reduced bandwidth usage
|
- Reduced bandwidth usage
|
||||||
- Faster repeat visits (cached assets)
|
- Faster repeat visits (cached assets)
|
||||||
- Improved navigation speed (stale-while-revalidate)
|
- Improved navigation speed (stale-while-revalidate)
|
||||||
@@ -137,18 +157,22 @@ Successfully implemented 7 categories of production optimizations for Next.js 16
|
|||||||
## 7. Bundle Analyzer Setup
|
## 7. Bundle Analyzer Setup
|
||||||
|
|
||||||
### Tools Installed:
|
### Tools Installed:
|
||||||
|
|
||||||
- `@next/bundle-analyzer` (16.0.3)
|
- `@next/bundle-analyzer` (16.0.3)
|
||||||
|
|
||||||
### NPM Scripts Added:
|
### NPM Scripts Added:
|
||||||
|
|
||||||
- `npm run analyze` - Full bundle analysis
|
- `npm run analyze` - Full bundle analysis
|
||||||
- `npm run analyze:server` - Server bundle only
|
- `npm run analyze:server` - Server bundle only
|
||||||
- `npm run analyze:browser` - Browser bundle only
|
- `npm run analyze:browser` - Browser bundle only
|
||||||
|
|
||||||
### Configuration:
|
### Configuration:
|
||||||
|
|
||||||
- `next.config.analyzer.js` created
|
- `next.config.analyzer.js` created
|
||||||
- Enabled with `ANALYZE=true` environment variable
|
- Enabled with `ANALYZE=true` environment variable
|
||||||
|
|
||||||
### Usage:
|
### Usage:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run analyze
|
npm run analyze
|
||||||
# Opens browser with bundle visualization
|
# Opens browser with bundle visualization
|
||||||
@@ -160,6 +184,7 @@ npm run analyze
|
|||||||
## Bundle Size Analysis
|
## Bundle Size Analysis
|
||||||
|
|
||||||
### Static Assets:
|
### Static Assets:
|
||||||
|
|
||||||
```
|
```
|
||||||
Total Static: 1.2MB
|
Total Static: 1.2MB
|
||||||
- Largest chunks:
|
- Largest chunks:
|
||||||
@@ -170,10 +195,12 @@ Total Static: 1.2MB
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Standalone Output:
|
### Standalone Output:
|
||||||
|
|
||||||
- Total: 44MB (includes Node.js runtime, dependencies, server)
|
- Total: 44MB (includes Node.js runtime, dependencies, server)
|
||||||
- Expected Docker image size: ~150MB (Alpine + Node.js + app)
|
- Expected Docker image size: ~150MB (Alpine + Node.js + app)
|
||||||
|
|
||||||
### Bundle Composition:
|
### Bundle Composition:
|
||||||
|
|
||||||
- React + React-DOM: Largest dependencies
|
- React + React-DOM: Largest dependencies
|
||||||
- react-markdown ecosystem: Second largest
|
- react-markdown ecosystem: Second largest
|
||||||
- Next.js framework: Optimized with tree-shaking
|
- Next.js framework: Optimized with tree-shaking
|
||||||
@@ -183,6 +210,7 @@ Total Static: 1.2MB
|
|||||||
## Build Verification
|
## Build Verification
|
||||||
|
|
||||||
### Build Output:
|
### Build Output:
|
||||||
|
|
||||||
```
|
```
|
||||||
Creating an optimized production build ...
|
Creating an optimized production build ...
|
||||||
✓ Compiled successfully in 3.9s
|
✓ Compiled successfully in 3.9s
|
||||||
@@ -200,6 +228,7 @@ Route (app)
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Pre-rendered Pages:
|
### Pre-rendered Pages:
|
||||||
|
|
||||||
- 19 static pages generated
|
- 19 static pages generated
|
||||||
- 3 blog posts
|
- 3 blog posts
|
||||||
- 7 tag pages
|
- 7 tag pages
|
||||||
@@ -210,6 +239,7 @@ Route (app)
|
|||||||
## Files Modified/Created
|
## Files Modified/Created
|
||||||
|
|
||||||
### Modified:
|
### Modified:
|
||||||
|
|
||||||
- `Dockerfile.nextjs` (security hardening)
|
- `Dockerfile.nextjs` (security hardening)
|
||||||
- `docker-compose.prod.yml` (security options)
|
- `docker-compose.prod.yml` (security options)
|
||||||
- `next.config.js` (image optimization, caching headers)
|
- `next.config.js` (image optimization, caching headers)
|
||||||
@@ -217,6 +247,7 @@ Route (app)
|
|||||||
- `package-lock.json` (dependency updates)
|
- `package-lock.json` (dependency updates)
|
||||||
|
|
||||||
### Created:
|
### Created:
|
||||||
|
|
||||||
- `app/sitemap.ts` (dynamic sitemap)
|
- `app/sitemap.ts` (dynamic sitemap)
|
||||||
- `app/robots.ts` (robots.txt)
|
- `app/robots.ts` (robots.txt)
|
||||||
- `app/feed.xml/route.ts` (RSS feed)
|
- `app/feed.xml/route.ts` (RSS feed)
|
||||||
@@ -227,6 +258,7 @@ Route (app)
|
|||||||
## Performance Recommendations
|
## Performance Recommendations
|
||||||
|
|
||||||
### Implemented:
|
### Implemented:
|
||||||
|
|
||||||
1. Bundle size reduced (11 packages removed)
|
1. Bundle size reduced (11 packages removed)
|
||||||
2. Security hardened (Docker + CSP)
|
2. Security hardened (Docker + CSP)
|
||||||
3. SEO optimized (sitemap + robots + RSS)
|
3. SEO optimized (sitemap + robots + RSS)
|
||||||
@@ -235,7 +267,8 @@ Route (app)
|
|||||||
6. Bundle analyzer ready for monitoring
|
6. Bundle analyzer ready for monitoring
|
||||||
|
|
||||||
### Future Optimizations:
|
### Future Optimizations:
|
||||||
1. Consider CDN for static assets (/images, /_next/static)
|
|
||||||
|
1. Consider CDN for static assets (/images, /\_next/static)
|
||||||
2. Monitor bundle sizes with `npm run analyze` on each release
|
2. Monitor bundle sizes with `npm run analyze` on each release
|
||||||
3. Add bundle size limits in CI/CD (fail if > threshold)
|
3. Add bundle size limits in CI/CD (fail if > threshold)
|
||||||
4. Consider Edge deployment for global performance
|
4. Consider Edge deployment for global performance
|
||||||
@@ -246,6 +279,7 @@ Route (app)
|
|||||||
## Production Deployment Checklist
|
## Production Deployment Checklist
|
||||||
|
|
||||||
Before deploying:
|
Before deploying:
|
||||||
|
|
||||||
- [ ] Set `NEXT_PUBLIC_SITE_URL` in production environment
|
- [ ] Set `NEXT_PUBLIC_SITE_URL` in production environment
|
||||||
- [ ] Verify Caddy reverse proxy configuration
|
- [ ] Verify Caddy reverse proxy configuration
|
||||||
- [ ] Test Docker build: `npm run docker:build`
|
- [ ] Test Docker build: `npm run docker:build`
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default [
|
|||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
'warn',
|
'warn',
|
||||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
{ argsIgnorePattern: '^_|node', varsIgnorePattern: '^_' },
|
||||||
],
|
],
|
||||||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||||
},
|
},
|
||||||
@@ -26,6 +26,7 @@ export default [
|
|||||||
'dist/',
|
'dist/',
|
||||||
'.cache/',
|
'.cache/',
|
||||||
'*.config.js',
|
'*.config.js',
|
||||||
|
'next.config.analyzer.js',
|
||||||
'public/',
|
'public/',
|
||||||
'coverage/',
|
'coverage/',
|
||||||
],
|
],
|
||||||
|
|||||||
11
fix.js
11
fix.js
@@ -1,11 +0,0 @@
|
|||||||
const fs = require('fs')
|
|
||||||
let content = fs.readFileSync('lib/remark-copy-images.ts', 'utf8')
|
|
||||||
const lines = content.split('\n')
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
if (lines[i].includes('replace')) {
|
|
||||||
console.log(`Line ${i + 1}:`, JSON.stringify(lines[i]))
|
|
||||||
lines[i] = lines[i].replace(/replace\(\/\\/g / g, 'replace(/\\/g')
|
|
||||||
console.log(`Fixed:`, JSON.stringify(lines[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fs.writeFileSync('lib/remark-copy-images.ts', lines.join('\n'))
|
|
||||||
@@ -3,16 +3,9 @@
|
|||||||
* Ensures all required environment variables are set before deployment
|
* Ensures all required environment variables are set before deployment
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const requiredEnvVars = [
|
const requiredEnvVars = ['NEXT_PUBLIC_SITE_URL', 'NODE_ENV'] as const
|
||||||
'NEXT_PUBLIC_SITE_URL',
|
|
||||||
'NODE_ENV',
|
|
||||||
] as const
|
|
||||||
|
|
||||||
const optionalEnvVars = [
|
const _optionalEnvVars = ['PORT', 'HOSTNAME', 'NEXT_PUBLIC_GA_ID'] as const
|
||||||
'PORT',
|
|
||||||
'HOSTNAME',
|
|
||||||
'NEXT_PUBLIC_GA_ID',
|
|
||||||
] as const
|
|
||||||
|
|
||||||
export function validateEnvironment() {
|
export function validateEnvironment() {
|
||||||
const missingVars: string[] = []
|
const missingVars: string[] = []
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ async function copyAndRewritePath(node: ImageNode, options: Options): Promise<vo
|
|||||||
node.url = publicUrl + queryParams
|
node.url = publicUrl + queryParams
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Stat failed, proceed with copy
|
// Stat failed, proceed with copy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
lib/tags.ts
10
lib/tags.ts
@@ -65,7 +65,11 @@ export async function getPopularTags(locale: string = 'en', limit = 10): Promise
|
|||||||
return allTags.slice(0, limit)
|
return allTags.slice(0, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRelatedTags(tagSlug: string, locale: string = 'en', limit = 5): Promise<TagInfo[]> {
|
export async function getRelatedTags(
|
||||||
|
tagSlug: string,
|
||||||
|
locale: string = 'en',
|
||||||
|
limit = 5
|
||||||
|
): Promise<TagInfo[]> {
|
||||||
const posts = await getPostsByTag(tagSlug, locale)
|
const posts = await getPostsByTag(tagSlug, locale)
|
||||||
const relatedTagMap = new Map<string, number>()
|
const relatedTagMap = new Map<string, number>()
|
||||||
|
|
||||||
@@ -107,7 +111,9 @@ export function validateTags(tags: any): string[] {
|
|||||||
return validTags
|
return validTags
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTagCloud(locale: string = 'en'): Promise<Array<TagInfo & { size: 'sm' | 'md' | 'lg' | 'xl' }>> {
|
export async function getTagCloud(
|
||||||
|
locale: string = 'en'
|
||||||
|
): Promise<Array<TagInfo & { size: 'sm' | 'md' | 'lg' | 'xl' }>> {
|
||||||
const tags = await getAllTags(locale)
|
const tags = await getAllTags(locale)
|
||||||
if (tags.length === 0) return []
|
if (tags.length === 0) return []
|
||||||
|
|
||||||
|
|||||||
@@ -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,35 @@
|
|||||||
"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": {
|
||||||
|
"terminalVersion": "TERMINAL:// V2.0",
|
||||||
|
"documentLevel": "DOCUMENT LEVEL-1 //",
|
||||||
|
"heroTitle": "BUILD. WRITE. SHARE.",
|
||||||
|
"heroSubtitle": "> Explore ideas",
|
||||||
|
"checkPostsButton": "[CHECK POSTS]",
|
||||||
|
"aboutMeButton": "[ABOUT ME]",
|
||||||
|
"recentEntriesLabel": "ARCHIVE ACCESS // RECENT ENTRIES",
|
||||||
|
"recentEntriesTitle": "> RECENT ENTRIES",
|
||||||
|
"fileLabel": "FILE#{number} // {category}",
|
||||||
|
"accessButton": "[ACCESS] >>",
|
||||||
|
"seePostsButton": "[SEE POSTS] >>",
|
||||||
|
"seeAllTagsButton": "[SEE ALL TAGS] >>"
|
||||||
},
|
},
|
||||||
|
|
||||||
"BlogListing": {
|
"BlogListing": {
|
||||||
"title": "Blog",
|
"title": "Blog",
|
||||||
"subtitle": "Latest articles and thoughts",
|
"subtitle": "Latest articles and thoughts",
|
||||||
@@ -29,9 +43,10 @@
|
|||||||
"filterByTag": "Filter by tag",
|
"filterByTag": "Filter by tag",
|
||||||
"clearFilters": "Clear filters",
|
"clearFilters": "Clear filters",
|
||||||
"foundPosts": "Found {count} posts",
|
"foundPosts": "Found {count} posts",
|
||||||
"noPosts": "No posts found"
|
"noPosts": "No posts found",
|
||||||
|
"prev": "< PREV",
|
||||||
|
"next": "NEXT >"
|
||||||
},
|
},
|
||||||
|
|
||||||
"BlogPost": {
|
"BlogPost": {
|
||||||
"readMore": "Read more",
|
"readMore": "Read more",
|
||||||
"readingTime": "{minutes} min read",
|
"readingTime": "{minutes} min read",
|
||||||
@@ -41,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",
|
||||||
@@ -50,18 +64,62 @@
|
|||||||
"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",
|
||||||
|
"classificationHeader": ">> _DOC://PUBLIC_ACCESS",
|
||||||
|
"mainTitle": "ABOUT ME_",
|
||||||
|
"introLabel": "STATUS: ACTIVE // ROLE: DAD + DEV",
|
||||||
|
"introParagraph1": "Welcome to my corner of the internet! This is where I share my thoughts, opinions, and experiences - from tech adventures to life as a family man. Yes, I love technology, but there's so much more to life than just code and servers.",
|
||||||
|
"lifeValuesTitle": "> LIFE & VALUES",
|
||||||
|
"familyFirstTitle": "[FAMILY FIRST]",
|
||||||
|
"familyFirstText": "Being a dad to an amazing toddler is my most important role. Family time is sacred - whether it's building block towers, exploring parks, or just enjoying the chaos of everyday life together. Tech can wait; these moments can't.",
|
||||||
|
"activeLifestyleTitle": "[ACTIVE LIFESTYLE]",
|
||||||
|
"activeLifestyleText": "I believe in keeping the body active. Whether it's hitting the gym, playing sports, or just staying on the move - physical activity keeps me sharp, balanced, and ready for whatever life throws my way.",
|
||||||
|
"simpleThingsTitle": "[ENJOYING THE SIMPLE THINGS]",
|
||||||
|
"simpleThingsText": "Life's too short not to enjoy it. A good drink, a relaxing evening after a long day, or just not doing anything a blowing some steam off.",
|
||||||
|
"techPurposeTitle": "[TECH WITH PURPOSE]",
|
||||||
|
"techPurposeText": "Yes, I love tech - self-hosting, privacy, tinkering with hardware. But it's a tool, not a lifestyle. Tech should serve life, not the other way around.",
|
||||||
|
"contentTitle": "> WHAT YOU'LL FIND HERE",
|
||||||
|
"contentSubtitle": "CONTENT SCOPE // EVERYTHING FROM TECH TO LIFE",
|
||||||
|
"contentThoughts": "Thoughts & Opinions",
|
||||||
|
"contentThoughtsDesc": "My take on life, work, and everything in between",
|
||||||
|
"contentLifeFamily": "Life & Family",
|
||||||
|
"contentLifeFamilyDesc": "Adventures in parenting, sports, and enjoying the simple things",
|
||||||
|
"contentTechResearch": "Tech Research",
|
||||||
|
"contentTechResearchDesc": "When I dive into interesting technologies and experiments",
|
||||||
|
"contentSysAdmin": "System Administration",
|
||||||
|
"contentSysAdminDesc": "Self-hosting, infrastructure, and DevOps adventures",
|
||||||
|
"contentDevelopment": "Development Insights",
|
||||||
|
"contentDevelopmentDesc": "Lessons learned from building software",
|
||||||
|
"contentRandom": "Random Stuff",
|
||||||
|
"contentRandomDesc": "Because life doesn't fit into neat categories!",
|
||||||
|
"focusTitle": "> AREAS OF FOCUS",
|
||||||
|
"focusBeingDadTitle": "[BEING A DAD]",
|
||||||
|
"focusBeingDadText": "Playing with my boy, teaching moments, watching him grow, building memories together",
|
||||||
|
"focusStayingActiveTitle": "[STAYING ACTIVE]",
|
||||||
|
"focusStayingActiveText": "Gym sessions, sports, keeping fit, maintaining energy for life's demands",
|
||||||
|
"focusTechnologyTitle": "[TECHNOLOGY & SYSTEMS]",
|
||||||
|
"focusTechnologyText": "Software development, infrastructure, DevOps, self-hosting adventures",
|
||||||
|
"focusLifeBalanceTitle": "[LIFE BALANCE]",
|
||||||
|
"focusLifeBalanceText": "Relaxing with good company, enjoying downtime, appreciating the simple moments",
|
||||||
|
"techStackTitle": "> TECH STACK",
|
||||||
|
"techStackSubtitle": "TOOLS I USE // WHEN NEEDED",
|
||||||
|
"techStackDevelopmentTitle": "[DEVELOPMENT]",
|
||||||
|
"techStackDevelopmentText": ".NET, Golang, TypeScript, Next.js, React",
|
||||||
|
"techStackInfrastructureTitle": "[INFRASTRUCTURE]",
|
||||||
|
"techStackInfrastructureText": "Windows Server, Linux, Docker, Hyper-V",
|
||||||
|
"techStackDesignTitle": "[DESIGN]",
|
||||||
|
"techStackDesignText": "Tailwind CSS, Markdown, Terminal aesthetics",
|
||||||
|
"techStackSelfHostingTitle": "[SELF-HOSTING]",
|
||||||
|
"techStackSelfHostingText": "Home lab, privacy-focused services, full control, Git server",
|
||||||
|
"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"
|
||||||
|
|||||||
@@ -3,21 +3,35 @@
|
|||||||
"siteTitle": "Blog Personal",
|
"siteTitle": "Blog Personal",
|
||||||
"siteDescription": "Gânduri despre tehnologie și dezvoltare"
|
"siteDescription": "Gânduri despre tehnologie și dezvoltare"
|
||||||
},
|
},
|
||||||
|
|
||||||
"Navigation": {
|
"Navigation": {
|
||||||
"home": "Acasă",
|
"home": "Acasă",
|
||||||
"blog": "Blog",
|
"blog": "Blog",
|
||||||
"tags": "Etichete",
|
"tags": "Etichete",
|
||||||
"about": "Despre"
|
"about": "Despre"
|
||||||
},
|
},
|
||||||
|
|
||||||
"Breadcrumbs": {
|
"Breadcrumbs": {
|
||||||
"home": "Acasă",
|
"home": "Acasă",
|
||||||
"blog": "Blog",
|
"blog": "Blog",
|
||||||
"tags": "Etichete",
|
"tags": "Etichete",
|
||||||
"about": "Despre"
|
"about": "Despre",
|
||||||
|
"tech": "Tehnologie",
|
||||||
|
"design": "Design",
|
||||||
|
"tutorial": "Tutoriale"
|
||||||
|
},
|
||||||
|
"Home": {
|
||||||
|
"terminalVersion": "TERMINAL:// V2.0",
|
||||||
|
"documentLevel": "DOCUMENT LEVEL-1 //",
|
||||||
|
"heroTitle": "BUILD. WRITE. SHARE.",
|
||||||
|
"heroSubtitle": "> Explore ideas",
|
||||||
|
"checkPostsButton": "[POSTĂRI]",
|
||||||
|
"aboutMeButton": "[DESPRE]",
|
||||||
|
"recentEntriesLabel": "ARCHIVE ACCESS // RECENT ENTRIES",
|
||||||
|
"recentEntriesTitle": "> RECENT ENTRIES",
|
||||||
|
"fileLabel": "FILE#{number} // {category}",
|
||||||
|
"accessButton": "[ACCESS] >>",
|
||||||
|
"seePostsButton": "[SEE POSTS] >>",
|
||||||
|
"seeAllTagsButton": "[SEE ALL TAGS] >>"
|
||||||
},
|
},
|
||||||
|
|
||||||
"BlogListing": {
|
"BlogListing": {
|
||||||
"title": "Blog",
|
"title": "Blog",
|
||||||
"subtitle": "Ultimele articole și gânduri",
|
"subtitle": "Ultimele articole și gânduri",
|
||||||
@@ -29,9 +43,10 @@
|
|||||||
"filterByTag": "Filtrează după etichetă",
|
"filterByTag": "Filtrează după etichetă",
|
||||||
"clearFilters": "Șterge filtrele",
|
"clearFilters": "Șterge filtrele",
|
||||||
"foundPosts": "{count} articole găsite",
|
"foundPosts": "{count} articole găsite",
|
||||||
"noPosts": "Niciun articol găsit"
|
"noPosts": "Niciun articol găsit",
|
||||||
|
"prev": "< PREV",
|
||||||
|
"next": "NEXT >"
|
||||||
},
|
},
|
||||||
|
|
||||||
"BlogPost": {
|
"BlogPost": {
|
||||||
"readMore": "Citește mai mult",
|
"readMore": "Citește mai mult",
|
||||||
"readingTime": "{minutes} min citire",
|
"readingTime": "{minutes} min citire",
|
||||||
@@ -41,7 +56,6 @@
|
|||||||
"relatedPosts": "Articole similare",
|
"relatedPosts": "Articole similare",
|
||||||
"sharePost": "Distribuie acest articol"
|
"sharePost": "Distribuie acest articol"
|
||||||
},
|
},
|
||||||
|
|
||||||
"Tags": {
|
"Tags": {
|
||||||
"title": "Etichete",
|
"title": "Etichete",
|
||||||
"subtitle": "Navighează după subiect",
|
"subtitle": "Navighează după subiect",
|
||||||
@@ -50,18 +64,62 @@
|
|||||||
"relatedTags": "Etichete similare",
|
"relatedTags": "Etichete similare",
|
||||||
"quickNav": "Navigare rapidă"
|
"quickNav": "Navigare rapidă"
|
||||||
},
|
},
|
||||||
|
|
||||||
"About": {
|
"About": {
|
||||||
"title": "Despre",
|
"title": "Despre",
|
||||||
"subtitle": "Află mai multe despre mine"
|
"subtitle": "Află mai multe despre mine",
|
||||||
|
"classificationHeader": ">> _DOC://PUBLIC_ACCESS",
|
||||||
|
"mainTitle": "DESPRE_",
|
||||||
|
"introLabel": "STATUS: ACTIV // ROL: TATĂ + DEV",
|
||||||
|
"introParagraph1": "Mi-am făcut un colțișor pe internet unde pot să împărtășesc cam tot ce vreau. O să găsești aici și tech, și viață, și haosul de zi cu zi.",
|
||||||
|
"lifeValuesTitle": "> VIAȚĂ & VALORI",
|
||||||
|
"familyFirstTitle": "[FAMILIA PE PRIMUL LOC]",
|
||||||
|
"familyFirstText": "Să fiu tată pentru un puști genial e cel mai important rol al meu. Timpul cu familia e sfânt – fie că construim turnuri din cuburi, explorăm parcuri sau doar trăim frumos haosul de zi cu zi. Tech-ul poate să aștepte, momentele astea nu.",
|
||||||
|
"activeLifestyleTitle": "[STIL DE VIAȚĂ ACTIV]",
|
||||||
|
"activeLifestyleText": "Încerc să-mi țin corpul în mișcare. Sală, sport, orice mă scoate din scaun. Mă ajută să fiu mai clar la minte, mai echilibrat și pregătit de ce aruncă viața în mine.",
|
||||||
|
"simpleThingsTitle": "[BUCURIA LUCRURILOR SIMPLE]",
|
||||||
|
"simpleThingsText": "Viața e prea scurtă să n-o savurezi. O băutură bună, o seară liniștită după o zi grea sau pur și simplu să nu faci nimic și să lași aburii să iasă… și e perfect așa.",
|
||||||
|
"techPurposeTitle": "[TECH CU SENS]",
|
||||||
|
"techPurposeText": "Da, îmi place tehnologia – self-hosting, privacy, joacă cu hardware. Dar pentru mine e o unealtă, nu un stil de viață. Tech-ul ar trebui să lucreze pentru tine, nu tu pentru el.",
|
||||||
|
"contentTitle": "> CE GĂSEȘTI AICI",
|
||||||
|
"contentSubtitle": "CONTENT SCOPE // DE LA TECH LA VIAȚĂ",
|
||||||
|
"contentThoughts": "Gânduri & Opinii",
|
||||||
|
"contentThoughtsDesc": "Cum văd eu viața, munca și tot ce e între ele",
|
||||||
|
"contentLifeFamily": "Viață & Familie",
|
||||||
|
"contentLifeFamilyDesc": "Aventuri de părinte, sport și bucuria lucrurilor mici",
|
||||||
|
"contentTechResearch": "Experimente Tech",
|
||||||
|
"contentTechResearchDesc": "Când mă afund în tehnologii interesante și experimente ciudate",
|
||||||
|
"contentSysAdmin": "Administrare Sisteme",
|
||||||
|
"contentSysAdminDesc": "Self-hosting, infrastructură și aventuri de tip DevOps",
|
||||||
|
"contentDevelopment": "Development Insights",
|
||||||
|
"contentDevelopmentDesc": "Lecții învățate din proiectele pe care le construiesc",
|
||||||
|
"contentRandom": "Chestii Random",
|
||||||
|
"contentRandomDesc": "Pentru că viața nu intră mereu frumos pe categorii!",
|
||||||
|
"focusTitle": "> ZONE DE FOCUS",
|
||||||
|
"focusBeingDadTitle": "[TATĂ ÎN PRIMUL RÂND]",
|
||||||
|
"focusBeingDadText": "Joacă cu băiatul meu, momente de învățat, să-l văd cum crește și să strângem amintiri împreună",
|
||||||
|
"focusStayingActiveTitle": "[SĂ RĂMÂN ACTIV]",
|
||||||
|
"focusStayingActiveText": "Sesiuni la sală, sport, să mă țin în formă și cu energie pentru tot ce am de dus",
|
||||||
|
"focusTechnologyTitle": "[TECH & SISTEME]",
|
||||||
|
"focusTechnologyText": "Dezvoltare software, infrastructură, DevOps, aventuri de self-hosting",
|
||||||
|
"focusLifeBalanceTitle": "[ECHILIBRU ÎN VIAȚĂ]",
|
||||||
|
"focusLifeBalanceText": "Relax cu prietenii, timp de respiro, apreciat momentele simple",
|
||||||
|
"techStackTitle": "> TECH STACK",
|
||||||
|
"techStackSubtitle": "UNELTELE PE CARE LE FOLOSESC // CÂND TREBUIE",
|
||||||
|
"techStackDevelopmentTitle": "[DEVELOPMENT]",
|
||||||
|
"techStackDevelopmentText": ".NET, Golang, TypeScript, Next.js, React",
|
||||||
|
"techStackInfrastructureTitle": "[INFRASTRUCTURĂ]",
|
||||||
|
"techStackInfrastructureText": "Windows Server, Linux, Docker, Hyper-V",
|
||||||
|
"techStackDesignTitle": "[DESIGN]",
|
||||||
|
"techStackDesignText": "Tailwind CSS, Markdown",
|
||||||
|
"techStackSelfHostingTitle": "[SELF-HOSTING]",
|
||||||
|
"techStackSelfHostingText": "Home lab, servicii cu focus pe privacy, control total, server Git",
|
||||||
|
"contactTitle": "> CONTACT"
|
||||||
},
|
},
|
||||||
|
|
||||||
"NotFound": {
|
"NotFound": {
|
||||||
"title": "Pagina nu a fost găsită",
|
"title": "Pagina nu a fost găsită",
|
||||||
"description": "Pagina pe care o cauți nu există",
|
"description": "Pagina pe care o cauți nu există",
|
||||||
"goHome": "Mergi la pagina principală"
|
"goHome": "Mergi la pagina principală"
|
||||||
},
|
},
|
||||||
|
|
||||||
"LanguageSwitcher": {
|
"LanguageSwitcher": {
|
||||||
"switchLanguage": "Schimbă limba",
|
"switchLanguage": "Schimbă limba",
|
||||||
"currentLanguage": "Limba curentă"
|
"currentLanguage": "Limba curentă"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import createMiddleware from 'next-intl/middleware';
|
import createMiddleware from 'next-intl/middleware'
|
||||||
import {routing} from './src/i18n/routing';
|
import { routing } from './src/i18n/routing'
|
||||||
|
|
||||||
export default createMiddleware({
|
export default createMiddleware({
|
||||||
...routing,
|
...routing,
|
||||||
@@ -7,14 +7,10 @@ export default createMiddleware({
|
|||||||
localeCookie: {
|
localeCookie: {
|
||||||
name: 'NEXT_LOCALE',
|
name: 'NEXT_LOCALE',
|
||||||
maxAge: 60 * 60 * 24 * 365,
|
maxAge: 60 * 60 * 24 * 365,
|
||||||
sameSite: 'lax'
|
sameSite: 'lax',
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: [
|
matcher: ['/', '/(en|ro)/:path*', '/((?!api|_next|_vercel|.*\\..*).*)'],
|
||||||
'/',
|
}
|
||||||
'/(en|ro)/:path*',
|
|
||||||
'/((?!api|_next|_vercel|.*\\..*).*)'
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const withNextIntl = require('next-intl/plugin')();
|
const withNextIntl = require('next-intl/plugin')()
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -8,7 +8,6 @@ const withNextIntl = require('next-intl/plugin')();
|
|||||||
// Deprecated options have been removed (swcMinify, reactStrictMode)
|
// Deprecated options have been removed (swcMinify, reactStrictMode)
|
||||||
// SWC minification is now default in Next.js 16
|
// SWC minification is now default in Next.js 16
|
||||||
|
|
||||||
|
|
||||||
// Production-ready Next.js configuration with standalone output
|
// Production-ready Next.js configuration with standalone output
|
||||||
// This configuration is optimized for Docker deployment with minimal image size
|
// This configuration is optimized for Docker deployment with minimal image size
|
||||||
//
|
//
|
||||||
@@ -123,12 +122,7 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Optimize package imports for smaller bundles
|
// Optimize package imports for smaller bundles
|
||||||
optimizePackageImports: [
|
optimizePackageImports: ['react-markdown', 'rehype-raw', 'rehype-sanitize', 'remark-gfm'],
|
||||||
'react-markdown',
|
|
||||||
'rehype-raw',
|
|
||||||
'rehype-sanitize',
|
|
||||||
'remark-gfm',
|
|
||||||
],
|
|
||||||
|
|
||||||
// Enable PPR (Partial Prerendering) - Next.js 16 feature
|
// Enable PPR (Partial Prerendering) - Next.js 16 feature
|
||||||
// Uncomment to enable (currently in beta)
|
// Uncomment to enable (currently in beta)
|
||||||
|
|||||||
379
package-lock.json
generated
379
package-lock.json
generated
@@ -11,16 +11,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.17",
|
"@tailwindcss/postcss": "^4.1.17",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@types/node": "^24.10.0",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.2",
|
"@types/react": "^19.2.7",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.22",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"next": "^16.0.1",
|
"next": "^16.0.7",
|
||||||
"next-intl": "^4.5.7",
|
"next-intl": "^4.5.8",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.1",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.1",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"rehype-sanitize": "^6.0.0",
|
"rehype-sanitize": "^6.0.0",
|
||||||
@@ -32,17 +32,17 @@
|
|||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.3",
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@next/bundle-analyzer": "^16.0.3",
|
"@next/bundle-analyzer": "^16.0.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.46.4",
|
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
||||||
"@typescript-eslint/parser": "^8.46.4",
|
"@typescript-eslint/parser": "^8.48.1",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-next": "^16.0.3",
|
"eslint-config-next": "^16.0.7",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.5.4",
|
"eslint-plugin-prettier": "^5.5.4",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.7.4",
|
||||||
"typescript-eslint": "^8.46.4"
|
"typescript-eslint": "^8.48.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
@@ -453,9 +453,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/eslintrc": {
|
"node_modules/@eslint/eslintrc": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
|
||||||
"integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
|
"integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -465,7 +465,7 @@
|
|||||||
"globals": "^14.0.0",
|
"globals": "^14.0.0",
|
||||||
"ignore": "^5.2.0",
|
"ignore": "^5.2.0",
|
||||||
"import-fresh": "^3.2.1",
|
"import-fresh": "^3.2.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.1",
|
||||||
"minimatch": "^3.1.2",
|
"minimatch": "^3.1.2",
|
||||||
"strip-json-comments": "^3.1.1"
|
"strip-json-comments": "^3.1.1"
|
||||||
},
|
},
|
||||||
@@ -1202,9 +1202,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/bundle-analyzer": {
|
"node_modules/@next/bundle-analyzer": {
|
||||||
"version": "16.0.3",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-16.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-16.0.7.tgz",
|
||||||
"integrity": "sha512-6Xo8f8/ZXtASfTPa6TH1aUn+xDg9Pkyl1YHVxu+89cVdLH7MnYjxv3rPOfEJ9BwCZCU2q4Flyw5MwltfD2pGbA==",
|
"integrity": "sha512-Um2YA3TSQND+DpqlMDuPZsdjdpcgLzo1wF3zx4zcBCLecS7ucP7O9YFqvHhg000HXTgt++KIjZ9FUwyJSKk1Kw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1212,55 +1212,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "16.0.1",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.7.tgz",
|
||||||
"integrity": "sha512-LFvlK0TG2L3fEOX77OC35KowL8D7DlFF45C0OvKMC4hy8c/md1RC4UMNDlUGJqfCoCS2VWrZ4dSE6OjaX5+8mw==",
|
"integrity": "sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/eslint-plugin-next": {
|
"node_modules/@next/eslint-plugin-next": {
|
||||||
"version": "16.0.3",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.0.7.tgz",
|
||||||
"integrity": "sha512-6sPWmZetzFWMsz7Dhuxsdmbu3fK+/AxKRtj7OB0/3OZAI2MHB/v2FeYh271LZ9abvnM1WIwWc/5umYjx0jo5sQ==",
|
"integrity": "sha512-hFrTNZcMEG+k7qxVxZJq3F32Kms130FAhG8lvw2zkKBgAcNOJIxlljNiCjGygvBshvaGBdf88q2CqWtnqezDHA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-glob": "3.3.1"
|
"fast-glob": "3.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/eslint-plugin-next/node_modules/fast-glob": {
|
|
||||||
"version": "3.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
|
|
||||||
"integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@nodelib/fs.stat": "^2.0.2",
|
|
||||||
"@nodelib/fs.walk": "^1.2.3",
|
|
||||||
"glob-parent": "^5.1.2",
|
|
||||||
"merge2": "^1.3.0",
|
|
||||||
"micromatch": "^4.0.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@next/eslint-plugin-next/node_modules/glob-parent": {
|
|
||||||
"version": "5.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
|
||||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"is-glob": "^4.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "16.0.1",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.7.tgz",
|
||||||
"integrity": "sha512-R0YxRp6/4W7yG1nKbfu41bp3d96a0EalonQXiMe+1H9GTHfKxGNCGFNWUho18avRBPsO8T3RmdWuzmfurlQPbg==",
|
"integrity": "sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1274,9 +1244,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "16.0.1",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.7.tgz",
|
||||||
"integrity": "sha512-kETZBocRux3xITiZtOtVoVvXyQLB7VBxN7L6EPqgI5paZiUlnsgYv4q8diTNYeHmF9EiehydOBo20lTttCbHAg==",
|
"integrity": "sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1290,9 +1260,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "16.0.1",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.7.tgz",
|
||||||
"integrity": "sha512-hWg3BtsxQuSKhfe0LunJoqxjO4NEpBmKkE+P2Sroos7yB//OOX3jD5ISP2wv8QdUwtRehMdwYz6VB50mY6hqAg==",
|
"integrity": "sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1306,9 +1276,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "16.0.1",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.7.tgz",
|
||||||
"integrity": "sha512-UPnOvYg+fjAhP3b1iQStcYPWeBFRLrugEyK/lDKGk7kLNua8t5/DvDbAEFotfV1YfcOY6bru76qN9qnjLoyHCQ==",
|
"integrity": "sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1322,9 +1292,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "16.0.1",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.7.tgz",
|
||||||
"integrity": "sha512-Et81SdWkcRqAJziIgFtsFyJizHoWne4fzJkvjd6V4wEkWTB4MX6J0uByUb0peiJQ4WeAt6GGmMszE5KrXK6WKg==",
|
"integrity": "sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1338,9 +1308,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "16.0.1",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.7.tgz",
|
||||||
"integrity": "sha512-qBbgYEBRrC1egcG03FZaVfVxrJm8wBl7vr8UFKplnxNRprctdP26xEv9nJ07Ggq4y1adwa0nz2mz83CELY7N6Q==",
|
"integrity": "sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1354,9 +1324,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "16.0.1",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.7.tgz",
|
||||||
"integrity": "sha512-cPuBjYP6I699/RdbHJonb3BiRNEDm5CKEBuJ6SD8k3oLam2fDRMKAvmrli4QMDgT2ixyRJ0+DTkiODbIQhRkeQ==",
|
"integrity": "sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1370,9 +1340,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "16.0.1",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.7.tgz",
|
||||||
"integrity": "sha512-XeEUJsE4JYtfrXe/LaJn3z1pD19fK0Q6Er8Qoufi+HqvdO4LEPyCxLUt4rxA+4RfYo6S9gMlmzCMU2F+AatFqQ==",
|
"integrity": "sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1991,21 +1961,21 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.10.0",
|
"version": "24.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
|
||||||
"integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==",
|
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.16.0"
|
"undici-types": "~7.16.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.2.2",
|
"version": "19.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
||||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/unist": {
|
"node_modules/@types/unist": {
|
||||||
@@ -2015,17 +1985,17 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.46.4",
|
"version": "8.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.1.tgz",
|
||||||
"integrity": "sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==",
|
"integrity": "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.46.4",
|
"@typescript-eslint/scope-manager": "8.48.1",
|
||||||
"@typescript-eslint/type-utils": "8.46.4",
|
"@typescript-eslint/type-utils": "8.48.1",
|
||||||
"@typescript-eslint/utils": "8.46.4",
|
"@typescript-eslint/utils": "8.48.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.46.4",
|
"@typescript-eslint/visitor-keys": "8.48.1",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^7.0.0",
|
"ignore": "^7.0.0",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@@ -2039,22 +2009,22 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@typescript-eslint/parser": "^8.46.4",
|
"@typescript-eslint/parser": "^8.48.1",
|
||||||
"eslint": "^8.57.0 || ^9.0.0",
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
"typescript": ">=4.8.4 <6.0.0"
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.46.4",
|
"version": "8.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.1.tgz",
|
||||||
"integrity": "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==",
|
"integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.46.4",
|
"@typescript-eslint/scope-manager": "8.48.1",
|
||||||
"@typescript-eslint/types": "8.46.4",
|
"@typescript-eslint/types": "8.48.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.46.4",
|
"@typescript-eslint/typescript-estree": "8.48.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.46.4",
|
"@typescript-eslint/visitor-keys": "8.48.1",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2070,14 +2040,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/project-service": {
|
"node_modules/@typescript-eslint/project-service": {
|
||||||
"version": "8.46.4",
|
"version": "8.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.1.tgz",
|
||||||
"integrity": "sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==",
|
"integrity": "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/tsconfig-utils": "^8.46.4",
|
"@typescript-eslint/tsconfig-utils": "^8.48.1",
|
||||||
"@typescript-eslint/types": "^8.46.4",
|
"@typescript-eslint/types": "^8.48.1",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2092,14 +2062,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.46.4",
|
"version": "8.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz",
|
||||||
"integrity": "sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==",
|
"integrity": "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.46.4",
|
"@typescript-eslint/types": "8.48.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.46.4"
|
"@typescript-eslint/visitor-keys": "8.48.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -2110,9 +2080,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||||
"version": "8.46.4",
|
"version": "8.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.1.tgz",
|
||||||
"integrity": "sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==",
|
"integrity": "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2127,15 +2097,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.46.4",
|
"version": "8.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.1.tgz",
|
||||||
"integrity": "sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==",
|
"integrity": "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.46.4",
|
"@typescript-eslint/types": "8.48.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.46.4",
|
"@typescript-eslint/typescript-estree": "8.48.1",
|
||||||
"@typescript-eslint/utils": "8.46.4",
|
"@typescript-eslint/utils": "8.48.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^2.1.0"
|
"ts-api-utils": "^2.1.0"
|
||||||
},
|
},
|
||||||
@@ -2152,9 +2122,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.46.4",
|
"version": "8.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz",
|
||||||
"integrity": "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==",
|
"integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2166,21 +2136,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.46.4",
|
"version": "8.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz",
|
||||||
"integrity": "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==",
|
"integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/project-service": "8.46.4",
|
"@typescript-eslint/project-service": "8.48.1",
|
||||||
"@typescript-eslint/tsconfig-utils": "8.46.4",
|
"@typescript-eslint/tsconfig-utils": "8.48.1",
|
||||||
"@typescript-eslint/types": "8.46.4",
|
"@typescript-eslint/types": "8.48.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.46.4",
|
"@typescript-eslint/visitor-keys": "8.48.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fast-glob": "^3.3.2",
|
|
||||||
"is-glob": "^4.0.3",
|
|
||||||
"minimatch": "^9.0.4",
|
"minimatch": "^9.0.4",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
|
"tinyglobby": "^0.2.15",
|
||||||
"ts-api-utils": "^2.1.0"
|
"ts-api-utils": "^2.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2195,16 +2164,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.46.4",
|
"version": "8.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.1.tgz",
|
||||||
"integrity": "sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==",
|
"integrity": "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.7.0",
|
"@eslint-community/eslint-utils": "^4.7.0",
|
||||||
"@typescript-eslint/scope-manager": "8.46.4",
|
"@typescript-eslint/scope-manager": "8.48.1",
|
||||||
"@typescript-eslint/types": "8.46.4",
|
"@typescript-eslint/types": "8.48.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.46.4"
|
"@typescript-eslint/typescript-estree": "8.48.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -2219,13 +2188,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.46.4",
|
"version": "8.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz",
|
||||||
"integrity": "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==",
|
"integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.46.4",
|
"@typescript-eslint/types": "8.48.1",
|
||||||
"eslint-visitor-keys": "^4.2.1"
|
"eslint-visitor-keys": "^4.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2790,9 +2759,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/autoprefixer": {
|
"node_modules/autoprefixer": {
|
||||||
"version": "10.4.21",
|
"version": "10.4.22",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz",
|
||||||
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
|
"integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -2809,9 +2778,9 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"browserslist": "^4.24.4",
|
"browserslist": "^4.27.0",
|
||||||
"caniuse-lite": "^1.0.30001702",
|
"caniuse-lite": "^1.0.30001754",
|
||||||
"fraction.js": "^4.3.7",
|
"fraction.js": "^5.3.4",
|
||||||
"normalize-range": "^0.1.2",
|
"normalize-range": "^0.1.2",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"postcss-value-parser": "^4.2.0"
|
"postcss-value-parser": "^4.2.0"
|
||||||
@@ -3179,9 +3148,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/damerau-levenshtein": {
|
"node_modules/damerau-levenshtein": {
|
||||||
@@ -3694,13 +3663,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-config-next": {
|
"node_modules/eslint-config-next": {
|
||||||
"version": "16.0.3",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.0.7.tgz",
|
||||||
"integrity": "sha512-5F6qDjcZldf0Y0ZbqvWvap9xzYUxyDf7/of37aeyhvkrQokj/4bT1JYWZdlWUr283aeVa+s52mPq9ogmGg+5dw==",
|
"integrity": "sha512-WubFGLFHfk2KivkdRGfx6cGSFhaQqhERRfyO8BRx+qiGPGp7WLKcPvYC4mdx1z3VhVRcrfFzczjjTrbJZOpnEQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/eslint-plugin-next": "16.0.3",
|
"@next/eslint-plugin-next": "16.0.7",
|
||||||
"eslint-import-resolver-node": "^0.3.6",
|
"eslint-import-resolver-node": "^0.3.6",
|
||||||
"eslint-import-resolver-typescript": "^3.5.2",
|
"eslint-import-resolver-typescript": "^3.5.2",
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
@@ -4325,9 +4294,9 @@
|
|||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
"version": "3.3.3",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
|
||||||
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
"integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4335,7 +4304,7 @@
|
|||||||
"@nodelib/fs.walk": "^1.2.3",
|
"@nodelib/fs.walk": "^1.2.3",
|
||||||
"glob-parent": "^5.1.2",
|
"glob-parent": "^5.1.2",
|
||||||
"merge2": "^1.3.0",
|
"merge2": "^1.3.0",
|
||||||
"micromatch": "^4.0.8"
|
"micromatch": "^4.0.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6.0"
|
"node": ">=8.6.0"
|
||||||
@@ -4459,15 +4428,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fraction.js": {
|
"node_modules/fraction.js": {
|
||||||
"version": "4.3.7",
|
"version": "5.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
|
||||||
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
|
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "*"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "patreon",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/rawify"
|
"url": "https://github.com/sponsors/rawify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -7007,12 +6976,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "16.0.1",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-16.0.7.tgz",
|
||||||
"integrity": "sha512-e9RLSssZwd35p7/vOa+hoDFggUZIUbZhIUSLZuETCwrCVvxOs87NamoUzT+vbcNAL8Ld9GobBnWOA6SbV/arOw==",
|
"integrity": "sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "16.0.1",
|
"@next/env": "16.0.7",
|
||||||
"@swc/helpers": "0.5.15",
|
"@swc/helpers": "0.5.15",
|
||||||
"caniuse-lite": "^1.0.30001579",
|
"caniuse-lite": "^1.0.30001579",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.31",
|
||||||
@@ -7025,14 +6994,14 @@
|
|||||||
"node": ">=20.9.0"
|
"node": ">=20.9.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "16.0.1",
|
"@next/swc-darwin-arm64": "16.0.7",
|
||||||
"@next/swc-darwin-x64": "16.0.1",
|
"@next/swc-darwin-x64": "16.0.7",
|
||||||
"@next/swc-linux-arm64-gnu": "16.0.1",
|
"@next/swc-linux-arm64-gnu": "16.0.7",
|
||||||
"@next/swc-linux-arm64-musl": "16.0.1",
|
"@next/swc-linux-arm64-musl": "16.0.7",
|
||||||
"@next/swc-linux-x64-gnu": "16.0.1",
|
"@next/swc-linux-x64-gnu": "16.0.7",
|
||||||
"@next/swc-linux-x64-musl": "16.0.1",
|
"@next/swc-linux-x64-musl": "16.0.7",
|
||||||
"@next/swc-win32-arm64-msvc": "16.0.1",
|
"@next/swc-win32-arm64-msvc": "16.0.7",
|
||||||
"@next/swc-win32-x64-msvc": "16.0.1",
|
"@next/swc-win32-x64-msvc": "16.0.7",
|
||||||
"sharp": "^0.34.4"
|
"sharp": "^0.34.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -7059,9 +7028,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next-intl": {
|
"node_modules/next-intl": {
|
||||||
"version": "4.5.7",
|
"version": "4.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.5.8.tgz",
|
||||||
"integrity": "sha512-7iT9rBEFZvsJI5uLoOLgI1kAieg1k7zCwbuby6ylKRbpvt08I1vkZ5FJnIBey1M+r1jam/wANlnqRYeJagjL2Q==",
|
"integrity": "sha512-BdN6494nvt09WtmW5gbWdwRhDDHC/Sg7tBMhN7xfYds3vcRCngSDXat81gmJkblw9jYOv8zXzzFJyu5VYXnJzg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
@@ -7073,9 +7042,9 @@
|
|||||||
"@formatjs/intl-localematcher": "^0.5.4",
|
"@formatjs/intl-localematcher": "^0.5.4",
|
||||||
"@swc/core": "^1.15.2",
|
"@swc/core": "^1.15.2",
|
||||||
"negotiator": "^1.0.0",
|
"negotiator": "^1.0.0",
|
||||||
"next-intl-swc-plugin-extractor": "^4.5.7",
|
"next-intl-swc-plugin-extractor": "^4.5.8",
|
||||||
"po-parser": "^1.0.2",
|
"po-parser": "^1.0.2",
|
||||||
"use-intl": "^4.5.7"
|
"use-intl": "^4.5.8"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
|
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
|
||||||
@@ -7089,9 +7058,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next-intl-swc-plugin-extractor": {
|
"node_modules/next-intl-swc-plugin-extractor": {
|
||||||
"version": "4.5.7",
|
"version": "4.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.5.8.tgz",
|
||||||
"integrity": "sha512-cSHtDpEoSHuEC4CzUDmAAfB0H3fqSephpJNd/GtS9LvUoZM78wJQwkEaqN9yTxXEvJ8uQG60nnOeSl2LQU9qdQ==",
|
"integrity": "sha512-hscCKUv+5GQ0CCNbvqZ8gaxnAGToCgDTbL++jgCq8SCk/ljtZDEeQZcMk46Nm6Ynn49Q/JKF4Npo/Sq1mpbusA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/next-intl/node_modules/@swc/core": {
|
"node_modules/next-intl/node_modules/@swc/core": {
|
||||||
@@ -7567,9 +7536,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "3.6.2",
|
"version": "3.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
|
||||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -7649,24 +7618,24 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "19.2.0",
|
"version": "19.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz",
|
||||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
"integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.2.0",
|
"version": "19.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz",
|
||||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
"integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^19.2.0"
|
"react": "^19.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
@@ -8775,16 +8744,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript-eslint": {
|
"node_modules/typescript-eslint": {
|
||||||
"version": "8.46.4",
|
"version": "8.48.1",
|
||||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.1.tgz",
|
||||||
"integrity": "sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg==",
|
"integrity": "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "8.46.4",
|
"@typescript-eslint/eslint-plugin": "8.48.1",
|
||||||
"@typescript-eslint/parser": "8.46.4",
|
"@typescript-eslint/parser": "8.48.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.46.4",
|
"@typescript-eslint/typescript-estree": "8.48.1",
|
||||||
"@typescript-eslint/utils": "8.46.4"
|
"@typescript-eslint/utils": "8.48.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -8986,9 +8955,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/use-intl": {
|
"node_modules/use-intl": {
|
||||||
"version": "4.5.7",
|
"version": "4.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.5.8.tgz",
|
||||||
"integrity": "sha512-WBVD1fxV9td5osQFK0TRQhz217zHERhxBuA3EmZuH7wCINJPXbYPs+0FH2oMpy6p6BBwuHCJK2ER8hKwxf0LQA==",
|
"integrity": "sha512-rWPV2Sirw55BQbA/7ndUBtsikh8WXwBrUkZJ1mD35+emj/ogPPqgCZdv1DdrEFK42AjF1g5w8d3x8govhqPH6Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/fast-memoize": "^2.2.0",
|
"@formatjs/fast-memoize": "^2.2.0",
|
||||||
|
|||||||
28
package.json
28
package.json
@@ -30,16 +30,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.17",
|
"@tailwindcss/postcss": "^4.1.17",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@types/node": "^24.10.0",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.2",
|
"@types/react": "^19.2.7",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.22",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"next": "^16.0.1",
|
"next": "^16.0.7",
|
||||||
"next-intl": "^4.5.7",
|
"next-intl": "^4.5.8",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.1",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.1",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"rehype-sanitize": "^6.0.0",
|
"rehype-sanitize": "^6.0.0",
|
||||||
@@ -51,16 +51,16 @@
|
|||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.3",
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@next/bundle-analyzer": "^16.0.3",
|
"@next/bundle-analyzer": "^16.0.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.46.4",
|
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
||||||
"@typescript-eslint/parser": "^8.46.4",
|
"@typescript-eslint/parser": "^8.48.1",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-next": "^16.0.3",
|
"eslint-config-next": "^16.0.7",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.5.4",
|
"eslint-plugin-prettier": "^5.5.4",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.7.4",
|
||||||
"typescript-eslint": "^8.46.4"
|
"typescript-eslint": "^8.48.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {createNavigation} from 'next-intl/navigation';
|
import { createNavigation } from 'next-intl/navigation'
|
||||||
import {routing} from './routing';
|
import { routing } from './routing'
|
||||||
|
|
||||||
export const {Link, redirect, usePathname, useRouter} =
|
export const { Link, redirect, usePathname, useRouter } = createNavigation(routing)
|
||||||
createNavigation(routing);
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import {getRequestConfig} from 'next-intl/server';
|
import { getRequestConfig } from 'next-intl/server'
|
||||||
import {routing} from './routing';
|
import { routing } from './routing'
|
||||||
|
|
||||||
export default getRequestConfig(async ({requestLocale}) => {
|
export default getRequestConfig(async ({ requestLocale }) => {
|
||||||
let locale = await requestLocale;
|
let locale = await requestLocale
|
||||||
|
|
||||||
if (!locale || !routing.locales.includes(locale as any)) {
|
if (!locale || !routing.locales.includes(locale as any)) {
|
||||||
locale = routing.defaultLocale;
|
locale = routing.defaultLocale
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
locale,
|
locale,
|
||||||
messages: (await import(`../../messages/${locale}.json`)).default
|
messages: (await import(`../../messages/${locale}.json`)).default,
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {defineRouting} from 'next-intl/routing';
|
import { defineRouting } from 'next-intl/routing'
|
||||||
|
|
||||||
export const routing = defineRouting({
|
export const routing = defineRouting({
|
||||||
locales: ['en', 'ro'],
|
locales: ['en', 'ro'],
|
||||||
@@ -6,8 +6,8 @@ export const routing = defineRouting({
|
|||||||
localePrefix: 'always',
|
localePrefix: 'always',
|
||||||
localeNames: {
|
localeNames: {
|
||||||
en: 'English',
|
en: 'English',
|
||||||
ro: 'Română'
|
ro: 'Română',
|
||||||
}
|
},
|
||||||
} as any);
|
} as any)
|
||||||
|
|
||||||
export type Locale = (typeof routing.locales)[number];
|
export type Locale = (typeof routing.locales)[number]
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"lib": [
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@@ -23,12 +19,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": ["./*"],
|
||||||
"./*"
|
"@/i18n/*": ["./src/i18n/*"]
|
||||||
],
|
|
||||||
"@/i18n/*": [
|
|
||||||
"./src/i18n/*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
@@ -38,7 +30,5 @@
|
|||||||
".next/types/**/*.ts",
|
".next/types/**/*.ts",
|
||||||
".next/dev/types/**/*.ts"
|
".next/dev/types/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": ["node_modules"]
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
3
types/translations.d.ts
vendored
3
types/translations.d.ts
vendored
@@ -1,5 +1,6 @@
|
|||||||
type Messages = typeof import('../messages/en.json');
|
type Messages = typeof import('../messages/en.json')
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||||
interface IntlMessages extends Messages {}
|
interface IntlMessages extends Messages {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user