Traduction EN et AR de tous les changements design /code : hero, timeline, valeurs, icônes, rythme, title
This commit is contained in:
parent
8427fb066e
commit
c2530f9181
3 changed files with 171 additions and 213 deletions
|
|
@ -5,10 +5,14 @@ import Layout from "../../../layouts/main.astro";
|
||||||
import Link from "../../../components/Link.astro";
|
import Link from "../../../components/Link.astro";
|
||||||
import FeaturedRecommendation from "../../../components/code/FeaturedRecommendation.astro";
|
import FeaturedRecommendation from "../../../components/code/FeaturedRecommendation.astro";
|
||||||
import ProjectCard from "../../../components/code/ProjectCard.astro";
|
import ProjectCard from "../../../components/code/ProjectCard.astro";
|
||||||
import SkillBadge from "../../../components/code/SkillBadge.astro";
|
import ValueItem from "../../../components/code/ValueItem.astro";
|
||||||
|
import LinkedInIcon from "../../../components/icons/LinkedInIcon.astro";
|
||||||
|
import MaltIcon from "../../../components/icons/MaltIcon.astro";
|
||||||
|
import StackOverflowIcon from "../../../components/icons/StackOverflowIcon.astro";
|
||||||
|
import GitHubIcon from "../../../components/icons/GitHubIcon.astro";
|
||||||
|
import ForgejoIcon from "../../../components/icons/ForgejoIcon.astro";
|
||||||
import { getProjectBaseSlug, getProjectsBasePath } from "../../../utils/i18n";
|
import { getProjectBaseSlug, getProjectsBasePath } from "../../../utils/i18n";
|
||||||
import logoTiqa from "../../../assets/images/logo-tiqa-blanc.png";
|
import logoTiqa from "../../../assets/images/logo-tiqa-blanc.png";
|
||||||
import skillsData from "../../../data/skills.json";
|
|
||||||
|
|
||||||
const locale = "ar";
|
const locale = "ar";
|
||||||
const projectsBasePath = getProjectsBasePath(locale);
|
const projectsBasePath = getProjectsBasePath(locale);
|
||||||
|
|
@ -17,7 +21,7 @@ const experiences = (await getCollection("experiences"))
|
||||||
.filter((e) => e.data.lang === locale && !e.data.draft)
|
.filter((e) => e.data.lang === locale && !e.data.draft)
|
||||||
.sort((a, b) => (b.data.startDate > a.data.startDate ? 1 : -1));
|
.sort((a, b) => (b.data.startDate > a.data.startDate ? 1 : -1));
|
||||||
|
|
||||||
const recentExperiences = experiences.slice(0, 4);
|
const recentExperiences = experiences.filter((e) => e.data.featured).slice(0, 4);
|
||||||
|
|
||||||
const projects = (await getCollection("projects"))
|
const projects = (await getCollection("projects"))
|
||||||
.filter((p) => p.data.lang === locale && !p.data.draft && p.data.category === "dev" && p.data.featured)
|
.filter((p) => p.data.lang === locale && !p.data.draft && p.data.category === "dev" && p.data.featured)
|
||||||
|
|
@ -32,8 +36,6 @@ const recommendationTexts = recommendations.map((rec) => ({
|
||||||
text: rec.body || '',
|
text: rec.body || '',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const topSkills = skillsData.categories.slice(0, 3);
|
|
||||||
|
|
||||||
function formatMonth(dateStr: string) {
|
function formatMonth(dateStr: string) {
|
||||||
const [year, month] = dateStr.split('-');
|
const [year, month] = dateStr.split('-');
|
||||||
return new Date(parseInt(year), parseInt(month) - 1)
|
return new Date(parseInt(year), parseInt(month) - 1)
|
||||||
|
|
@ -42,14 +44,16 @@ function formatMonth(dateStr: string) {
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
title="برمجة - جليل عرفاوي"
|
title="جليل عرفاوي · مطوّر مستقل في ألبي، حِرَفيّ البرمجيات"
|
||||||
facet="code"
|
facet="code"
|
||||||
description="المسار المهني لجليل عرفاوي: مطوّر مستقل متخصص في Software Craftsmanship، TDD، DDD. TypeScript، PHP، Elixir."
|
description="المسار المهني لجليل عرفاوي: مطوّر مستقل متخصص في Software Craftsmanship، TDD، DDD. TypeScript، PHP، Elixir."
|
||||||
>
|
>
|
||||||
<section dir="rtl" lang="ar" class="relative z-20 max-w-3xl mx-auto my-12 px-7 lg:px-0">
|
<section dir="rtl" lang="ar" class="relative z-20 max-w-3xl mx-auto my-16 px-7 lg:px-0">
|
||||||
<div class="mb-10">
|
<div class="mb-16">
|
||||||
<h1 class="text-4xl sm:text-5xl font-semibold text-white font-display tracking-wide">حِرَفيّ البرمجيات</h1>
|
<span class="inline-block px-3 py-1 text-sm text-purple-200/80 bg-white/[0.06] border border-white/[0.08] rounded-full mb-5">مطوّر مستقل · ألبي</span>
|
||||||
<div class="mt-6 space-y-4 text-lg text-white/60 leading-relaxed max-w-2xl">
|
<h1 class="text-5xl sm:text-6xl font-semibold text-white font-display tracking-wide">حِرَفيّ البرمجيات</h1>
|
||||||
|
<div class="mt-6 mb-6 w-16 h-0.5 bg-gradient-to-l from-purple-400 to-transparent rounded-full"></div>
|
||||||
|
<div class="space-y-4 text-lg text-white/60 leading-relaxed max-w-2xl">
|
||||||
<p>
|
<p>
|
||||||
TDD، Clean Code، Domain-Driven Design: هذه طريقتي في بناء البرمجيات. أرافق الفرق كمطوّر أول، أو قائد تقني، أو مدرب تقني. أدواتي: TypeScript/JavaScript، وكذلك PHP وElixir.
|
TDD، Clean Code، Domain-Driven Design: هذه طريقتي في بناء البرمجيات. أرافق الفرق كمطوّر أول، أو قائد تقني، أو مدرب تقني. أدواتي: TypeScript/JavaScript، وكذلك PHP وElixir.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -62,44 +66,56 @@ function formatMonth(dateStr: string) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-12">
|
<div class="mb-20 -mx-7 px-7 py-8 bg-white/[0.03] rounded-2xl">
|
||||||
<div class="flex items-center justify-between mb-5">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<h2 class="text-2xl font-bold text-white ">المسار</h2>
|
<h2 class="text-2xl font-bold text-white ">المسار</h2>
|
||||||
<a href="/ar/برمجة/مسار" class="text-sm text-purple-200 hover:text-white transition-colors">← عرض الكل</a>
|
<a href="/ar/برمجة/مسار" class="text-sm text-purple-200 hover:text-white transition-colors">← عرض الكل</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-3">
|
<div class="relative mr-3">
|
||||||
|
<div class="absolute right-0 top-1.5 bottom-1.5 w-px bg-purple-300/20"></div>
|
||||||
{recentExperiences.map((exp) => {
|
{recentExperiences.map((exp) => {
|
||||||
const isOngoing = !exp.data.endDate;
|
const isOngoing = !exp.data.endDate;
|
||||||
const start = formatMonth(exp.data.startDate);
|
const start = formatMonth(exp.data.startDate);
|
||||||
const end = exp.data.endDate ? formatMonth(exp.data.endDate) : 'الحالي';
|
const end = exp.data.endDate ? formatMonth(exp.data.endDate) : 'الحالي';
|
||||||
return (
|
return (
|
||||||
<div class="facet-card rounded-xl bg-white/[0.06] border border-white/[0.1] p-4 hover:bg-white/[0.1] transition-colors">
|
<div class="relative pr-7 pb-6 last:pb-0">
|
||||||
<div class="flex items-start justify-between gap-4">
|
<div class:list={[
|
||||||
<div class="min-w-0">
|
"absolute right-0 top-1.5 w-2.5 h-2.5 rounded-full translate-x-1/2 border-2",
|
||||||
<p class="font-semibold text-white text-sm truncate">{exp.data.role}</p>
|
isOngoing
|
||||||
<p class="text-xs text-white/50 mt-0.5">
|
? "bg-purple-400 border-purple-400/50 shadow-[0_0_8px_rgba(168,85,247,0.4)]"
|
||||||
|
: "bg-transparent border-purple-300/30"
|
||||||
|
]} />
|
||||||
|
{isOngoing && (
|
||||||
|
<div class="absolute right-0 top-1.5 w-2.5 h-2.5 rounded-full translate-x-1/2 bg-purple-400/50 animate-ping" />
|
||||||
|
)}
|
||||||
|
<span class:list={[isOngoing ? "text-purple-200" : "text-white/55"]}>
|
||||||
|
{start} — {end}
|
||||||
|
</span>
|
||||||
|
<p class="font-semibold text-white text-lg mt-0.5">{exp.data.role}</p>
|
||||||
|
<p class="text-white/55 mt-0.5">
|
||||||
{exp.data.companyUrl ? (
|
{exp.data.companyUrl ? (
|
||||||
<a href={exp.data.companyUrl} target="_blank" rel="noopener noreferrer" class="text-purple-200 hover:text-white transition-colors">{exp.data.company}</a>
|
<a href={exp.data.companyUrl} target="_blank" rel="noopener noreferrer" class="text-purple-200 hover:text-white transition-colors">{exp.data.company}</a>
|
||||||
) : exp.data.company}
|
) : exp.data.company}
|
||||||
{exp.data.location && ` · ${exp.data.location}`}
|
{exp.data.location && ` · ${exp.data.location}`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span class:list={["text-xs whitespace-nowrap flex-shrink-0", isOngoing ? "text-purple-200 font-semibold" : "text-white/40"]}>
|
|
||||||
{start} — {end}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
<div class="relative pr-7 pt-2 flex items-center gap-3">
|
||||||
|
<span class="text-purple-300/40 tracking-[0.3em]">...</span>
|
||||||
|
<a href="/ar/برمجة/مسار" class="text-purple-200 hover:text-white transition-colors">
|
||||||
|
عرض المسار الكامل ←
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-12">
|
<div class="mb-20 -mx-7 px-7 py-8 bg-white/[0.03] rounded-2xl">
|
||||||
<div class="flex items-center justify-between mb-5">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<h2 class="text-2xl font-bold text-white ">المشاريع</h2>
|
<h2 class="text-2xl font-bold text-white ">المشاريع</h2>
|
||||||
<a href="/ar/برمجة/مشاريع" class="text-sm text-purple-200 hover:text-white transition-colors">← عرض الكل</a>
|
<a href="/ar/برمجة/مشاريع" class="text-sm text-purple-200 hover:text-white transition-colors">← عرض الكل</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
||||||
{projects.map((project) => (
|
{projects.map((project) => (
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
title={project.data.title}
|
title={project.data.title}
|
||||||
|
|
@ -114,12 +130,12 @@ function formatMonth(dateStr: string) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{recommendationTexts.length > 0 && (
|
{recommendationTexts.length > 0 && (
|
||||||
<div class="mb-12">
|
<div class="mb-20">
|
||||||
<div class="flex items-center justify-between mb-5">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<h2 class="text-2xl font-bold text-white ">التوصيات</h2>
|
<h2 class="text-2xl font-bold text-white ">التوصيات</h2>
|
||||||
<a href="/ar/برمجة/توصيات" class="text-sm text-purple-200 hover:text-white transition-colors">← عرض الكل</a>
|
<a href="/ar/برمجة/توصيات" class="text-sm text-purple-200 hover:text-white transition-colors">← عرض الكل</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-4">
|
<div class="space-y-5">
|
||||||
{recommendationTexts.map((rec) => (
|
{recommendationTexts.map((rec) => (
|
||||||
<FeaturedRecommendation
|
<FeaturedRecommendation
|
||||||
author={rec.data.author}
|
author={rec.data.author}
|
||||||
|
|
@ -134,86 +150,49 @@ function formatMonth(dateStr: string) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div class="mb-12">
|
<div class="mb-20">
|
||||||
<div class="flex items-center justify-between mb-5">
|
<h2 class="text-2xl font-bold text-white mb-6">القيم والمنهج</h2>
|
||||||
<h2 class="text-2xl font-bold text-white">المهارات</h2>
|
<ul class="space-y-4 text-white/80">
|
||||||
<a href="/ar/برمجة/مهارات" class="text-sm text-purple-200 hover:text-white transition-colors">← عرض الكل</a>
|
<ValueItem>حركة <Link href="http://manifesto.softwarecraftsmanship.org/#/fr-fr" external>Software Craftsmanship</Link></ValueItem>
|
||||||
</div>
|
<ValueItem>الفائدة الاجتماعية للمطوّر</ValueItem>
|
||||||
<div class="space-y-4">
|
<ValueItem>الفخر بالعمل، دون غرور</ValueItem>
|
||||||
{topSkills.map((category) => (
|
<ValueItem>منهج <strong class="text-white">Domain Driven Design</strong></ValueItem>
|
||||||
<div>
|
<ValueItem>تنظيم <Link href="https://agilemanifesto.org/iso/fr/manifesto.html" external>أجايل</Link>: التكرار والتحسين المستمر</ValueItem>
|
||||||
<p class="text-xs font-semibold text-white/40 uppercase tracking-wider mb-2">{category.name[locale as keyof typeof category.name]}</p>
|
|
||||||
<div class="flex flex-wrap gap-1.5">
|
|
||||||
{category.skills.map((skill) => (
|
|
||||||
<SkillBadge name={skill} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 class="text-2xl font-bold text-white mb-5">القيم والمنهج</h2>
|
|
||||||
<div class="facet-card rounded-2xl bg-white/[0.04] border border-white/[0.08] p-6 mb-10">
|
|
||||||
<ul class="space-y-3 text-white/70">
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-purple-400 mt-2 flex-shrink-0"></span>
|
|
||||||
<span>حركة <Link href="http://manifesto.softwarecraftsmanship.org/#/fr-fr" external>Software Craftsmanship</Link></span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-purple-400 mt-2 flex-shrink-0"></span>
|
|
||||||
<span>الفائدة الاجتماعية للمطوّر</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-purple-400 mt-2 flex-shrink-0"></span>
|
|
||||||
<span>الفخر بالعمل، دون غرور</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-purple-400 mt-2 flex-shrink-0"></span>
|
|
||||||
<span>منهج <strong class="text-white">Domain Driven Design</strong></span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-purple-400 mt-2 flex-shrink-0"></span>
|
|
||||||
<span>تنظيم <Link href="https://agilemanifesto.org/iso/fr/manifesto.html" external>أجايل</Link>: التكرار والتحسين المستمر</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="text-2xl font-bold text-white mb-5">المجتمع والتدريس</h2>
|
<div class="mb-20">
|
||||||
<div class="facet-card rounded-2xl bg-white/[0.04] border border-white/[0.08] p-6 mb-10">
|
<h2 class="text-2xl font-bold text-white mb-6">المجتمع والتدريس</h2>
|
||||||
<p class="text-white/70 leading-relaxed">
|
<p class="text-white/60 leading-relaxed">
|
||||||
أنشّط مجتمع <Link href="https://www.meetup.com/software-crafters-albi/" external>Software Crafters Albi</Link> منذ 2018. أستاذ هندسة البرمجيات في <Link href="https://www.univ-jfc.fr/" external>جامعة شامبوليون</Link> في ألبي منذ 2019.
|
أنشّط مجتمع <Link href="https://www.meetup.com/software-crafters-albi/" external>Software Crafters Albi</Link> منذ 2018. أستاذ هندسة البرمجيات في <Link href="https://www.univ-jfc.fr/" external>جامعة شامبوليون</Link> في ألبي منذ 2019.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="text-2xl font-bold text-white mb-5">على الإنترنت</h2>
|
<div class="mb-20">
|
||||||
<div class="facet-card flex flex-wrap gap-3 mb-12">
|
<h2 class="text-2xl font-bold text-white mb-6">على الإنترنت</h2>
|
||||||
{[
|
<div class="flex flex-wrap gap-x-6 gap-y-3">
|
||||||
{ label: 'LinkedIn', href: 'https://www.linkedin.com/in/jalil' },
|
<a href="https://www.linkedin.com/in/jalil" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 text-white/50 hover:text-white transition-colors">
|
||||||
{ label: 'Malt', href: 'https://www.malt.fr/profile/jalilarfaoui' },
|
<LinkedInIcon size={18} /> LinkedIn
|
||||||
{ label: 'Stack Overflow', href: 'https://stackexchange.com/users/54164/jalil' },
|
|
||||||
{ label: 'GitHub', href: 'https://github.com/JalilArfaoui' },
|
|
||||||
{ label: 'Framagit', href: 'https://framagit.org/jalil' },
|
|
||||||
{ label: 'Forge شخصية', href: 'https://forge.tiqa.fr' },
|
|
||||||
].map((link) => (
|
|
||||||
<a
|
|
||||||
href={link.href}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="inline-flex items-center gap-1.5 px-4 py-2 rounded-full bg-white/[0.06] border border-white/[0.1] text-sm text-white/70 hover:bg-white/[0.12] hover:text-white transition-all duration-200"
|
|
||||||
>
|
|
||||||
{link.label}
|
|
||||||
<svg class="w-3 h-3 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
</a>
|
||||||
))}
|
<a href="https://www.malt.fr/profile/jalilarfaoui" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 text-white/50 hover:text-white transition-colors">
|
||||||
|
<MaltIcon size={18} /> Malt
|
||||||
|
</a>
|
||||||
|
<a href="https://stackexchange.com/users/54164/jalil" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 text-white/50 hover:text-white transition-colors">
|
||||||
|
<StackOverflowIcon size={18} /> Stack Overflow
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/JalilArfaoui" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 text-white/50 hover:text-white transition-colors">
|
||||||
|
<GitHubIcon size={18} /> GitHub
|
||||||
|
</a>
|
||||||
|
<a href="https://forge.tiqa.fr" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 text-white/50 hover:text-white transition-colors">
|
||||||
|
<ForgejoIcon size={18} /> Forge شخصية
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center pt-8 border-t border-white/[0.08]">
|
<div class="text-center pt-10 border-t border-white/[0.06]">
|
||||||
<Image src={logoTiqa} alt="شعار Tiqa" class="mx-auto mb-4" width={160} />
|
<Image src={logoTiqa} alt="شعار Tiqa" class="mx-auto mb-4" width={160} />
|
||||||
<p class="text-sm text-white/40">
|
<p class="text-sm text-white/30">
|
||||||
<strong class="text-white/60">SAS Tiqa</strong><br />
|
<strong class="text-white/45">SAS Tiqa</strong><br />
|
||||||
12, rue Fabre d'Églantine — 81 000 Albi, France<br />
|
12, rue Fabre d'Églantine — 81 000 Albi, France<br />
|
||||||
811 917 871 RCS Albi
|
811 917 871 RCS Albi
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ function formatMonth(dateStr: string) {
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
title="Code - Jalil Arfaoui"
|
title="Jalil Arfaoui · Développeur freelance à Albi, artisan du logiciel"
|
||||||
facet="code"
|
facet="code"
|
||||||
description="Parcours professionnel de Jalil Arfaoui : développeur freelance spécialisé en Software Craftsmanship, TDD, DDD. TypeScript, PHP, Elixir."
|
description="Parcours professionnel de Jalil Arfaoui : développeur freelance spécialisé en Software Craftsmanship, TDD, DDD. TypeScript, PHP, Elixir."
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,14 @@ import Layout from "../../../layouts/main.astro";
|
||||||
import Link from "../../../components/Link.astro";
|
import Link from "../../../components/Link.astro";
|
||||||
import FeaturedRecommendation from "../../../components/code/FeaturedRecommendation.astro";
|
import FeaturedRecommendation from "../../../components/code/FeaturedRecommendation.astro";
|
||||||
import ProjectCard from "../../../components/code/ProjectCard.astro";
|
import ProjectCard from "../../../components/code/ProjectCard.astro";
|
||||||
import SkillBadge from "../../../components/code/SkillBadge.astro";
|
import ValueItem from "../../../components/code/ValueItem.astro";
|
||||||
|
import LinkedInIcon from "../../../components/icons/LinkedInIcon.astro";
|
||||||
|
import MaltIcon from "../../../components/icons/MaltIcon.astro";
|
||||||
|
import StackOverflowIcon from "../../../components/icons/StackOverflowIcon.astro";
|
||||||
|
import GitHubIcon from "../../../components/icons/GitHubIcon.astro";
|
||||||
|
import ForgejoIcon from "../../../components/icons/ForgejoIcon.astro";
|
||||||
import { getProjectBaseSlug, getProjectsBasePath } from "../../../utils/i18n";
|
import { getProjectBaseSlug, getProjectsBasePath } from "../../../utils/i18n";
|
||||||
import logoTiqa from "../../../assets/images/logo-tiqa-blanc.png";
|
import logoTiqa from "../../../assets/images/logo-tiqa-blanc.png";
|
||||||
import skillsData from "../../../data/skills.json";
|
|
||||||
|
|
||||||
const locale = "en";
|
const locale = "en";
|
||||||
const projectsBasePath = getProjectsBasePath(locale);
|
const projectsBasePath = getProjectsBasePath(locale);
|
||||||
|
|
@ -17,7 +21,7 @@ const experiences = (await getCollection("experiences"))
|
||||||
.filter((e) => e.data.lang === locale && !e.data.draft)
|
.filter((e) => e.data.lang === locale && !e.data.draft)
|
||||||
.sort((a, b) => (b.data.startDate > a.data.startDate ? 1 : -1));
|
.sort((a, b) => (b.data.startDate > a.data.startDate ? 1 : -1));
|
||||||
|
|
||||||
const recentExperiences = experiences.slice(0, 4);
|
const recentExperiences = experiences.filter((e) => e.data.featured).slice(0, 4);
|
||||||
|
|
||||||
const projects = (await getCollection("projects"))
|
const projects = (await getCollection("projects"))
|
||||||
.filter((p) => p.data.lang === locale && !p.data.draft && p.data.category === "dev" && p.data.featured)
|
.filter((p) => p.data.lang === locale && !p.data.draft && p.data.category === "dev" && p.data.featured)
|
||||||
|
|
@ -32,8 +36,6 @@ const recommendationTexts = recommendations.map((rec) => ({
|
||||||
text: rec.body || '',
|
text: rec.body || '',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const topSkills = skillsData.categories.slice(0, 3);
|
|
||||||
|
|
||||||
function formatMonth(dateStr: string) {
|
function formatMonth(dateStr: string) {
|
||||||
const [year, month] = dateStr.split('-');
|
const [year, month] = dateStr.split('-');
|
||||||
return new Date(parseInt(year), parseInt(month) - 1)
|
return new Date(parseInt(year), parseInt(month) - 1)
|
||||||
|
|
@ -42,14 +44,16 @@ function formatMonth(dateStr: string) {
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
title="Code - Jalil Arfaoui"
|
title="Jalil Arfaoui · Freelance developer in Albi, software craftsman"
|
||||||
facet="code"
|
facet="code"
|
||||||
description="Jalil Arfaoui's professional journey: freelance developer specializing in Software Craftsmanship, TDD, DDD. TypeScript, PHP, Elixir."
|
description="Jalil Arfaoui's professional journey: freelance developer specializing in Software Craftsmanship, TDD, DDD. TypeScript, PHP, Elixir."
|
||||||
>
|
>
|
||||||
<section class="relative z-20 max-w-3xl mx-auto my-12 px-7 lg:px-0">
|
<section class="relative z-20 max-w-3xl mx-auto my-16 px-7 lg:px-0">
|
||||||
<div class="mb-10">
|
<div class="mb-16">
|
||||||
<h1 class="text-4xl sm:text-5xl font-semibold text-white font-display tracking-wide">Software Craftsman</h1>
|
<span class="inline-block px-3 py-1 text-sm text-purple-200/80 bg-white/[0.06] border border-white/[0.08] rounded-full mb-5">Freelance developer · Albi</span>
|
||||||
<div class="mt-6 space-y-4 text-lg text-white/60 leading-relaxed max-w-2xl">
|
<h1 class="text-5xl sm:text-6xl font-semibold text-white font-display tracking-wide">Software Craftsman</h1>
|
||||||
|
<div class="mt-6 mb-6 w-16 h-0.5 bg-gradient-to-r from-purple-400 to-transparent rounded-full"></div>
|
||||||
|
<div class="space-y-4 text-lg text-white/60 leading-relaxed max-w-2xl">
|
||||||
<p>
|
<p>
|
||||||
TDD, Clean Code, Domain-Driven Design: that's how I build software. I work with teams as a senior developer, tech lead or technical coach. My stack: TypeScript/JavaScript, but also PHP and Elixir.
|
TDD, Clean Code, Domain-Driven Design: that's how I build software. I work with teams as a senior developer, tech lead or technical coach. My stack: TypeScript/JavaScript, but also PHP and Elixir.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -62,44 +66,56 @@ function formatMonth(dateStr: string) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-12">
|
<div class="mb-20 -mx-7 px-7 py-8 bg-white/[0.03] rounded-2xl">
|
||||||
<div class="flex items-center justify-between mb-5">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<h2 class="text-2xl font-bold text-white ">Career</h2>
|
<h2 class="text-2xl font-bold text-white ">Career</h2>
|
||||||
<a href="/en/code/career" class="text-sm text-purple-200 hover:text-white transition-colors">See all →</a>
|
<a href="/en/code/career" class="text-sm text-purple-200 hover:text-white transition-colors">See all →</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-3">
|
<div class="relative ml-3">
|
||||||
|
<div class="absolute left-0 top-1.5 bottom-1.5 w-px bg-purple-300/20"></div>
|
||||||
{recentExperiences.map((exp) => {
|
{recentExperiences.map((exp) => {
|
||||||
const isOngoing = !exp.data.endDate;
|
const isOngoing = !exp.data.endDate;
|
||||||
const start = formatMonth(exp.data.startDate);
|
const start = formatMonth(exp.data.startDate);
|
||||||
const end = exp.data.endDate ? formatMonth(exp.data.endDate) : 'Present';
|
const end = exp.data.endDate ? formatMonth(exp.data.endDate) : 'Present';
|
||||||
return (
|
return (
|
||||||
<div class="facet-card rounded-xl bg-white/[0.06] border border-white/[0.1] p-4 hover:bg-white/[0.1] transition-colors">
|
<div class="relative pl-7 pb-6 last:pb-0">
|
||||||
<div class="flex items-start justify-between gap-4">
|
<div class:list={[
|
||||||
<div class="min-w-0">
|
"absolute left-0 top-1.5 w-2.5 h-2.5 rounded-full -translate-x-1/2 border-2",
|
||||||
<p class="font-semibold text-white text-sm truncate">{exp.data.role}</p>
|
isOngoing
|
||||||
<p class="text-xs text-white/50 mt-0.5">
|
? "bg-purple-400 border-purple-400/50 shadow-[0_0_8px_rgba(168,85,247,0.4)]"
|
||||||
|
: "bg-transparent border-purple-300/30"
|
||||||
|
]} />
|
||||||
|
{isOngoing && (
|
||||||
|
<div class="absolute left-0 top-1.5 w-2.5 h-2.5 rounded-full -translate-x-1/2 bg-purple-400/50 animate-ping" />
|
||||||
|
)}
|
||||||
|
<span class:list={[isOngoing ? "text-purple-200" : "text-white/55"]}>
|
||||||
|
{start} — {end}
|
||||||
|
</span>
|
||||||
|
<p class="font-semibold text-white text-lg mt-0.5">{exp.data.role}</p>
|
||||||
|
<p class="text-white/55 mt-0.5">
|
||||||
{exp.data.companyUrl ? (
|
{exp.data.companyUrl ? (
|
||||||
<a href={exp.data.companyUrl} target="_blank" rel="noopener noreferrer" class="text-purple-200 hover:text-white transition-colors">{exp.data.company}</a>
|
<a href={exp.data.companyUrl} target="_blank" rel="noopener noreferrer" class="text-purple-200 hover:text-white transition-colors">{exp.data.company}</a>
|
||||||
) : exp.data.company}
|
) : exp.data.company}
|
||||||
{exp.data.location && ` · ${exp.data.location}`}
|
{exp.data.location && ` · ${exp.data.location}`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span class:list={["text-xs whitespace-nowrap flex-shrink-0", isOngoing ? "text-purple-200 font-semibold" : "text-white/40"]}>
|
|
||||||
{start} — {end}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
<div class="relative pl-7 pt-2 flex items-center gap-3">
|
||||||
|
<span class="text-purple-300/40 tracking-[0.3em]">...</span>
|
||||||
|
<a href="/en/code/career" class="text-purple-200 hover:text-white transition-colors">
|
||||||
|
See full career →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-12">
|
<div class="mb-20 -mx-7 px-7 py-8 bg-white/[0.03] rounded-2xl">
|
||||||
<div class="flex items-center justify-between mb-5">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<h2 class="text-2xl font-bold text-white ">Projects</h2>
|
<h2 class="text-2xl font-bold text-white ">Projects</h2>
|
||||||
<a href="/en/code/projects" class="text-sm text-purple-200 hover:text-white transition-colors">See all →</a>
|
<a href="/en/code/projects" class="text-sm text-purple-200 hover:text-white transition-colors">See all →</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
||||||
{projects.map((project) => (
|
{projects.map((project) => (
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
title={project.data.title}
|
title={project.data.title}
|
||||||
|
|
@ -114,12 +130,12 @@ function formatMonth(dateStr: string) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{recommendationTexts.length > 0 && (
|
{recommendationTexts.length > 0 && (
|
||||||
<div class="mb-12">
|
<div class="mb-20">
|
||||||
<div class="flex items-center justify-between mb-5">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<h2 class="text-2xl font-bold text-white ">Recommendations</h2>
|
<h2 class="text-2xl font-bold text-white ">Recommendations</h2>
|
||||||
<a href="/en/code/recommendations" class="text-sm text-purple-200 hover:text-white transition-colors">See all →</a>
|
<a href="/en/code/recommendations" class="text-sm text-purple-200 hover:text-white transition-colors">See all →</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-4">
|
<div class="space-y-5">
|
||||||
{recommendationTexts.map((rec) => (
|
{recommendationTexts.map((rec) => (
|
||||||
<FeaturedRecommendation
|
<FeaturedRecommendation
|
||||||
author={rec.data.author}
|
author={rec.data.author}
|
||||||
|
|
@ -134,86 +150,49 @@ function formatMonth(dateStr: string) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div class="mb-12">
|
<div class="mb-20">
|
||||||
<div class="flex items-center justify-between mb-5">
|
<h2 class="text-2xl font-bold text-white mb-6">Values & Approach</h2>
|
||||||
<h2 class="text-2xl font-bold text-white">Skills</h2>
|
<ul class="space-y-4 text-white/80">
|
||||||
<a href="/en/code/skills" class="text-sm text-purple-200 hover:text-white transition-colors">See all →</a>
|
<ValueItem>The <Link href="http://manifesto.softwarecraftsmanship.org/#/en" external>Software Craftsmanship</Link> movement</ValueItem>
|
||||||
</div>
|
<ValueItem>The social usefulness of the developer</ValueItem>
|
||||||
<div class="space-y-4">
|
<ValueItem>Taking pride in your work, without ego</ValueItem>
|
||||||
{topSkills.map((category) => (
|
<ValueItem><strong class="text-white">Domain Driven Design</strong> approach</ValueItem>
|
||||||
<div>
|
<ValueItem><Link href="https://agilemanifesto.org/iso/en/manifesto.html" external>Agile</Link> organization: iteration and continuous improvement</ValueItem>
|
||||||
<p class="text-xs font-semibold text-white/40 uppercase tracking-wider mb-2">{category.name[locale as keyof typeof category.name]}</p>
|
|
||||||
<div class="flex flex-wrap gap-1.5">
|
|
||||||
{category.skills.map((skill) => (
|
|
||||||
<SkillBadge name={skill} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 class="text-2xl font-bold text-white mb-5">Values & Approach</h2>
|
|
||||||
<div class="facet-card rounded-2xl bg-white/[0.04] border border-white/[0.08] p-6 mb-10">
|
|
||||||
<ul class="space-y-3 text-white/70">
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-purple-400 mt-2 flex-shrink-0"></span>
|
|
||||||
<span>The <Link href="http://manifesto.softwarecraftsmanship.org/#/en" external>Software Craftsmanship</Link> movement</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-purple-400 mt-2 flex-shrink-0"></span>
|
|
||||||
<span>The social usefulness of the developer</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-purple-400 mt-2 flex-shrink-0"></span>
|
|
||||||
<span>Taking pride in your work, without ego</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-purple-400 mt-2 flex-shrink-0"></span>
|
|
||||||
<span><strong class="text-white">Domain Driven Design</strong> approach</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex items-start gap-3">
|
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-purple-400 mt-2 flex-shrink-0"></span>
|
|
||||||
<span><Link href="https://agilemanifesto.org/iso/en/manifesto.html" external>Agile</Link> organization: iteration and continuous improvement</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="text-2xl font-bold text-white mb-5">Community & Teaching</h2>
|
<div class="mb-20">
|
||||||
<div class="facet-card rounded-2xl bg-white/[0.04] border border-white/[0.08] p-6 mb-10">
|
<h2 class="text-2xl font-bold text-white mb-6">Community & Teaching</h2>
|
||||||
<p class="text-white/70 leading-relaxed">
|
<p class="text-white/60 leading-relaxed">
|
||||||
I've been running the <Link href="https://www.meetup.com/software-crafters-albi/" external>Software Crafters Albi</Link> meetup since 2018. Software engineering professor at <Link href="https://www.univ-jfc.fr/" external>Université Champollion</Link> in Albi since 2019.
|
I've been running the <Link href="https://www.meetup.com/software-crafters-albi/" external>Software Crafters Albi</Link> meetup since 2018. Software engineering professor at <Link href="https://www.univ-jfc.fr/" external>Université Champollion</Link> in Albi since 2019.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="text-2xl font-bold text-white mb-5">Online</h2>
|
<div class="mb-20">
|
||||||
<div class="facet-card flex flex-wrap gap-3 mb-12">
|
<h2 class="text-2xl font-bold text-white mb-6">Online</h2>
|
||||||
{[
|
<div class="flex flex-wrap gap-x-6 gap-y-3">
|
||||||
{ label: 'LinkedIn', href: 'https://www.linkedin.com/in/jalil' },
|
<a href="https://www.linkedin.com/in/jalil" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 text-white/50 hover:text-white transition-colors">
|
||||||
{ label: 'Malt', href: 'https://www.malt.fr/profile/jalilarfaoui' },
|
<LinkedInIcon size={18} /> LinkedIn
|
||||||
{ label: 'Stack Overflow', href: 'https://stackexchange.com/users/54164/jalil' },
|
|
||||||
{ label: 'GitHub', href: 'https://github.com/JalilArfaoui' },
|
|
||||||
{ label: 'Framagit', href: 'https://framagit.org/jalil' },
|
|
||||||
{ label: 'Personal forge', href: 'https://forge.tiqa.fr' },
|
|
||||||
].map((link) => (
|
|
||||||
<a
|
|
||||||
href={link.href}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="inline-flex items-center gap-1.5 px-4 py-2 rounded-full bg-white/[0.06] border border-white/[0.1] text-sm text-white/70 hover:bg-white/[0.12] hover:text-white transition-all duration-200"
|
|
||||||
>
|
|
||||||
{link.label}
|
|
||||||
<svg class="w-3 h-3 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
</a>
|
||||||
))}
|
<a href="https://www.malt.fr/profile/jalilarfaoui" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 text-white/50 hover:text-white transition-colors">
|
||||||
|
<MaltIcon size={18} /> Malt
|
||||||
|
</a>
|
||||||
|
<a href="https://stackexchange.com/users/54164/jalil" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 text-white/50 hover:text-white transition-colors">
|
||||||
|
<StackOverflowIcon size={18} /> Stack Overflow
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/JalilArfaoui" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 text-white/50 hover:text-white transition-colors">
|
||||||
|
<GitHubIcon size={18} /> GitHub
|
||||||
|
</a>
|
||||||
|
<a href="https://forge.tiqa.fr" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 text-white/50 hover:text-white transition-colors">
|
||||||
|
<ForgejoIcon size={18} /> Personal forge
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center pt-8 border-t border-white/[0.08]">
|
<div class="text-center pt-10 border-t border-white/[0.06]">
|
||||||
<Image src={logoTiqa} alt="Tiqa logo" class="mx-auto mb-4" width={160} />
|
<Image src={logoTiqa} alt="Tiqa logo" class="mx-auto mb-4" width={160} />
|
||||||
<p class="text-sm text-white/40">
|
<p class="text-sm text-white/30">
|
||||||
<strong class="text-white/60">SAS Tiqa</strong><br />
|
<strong class="text-white/45">SAS Tiqa</strong><br />
|
||||||
12, rue Fabre d'Églantine — 81 000 Albi, France<br />
|
12, rue Fabre d'Églantine — 81 000 Albi, France<br />
|
||||||
811 917 871 RCS Albi
|
811 917 871 RCS Albi
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue