Compare commits
10
Commits
c12cbc0a8d
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f93edade88 | ||
|
|
b59c857435 | ||
|
|
ca4ae30ddd | ||
|
|
f8c7232513 | ||
|
|
bcd5b88340 | ||
|
|
14d9284911 | ||
|
|
ad3f4a8a3b | ||
|
|
d2ed1b8868 | ||
|
|
af0023788e | ||
|
|
a924032605 |
@@ -0,0 +1,5 @@
|
|||||||
|
<!-- BEGIN:nextjs-agent-rules -->
|
||||||
|
# This is NOT the Next.js you know
|
||||||
|
|
||||||
|
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
||||||
|
<!-- END:nextjs-agent-rules -->
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 128 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||||
|
After Width: | Height: | Size: 385 B |
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,78 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
import { useInView } from 'framer-motion';
|
||||||
|
import { useLocale, useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
type CountUpProps = {
|
||||||
|
end: number;
|
||||||
|
duration?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function CountUp({ end, duration = 1800 }: CountUpProps) {
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
const ref = useRef(null);
|
||||||
|
const isInView = useInView(ref, { once: true, margin: '-50px' });
|
||||||
|
const locale = useLocale();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isInView) return;
|
||||||
|
|
||||||
|
let startTime: number | null = null;
|
||||||
|
const startValue = 0;
|
||||||
|
|
||||||
|
const animate = (timestamp: number) => {
|
||||||
|
if (!startTime) startTime = timestamp;
|
||||||
|
const elapsed = timestamp - startTime;
|
||||||
|
const progress = Math.min(elapsed / duration, 1);
|
||||||
|
|
||||||
|
// Easing function: easeOutQuad
|
||||||
|
const easeProgress = progress * (2 - progress);
|
||||||
|
|
||||||
|
const current = Math.floor(easeProgress * (end - startValue) + startValue);
|
||||||
|
setCount(current);
|
||||||
|
|
||||||
|
if (progress < 1) {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
} else {
|
||||||
|
setCount(end);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}, [isInView, end, duration]);
|
||||||
|
|
||||||
|
return <span ref={ref}>{count.toLocaleString(locale === 'fa' ? 'fa-IR' : 'en-US')}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StatsBar() {
|
||||||
|
const t = useTranslations('Stats');
|
||||||
|
|
||||||
|
const stats = [
|
||||||
|
{ value: 28, key: 'articles' },
|
||||||
|
{ value: 118280, key: 'views' },
|
||||||
|
{ value: 4789, key: 'reactions' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="w-full bg-sand/20 dark:bg-charcoal/20 border-y border-sand/40 dark:border-charcoal/40 py-8 transition-colors duration-300">
|
||||||
|
<div className="max-w-[1200px] mx-auto px-6 grid grid-cols-1 md:grid-cols-3 gap-8 text-center">
|
||||||
|
{stats.map((stat, idx) => (
|
||||||
|
<div
|
||||||
|
key={stat.key}
|
||||||
|
className={`flex flex-col items-center justify-center ${
|
||||||
|
idx !== stats.length - 1 ? 'md:border-r border-sand/50 dark:border-charcoal/50 rtl:md:border-r-0 rtl:md:border-l' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="text-3xl md:text-5xl font-black text-teal-brand dark:text-teal-brand tracking-tight mb-2">
|
||||||
|
<CountUp end={stat.value} />
|
||||||
|
</div>
|
||||||
|
<div className="text-xs uppercase tracking-widest text-charcoal/60 dark:text-warm-white/50 font-semibold">
|
||||||
|
{t(stat.key)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { useLocale, useTranslations } from 'next-intl';
|
||||||
|
import { BookOpen, Eye, Heart, ArrowUpRight } from 'lucide-react';
|
||||||
|
|
||||||
|
type Article = {
|
||||||
|
id: string;
|
||||||
|
titleKey: string;
|
||||||
|
descKey: string;
|
||||||
|
dateKey: string;
|
||||||
|
viewsKey: string;
|
||||||
|
link: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Writing() {
|
||||||
|
const t = useTranslations('Writing');
|
||||||
|
const tStats = useTranslations('Stats');
|
||||||
|
const locale = useLocale();
|
||||||
|
|
||||||
|
const articles: Article[] = [
|
||||||
|
{
|
||||||
|
id: 'art1',
|
||||||
|
titleKey: 'articles.art1.title',
|
||||||
|
descKey: 'articles.art1.desc',
|
||||||
|
dateKey: 'articles.art1.date',
|
||||||
|
viewsKey: 'articles.art1.views',
|
||||||
|
link: 'https://teknav.ir/profile/@arsam-sabbagh',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'art2',
|
||||||
|
titleKey: 'articles.art2.title',
|
||||||
|
descKey: 'articles.art2.desc',
|
||||||
|
dateKey: 'articles.art2.date',
|
||||||
|
viewsKey: 'articles.art2.views',
|
||||||
|
link: 'https://teknav.ir/profile/@arsam-sabbagh',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'art3',
|
||||||
|
titleKey: 'articles.art3.title',
|
||||||
|
descKey: 'articles.art3.desc',
|
||||||
|
dateKey: 'articles.art3.date',
|
||||||
|
viewsKey: 'articles.art3.views',
|
||||||
|
link: 'https://teknav.ir/profile/@arsam-sabbagh',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="writing" className="py-24 bg-warm-white dark:bg-near-black border-t border-sand/20 dark:border-charcoal/50 transition-colors duration-300">
|
||||||
|
<div className="max-w-[1200px] mx-auto px-6">
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center max-w-2xl mx-auto mb-16">
|
||||||
|
<h2 className="text-xs font-semibold tracking-widest text-terracotta uppercase mb-2">
|
||||||
|
{t('title')}
|
||||||
|
</h2>
|
||||||
|
<h3 className="text-3xl font-extrabold tracking-tight text-charcoal dark:text-warm-white">
|
||||||
|
{t('subtitle')}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Featured Article Hero Card */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true, margin: '-50px' }}
|
||||||
|
transition={{ duration: 0.8 }}
|
||||||
|
className="mb-12 rounded-2xl border border-sand dark:border-charcoal bg-warm-white dark:bg-charcoal/10 overflow-hidden p-6 md:p-10 relative group hover:border-teal-brand/30 dark:hover:border-teal-brand/30 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="relative z-10 flex flex-col md:flex-row md:items-center gap-8 justify-between">
|
||||||
|
<div className="space-y-4 max-w-2xl">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-terracotta/10 text-terracotta uppercase tracking-wider">
|
||||||
|
{t('featured')}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-charcoal/50 dark:text-warm-white/40 font-semibold">
|
||||||
|
{t('articles.art1.date')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 className="text-2xl md:text-3xl font-bold text-charcoal dark:text-warm-white leading-tight">
|
||||||
|
{t('articles.art1.title')}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p className="text-sm md:text-base text-charcoal/70 dark:text-warm-white/60 leading-relaxed">
|
||||||
|
{t('articles.art1.desc')}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4 text-xs font-semibold text-charcoal/60 dark:text-warm-white/50">
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Eye className="w-3.5 h-3.5" />
|
||||||
|
<span>{t('articles.art1.views')}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-shrink-0 pt-4 md:pt-0">
|
||||||
|
<a
|
||||||
|
href="https://teknav.ir/profile/@arsam-sabbagh"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-flex items-center gap-2 px-5 py-3 rounded-full bg-teal-brand hover:bg-teal-brand/90 text-warm-white text-sm font-semibold transition-all hover:scale-102 hover:shadow-md cursor-pointer"
|
||||||
|
>
|
||||||
|
<span>{t('readMore')}</span>
|
||||||
|
<ArrowUpRight className="w-4 h-4" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-teal-brand/5 to-transparent pointer-events-none opacity-50" />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Profile Card & List Columns */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 items-start">
|
||||||
|
|
||||||
|
{/* Teknav Profile Summary Card (Left) */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: -20 }}
|
||||||
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
className="lg:col-span-4 p-6 rounded-2xl border border-sand dark:border-charcoal bg-sand/10 dark:bg-charcoal/20 backdrop-blur-sm"
|
||||||
|
>
|
||||||
|
<h4 className="text-lg font-bold text-charcoal dark:text-warm-white mb-6 flex items-center gap-2">
|
||||||
|
<BookOpen className="w-5 h-5 text-teal-brand" />
|
||||||
|
<span>{t('stats')}</span>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<div className="text-3xl font-black text-teal-brand dark:text-teal-brand">
|
||||||
|
{(28).toLocaleString(locale === 'fa' ? 'fa-IR' : 'en-US')}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs font-semibold text-charcoal/60 dark:text-warm-white/50 uppercase tracking-wider mt-1">
|
||||||
|
{tStats('articles')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr className="border-sand/30 dark:border-charcoal/30" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="text-3xl font-black text-teal-brand dark:text-teal-brand">
|
||||||
|
{(118280).toLocaleString(locale === 'fa' ? 'fa-IR' : 'en-US')}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs font-semibold text-charcoal/60 dark:text-warm-white/50 uppercase tracking-wider mt-1">
|
||||||
|
{tStats('views')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr className="border-sand/30 dark:border-charcoal/30" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="text-3xl font-black text-teal-brand dark:text-teal-brand">
|
||||||
|
{(4789).toLocaleString(locale === 'fa' ? 'fa-IR' : 'en-US')}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs font-semibold text-charcoal/60 dark:text-warm-white/50 uppercase tracking-wider mt-1">
|
||||||
|
{tStats('reactions')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="https://teknav.ir/profile/@arsam-sabbagh"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="mt-8 flex items-center justify-center gap-2 w-full py-3 rounded-xl border border-sand dark:border-charcoal hover:border-teal-brand hover:text-teal-brand text-charcoal dark:text-warm-white text-xs font-bold transition-all cursor-pointer"
|
||||||
|
>
|
||||||
|
<span>{t('viewProfile')}</span>
|
||||||
|
<ArrowUpRight className="w-3.5 h-3.5" />
|
||||||
|
</a>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Other Articles List (Right) */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: 20 }}
|
||||||
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
className="lg:col-span-8 space-y-4"
|
||||||
|
>
|
||||||
|
{articles.map((art) => (
|
||||||
|
<a
|
||||||
|
key={art.id}
|
||||||
|
href={art.link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="block p-6 rounded-2xl border border-sand dark:border-charcoal bg-warm-white dark:bg-charcoal/10 hover:border-teal-brand/35 dark:hover:border-teal-brand/35 transition-all duration-300 group"
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-start gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<span className="text-xs text-charcoal/40 dark:text-warm-white/30 font-semibold">
|
||||||
|
{t(`${art.dateKey}`)}
|
||||||
|
</span>
|
||||||
|
<h5 className="text-lg font-bold text-charcoal dark:text-warm-white group-hover:text-teal-brand dark:group-hover:text-teal-brand transition-colors">
|
||||||
|
{t(`${art.titleKey}`)}
|
||||||
|
</h5>
|
||||||
|
<p className="text-xs md:text-sm text-charcoal/70 dark:text-warm-white/60 leading-relaxed">
|
||||||
|
{t(`${art.descKey}`)}
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-1 text-[11px] font-semibold text-charcoal/50 dark:text-warm-white/40">
|
||||||
|
<Eye className="w-3 h-3" />
|
||||||
|
<span>{t(`${art.viewsKey}`)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-2 rounded-full border border-sand/40 dark:border-charcoal/50 group-hover:border-teal-brand text-charcoal/40 dark:text-warm-white/40 group-hover:text-teal-brand group-hover:bg-teal-brand/5 transition-all">
|
||||||
|
<ArrowUpRight className="w-4 h-4" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import {getRequestConfig} from 'next-intl/server';
|
||||||
|
import {routing} from './routing';
|
||||||
|
|
||||||
|
export default getRequestConfig(async ({requestLocale}) => {
|
||||||
|
let locale = await requestLocale;
|
||||||
|
|
||||||
|
// Ensure that the incoming locale is valid
|
||||||
|
if (!locale || !routing.locales.includes(locale as any)) {
|
||||||
|
locale = routing.defaultLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
locale,
|
||||||
|
messages: (await import(`../../messages/${locale}.json`)).default
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import {defineRouting} from 'next-intl/routing';
|
||||||
|
import {createNavigation} from 'next-intl/navigation';
|
||||||
|
|
||||||
|
export const routing = defineRouting({
|
||||||
|
// A list of all locales that are supported
|
||||||
|
locales: ['en', 'fa'],
|
||||||
|
|
||||||
|
// Used when no locale matches
|
||||||
|
defaultLocale: 'en',
|
||||||
|
|
||||||
|
// Custom prefix mode so that default locale doesn't get prefix
|
||||||
|
localePrefix: 'as-needed'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lightweight wrappers around Next.js' navigation APIs
|
||||||
|
// that will automatically consider the routing configuration
|
||||||
|
export const {Link, redirect, usePathname, useRouter, getPathname} =
|
||||||
|
createNavigation(routing);
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import createMiddleware from 'next-intl/middleware';
|
||||||
|
import {routing} from './i18n/routing';
|
||||||
|
|
||||||
|
export default createMiddleware(routing);
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
// Match all pathnames except for
|
||||||
|
// - … if they start with `/api`, `/_next` or `/_vercel`
|
||||||
|
// - … the ones containing a dot (e.g. `favicon.ico`)
|
||||||
|
matcher: ['/', '/(fa|en)/:path*', '/((?!api|_next|_vercel|.*\\..*).*)']
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user