jalil.arfaoui.net/src/pages/ar/برمجة/مسار.astro

228 lines
11 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
import { getCollection, render } from "astro:content";
import { Image } from "astro:assets";
import Layout from "../../../layouts/main.astro";
const logos = import.meta.glob<{ default: ImageMetadata }>('../../../assets/images/companies/*.png', { eager: true });
function getLogo(filename?: string) {
if (!filename) return null;
const key = `../../../assets/images/companies/${filename}`;
return logos[key]?.default ?? null;
}
function getInitials(company: string) {
return company.split(/[\s-]+/).map(w => w[0]).filter(Boolean).slice(0, 2).join('').toUpperCase();
}
const locale = "ar";
const experiences = (await getCollection("experiences"))
.filter((e) => e.data.lang === locale && !e.data.draft)
.sort((a, b) => (b.data.startDate > a.data.startDate ? 1 : -1));
const typeLabels: Record<string, string> = {
employment: 'وظيفة', freelance: 'مستقل', teaching: 'تدريس',
community: 'مجتمع', entrepreneurship: 'ريادة أعمال',
};
const typeColors: Record<string, { badge: string; border: string; dot: string }> = {
employment: { badge: 'type-badge-blue', border: 'type-border-blue', dot: 'bg-blue-400 border-blue-400/50' },
freelance: { badge: 'type-badge-amber', border: 'type-border-amber', dot: 'bg-amber-400 border-amber-400/50' },
teaching: { badge: 'type-badge-emerald', border: 'type-border-emerald', dot: 'bg-emerald-400 border-emerald-400/50' },
community: { badge: 'type-badge-pink', border: 'type-border-pink', dot: 'bg-pink-400 border-pink-400/50' },
entrepreneurship: { badge: 'type-badge-purple', border: 'type-border-purple', dot: 'bg-purple-400 border-purple-400/50' },
};
function formatMonth(dateStr: string) {
const [year, month] = dateStr.split('-');
return new Date(parseInt(year), parseInt(month) - 1)
.toLocaleDateString('ar-SA', { year: 'numeric', month: 'short' });
}
function computeDuration(startStr: string, endStr?: string) {
const [sy, sm] = startStr.split('-').map(Number);
const end = endStr ? endStr.split('-').map(Number) : [new Date().getFullYear(), new Date().getMonth() + 1];
const totalMonths = (end[0] - sy) * 12 + (end[1] - sm);
const years = Math.floor(totalMonths / 12);
const months = totalMonths % 12;
if (years === 0) return `${months} أشهر`;
if (months === 0) return `${years} سنة`;
return `${years} سنة ${months} أشهر`;
}
---
<Layout
title="المسار المهني - جليل عرفاوي"
facet="code"
description="المسار المهني لجليل عرفاوي: أكثر من 20 سنة من الخبرة في تطوير البرمجيات، من مطوّر مبتدئ إلى مطوّر رئيسي ومدير تقني."
>
<section dir="rtl" lang="ar" class="relative z-20 max-w-3xl mx-auto my-12 px-7 lg:px-0">
<div class="mb-10">
<a href="/ar/برمجة" class="inline-flex items-center gap-1.5 text-sm text-white/50 hover:text-white/80 transition-colors mb-6 group">
برمجة
<svg class="w-4 h-4 group-hover:translate-x-0.5 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</a>
<h1 class="text-3xl sm:text-4xl font-bold text-white">المسار</h1>
<p class="mt-3 text-white/60 text-lg">أكثر من 20 سنة من الخبرة في تطوير البرمجيات.</p>
</div>
<div class="mt-10">
{experiences.map(async (exp) => {
const { Content } = await render(exp);
const isOngoing = !exp.data.endDate;
const start = formatMonth(exp.data.startDate);
const end = exp.data.endDate ? formatMonth(exp.data.endDate) : 'الحاضر';
const duration = computeDuration(exp.data.startDate, exp.data.endDate);
const colors = typeColors[exp.data.type] || typeColors.employment;
const label = typeLabels[exp.data.type] || exp.data.type;
return (
<div class="relative pr-10 pb-10 border-r-2 border-white/[0.12] last:pb-0">
<div
class:list={[
"absolute -right-[7px] top-2 w-3 h-3 rounded-full border-2",
isOngoing ? colors.dot : "border-white/20 bg-white/10"
]}
>
{isOngoing && <div class="absolute inset-0 rounded-full bg-current opacity-40 animate-ping" />}
</div>
<div class:list={[
"exp-card relative rounded-2xl p-6 transition-colors duration-200 border-r-[3px]",
isOngoing ? "exp-card--ongoing" : "",
colors.border
]}>
{getLogo(exp.data.logo) ? (
<div class="exp-logo absolute top-5 left-5 w-16 h-16 rounded-xl flex items-center justify-center p-2">
<Image
src={getLogo(exp.data.logo)!}
alt={`شعار ${exp.data.company}`}
width={52}
height={52}
class="rounded-lg object-contain"
/>
</div>
) : (
<div class="exp-logo-fallback absolute top-5 left-5 w-16 h-16 rounded-xl flex items-center justify-center">
<span class="text-base font-bold">{getInitials(exp.data.company)}</span>
</div>
)}
<div class="mb-3 flex items-center gap-2 flex-wrap pl-20">
<span class:list={["inline-block px-2 py-0.5 text-[11px] font-medium rounded-full border", colors.badge]}>
{label}
</span>
<span class="exp-meta text-xs">
{start} — {end}
</span>
<span class="exp-meta text-xs">· {duration}</span>
{exp.data.location && <span class="exp-meta text-xs">· {exp.data.location}</span>}
</div>
<h3 class="text-xl font-bold pl-20">{exp.data.role}</h3>
<p class="exp-company text-sm font-medium mt-1 mb-3">
{exp.data.companyUrl ? (
<a href={exp.data.companyUrl} target="_blank" rel="noopener noreferrer" class="transition-colors">{exp.data.company}</a>
) : exp.data.company}
</p>
<div class="exp-content text-sm leading-relaxed mb-4 prose prose-sm max-w-none">
<Content />
</div>
{exp.data.technologies && exp.data.technologies.length > 0 && (
<div class="flex flex-wrap gap-1.5">
{exp.data.technologies.map((tech: string) => (
<span class="exp-tech inline-block px-2.5 py-0.5 text-xs rounded-full border">
{tech}
</span>
))}
</div>
)}
</div>
</div>
);
})}
</div>
</section>
</Layout>
<style>
/* ── Light mode (default): white cards on purple gradient ── */
.exp-card {
background: white;
border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.exp-card--ongoing { box-shadow: 0 0 0 1px rgba(168, 85, 247, 0.25), 0 1px 3px rgba(0, 0, 0, 0.08); }
.exp-card h3 { color: #111827 !important; }
.exp-card .exp-meta { color: #9ca3af; }
.exp-card .exp-company { color: #6b21a8; }
.exp-card .exp-company a { color: #6b21a8 !important; text-decoration: none !important; }
.exp-card .exp-company a:hover { color: #581c87 !important; }
.exp-card .exp-content,
.exp-card .exp-content p { color: #4b5563 !important; }
.exp-card .exp-content a { color: #6b21a8 !important; text-decoration-color: rgba(107, 33, 168, 0.3) !important; }
.exp-card .exp-content a:hover { color: #581c87 !important; }
.exp-card .exp-tech { background: #f3f4f6; color: #6b7280; border-color: #e5e7eb; }
.exp-logo { background: #f9fafb; border: 1px solid #e5e7eb; }
.exp-logo-fallback { background: #f3f4f6; border: 1px solid #e5e7eb; }
.exp-logo-fallback span { color: #9ca3af; }
/* Type badges light */
.type-badge-blue { background: #eff6ff; color: #1d4ed8; border-color: #bfdbfe; }
.type-badge-amber { background: #fffbeb; color: #b45309; border-color: #fde68a; }
.type-badge-emerald { background: #ecfdf5; color: #047857; border-color: #a7f3d0; }
.type-badge-pink { background: #fdf2f8; color: #be185d; border-color: #fbcfe8; }
.type-badge-purple { background: #faf5ff; color: #7e22ce; border-color: #e9d5ff; }
/* Type borders light (RTL: border-right) */
.type-border-blue { border-right-color: #3b82f6 !important; }
.type-border-amber { border-right-color: #f59e0b !important; }
.type-border-emerald { border-right-color: #10b981 !important; }
.type-border-pink { border-right-color: #ec4899 !important; }
.type-border-purple { border-right-color: #a855f7 !important; }
/* ── Dark mode: translucent cards ── */
:global(.dark) .exp-card {
background: rgba(255, 255, 255, 0.03);
border-color: rgba(255, 255, 255, 0.08);
box-shadow: none;
}
:global(.dark) .exp-card:hover { background: rgba(255, 255, 255, 0.06); }
:global(.dark) .exp-card--ongoing {
background: rgba(255, 255, 255, 0.07);
border-color: rgba(255, 255, 255, 0.12);
box-shadow: none;
}
:global(.dark) .exp-card h3 { color: white !important; }
:global(.dark) .exp-card .exp-meta { color: rgba(255, 255, 255, 0.3); }
:global(.dark) .exp-card .exp-company { color: #d4b5ff; }
:global(.dark) .exp-card .exp-company a { color: #d4b5ff !important; }
:global(.dark) .exp-card .exp-company a:hover { color: white !important; }
:global(.dark) .exp-card .exp-content,
:global(.dark) .exp-card .exp-content p { color: rgba(255, 255, 255, 0.6) !important; }
:global(.dark) .exp-card .exp-content a { color: #d4b5ff !important; }
:global(.dark) .exp-card .exp-content a:hover { color: white !important; }
:global(.dark) .exp-card .exp-tech { background: rgba(255, 255, 255, 0.06); color: rgba(255, 255, 255, 0.45); border-color: rgba(255, 255, 255, 0.06); }
:global(.dark) .exp-logo { background: white; border-color: rgba(255, 255, 255, 0.2); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); }
:global(.dark) .exp-logo-fallback { background: rgba(255, 255, 255, 0.08); border-color: rgba(255, 255, 255, 0.1); }
:global(.dark) .exp-logo-fallback span { color: rgba(255, 255, 255, 0.4); }
/* Type badges dark */
:global(.dark) .type-badge-blue { background: rgba(96, 165, 250, 0.15); color: #93c5fd; border-color: rgba(96, 165, 250, 0.2); }
:global(.dark) .type-badge-amber { background: rgba(251, 191, 36, 0.15); color: #fcd34d; border-color: rgba(251, 191, 36, 0.2); }
:global(.dark) .type-badge-emerald { background: rgba(52, 211, 153, 0.15); color: #6ee7b7; border-color: rgba(52, 211, 153, 0.2); }
:global(.dark) .type-badge-pink { background: rgba(244, 114, 182, 0.15); color: #f9a8d4; border-color: rgba(244, 114, 182, 0.2); }
:global(.dark) .type-badge-purple { background: rgba(168, 85, 247, 0.15); color: #d4b5ff; border-color: rgba(168, 85, 247, 0.2); }
/* Type borders dark (RTL: border-right) */
:global(.dark) .type-border-blue { border-right-color: rgba(96, 165, 250, 0.4) !important; }
:global(.dark) .type-border-amber { border-right-color: rgba(251, 191, 36, 0.4) !important; }
:global(.dark) .type-border-emerald { border-right-color: rgba(52, 211, 153, 0.4) !important; }
:global(.dark) .type-border-pink { border-right-color: rgba(244, 114, 182, 0.4) !important; }
:global(.dark) .type-border-purple { border-right-color: rgba(168, 85, 247, 0.4) !important; }
</style>