228 lines
11 KiB
Text
228 lines
11 KiB
Text
---
|
||
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>
|