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>
|
||||
);
|
||||
}
|
||||