images updated
BIN
public/images/parsa2.jpg
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
public/images/profile1.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
public/images/projects/project1_image1.jpg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
public/images/projects/project1_image2.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
public/images/projects/project2_image1.jpg
Normal file
|
After Width: | Height: | Size: 307 KiB |
BIN
public/images/projects/project2_image2.jpg
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
public/images/projects/project3_image1.jpg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
public/images/projects/project3_image2.jpg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
public/images/projects/project4_image1.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
public/images/projects/project4_image2.jpg
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
public/images/projects/project5_image1.jpg
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
public/images/projects/project5_image2.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
public/images/projects/project6_image1.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
public/images/projects/project6_image2.jpg
Normal file
|
After Width: | Height: | Size: 110 KiB |
@ -0,0 +1,115 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { AboutMeDataType } from "@/app/types/types";
|
||||||
|
|
||||||
|
interface AboutMeContentProps {
|
||||||
|
data: AboutMeDataType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CATEGORIES = {
|
||||||
|
"Core Tech": ["React", "Next.js", "TypeScript", "JavaScript"],
|
||||||
|
"Backend & Tools": ["Laravel", "PHP", "MySQL", "Node.js", "Docker", "Git"],
|
||||||
|
"Architecture & Others": ["REST API", "TDD", "UI/UX Design", "Clean Architecture"],
|
||||||
|
};
|
||||||
|
|
||||||
|
type CategoryKey = keyof typeof CATEGORIES | "Other";
|
||||||
|
|
||||||
|
export default function AboutMeContent({ data }: AboutMeContentProps) {
|
||||||
|
const categorizedSkills: Record<CategoryKey, string[]> = {
|
||||||
|
"Core Tech": [],
|
||||||
|
"Backend & Tools": [],
|
||||||
|
"Architecture & Others": [],
|
||||||
|
"Other": [],
|
||||||
|
};
|
||||||
|
|
||||||
|
data.skills.forEach((skill) => {
|
||||||
|
let matched = false;
|
||||||
|
for (const [cat, skills] of Object.entries(CATEGORIES)) {
|
||||||
|
if (skills.some((s) => skill.title.toLowerCase().includes(s.toLowerCase()))) {
|
||||||
|
categorizedSkills[cat as CategoryKey].push(skill.title);
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!matched) categorizedSkills["Other"].push(skill.title);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isRTL = typeof document !== "undefined" && document.dir === "rtl";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className="relative flex w-full max-w-[1200px] flex-col items-center justify-center gap-12 py-20 lg:flex-row"
|
||||||
|
id="about-me"
|
||||||
|
>
|
||||||
|
{/* Text Section */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: isRTL ? 50 : -50 }}
|
||||||
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ duration: 0.8 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="flex w-full flex-col items-start gap-6 lg:w-[55%]"
|
||||||
|
>
|
||||||
|
<h2 className="text-4xl md:text-5xl font-bold text-foreground tracking-tight">
|
||||||
|
{data.title}
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-muted-foreground leading-relaxed text-balance">
|
||||||
|
{data.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-8 grid grid-cols-1 md:grid-cols-2 gap-8 w-full">
|
||||||
|
{Object.entries(categorizedSkills).map(([category, skills]) => (
|
||||||
|
skills.length > 0 && (
|
||||||
|
<motion.div
|
||||||
|
key={category}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.2 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="flex flex-col gap-3"
|
||||||
|
>
|
||||||
|
<h3 className="text-sm font-mono font-bold text-primary uppercase tracking-widest">
|
||||||
|
{category}
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{skills.map((skill, idx) => (
|
||||||
|
<span
|
||||||
|
key={idx}
|
||||||
|
className="tech-tag"
|
||||||
|
>
|
||||||
|
{skill}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Image Section */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
|
whileInView={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ duration: 0.8 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="relative w-full max-w-[450px] flex justify-center lg:w-[40%]"
|
||||||
|
>
|
||||||
|
<div className="relative group">
|
||||||
|
<div className="absolute -inset-4 bg-gradient-to-tr from-primary to-purple-600 rounded-3xl blur opacity-20 group-hover:opacity-40 transition duration-500"></div>
|
||||||
|
<div className="relative overflow-hidden rounded-3xl border-4 border-background shadow-2xl">
|
||||||
|
<Image
|
||||||
|
src={process.env.NEXT_PUBLIC_BACK_URL + data.image}
|
||||||
|
alt="profile picture"
|
||||||
|
width={450}
|
||||||
|
height={450}
|
||||||
|
className="w-full h-auto transition-transform duration-500 group-hover:scale-105"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
50
src/app/layout-ssh-fixed.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import localFont from 'next/font/local';
|
||||||
|
import './globals.css';
|
||||||
|
import { getDefaultLocale } from './lib/functions/utils';
|
||||||
|
import { ThemeProvider } from './lib/providers/theme-provider';
|
||||||
|
|
||||||
|
const mikhak = localFont({
|
||||||
|
src: 'lib/fonts/mikhak-v32/variable/Mikhak[DSTY,KSHD,wght].woff2',
|
||||||
|
variable: '--font-mikhak',
|
||||||
|
weight: '100 900',
|
||||||
|
});
|
||||||
|
|
||||||
|
const inter = localFont({
|
||||||
|
src: 'lib/fonts/inter/variable/Inter-Variable.woff2',
|
||||||
|
variable: '--font-inter',
|
||||||
|
weight: '100 900',
|
||||||
|
});
|
||||||
|
|
||||||
|
const jetbrainsMono = localFont({
|
||||||
|
src: 'lib/fonts/jetbrains-mono/variable/JetBrainsMono-Variable.woff2',
|
||||||
|
variable: '--font-jetbrains-mono',
|
||||||
|
weight: '100 800',
|
||||||
|
});
|
||||||
|
|
||||||
|
export default async function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
const defaultLocale = await getDefaultLocale();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang={defaultLocale} dir={defaultLocale === 'fa' ? 'rtl' : 'ltr'} suppressHydrationWarning>
|
||||||
|
<body
|
||||||
|
suppressHydrationWarning={true}
|
||||||
|
className={`${mikhak.variable} ${inter.variable} ${jetbrainsMono.variable} relative flex h-full w-full flex-col items-center justify-between antialiased`}
|
||||||
|
>
|
||||||
|
<ThemeProvider
|
||||||
|
attribute="class"
|
||||||
|
defaultTheme="dark"
|
||||||
|
enableSystem
|
||||||
|
disableTransitionOnChange
|
||||||
|
>
|
||||||
|
<div className="main flex h-full w-full flex-wrap justify-center">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</ThemeProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||