Compare commits

...

4 commits

9 changed files with 260 additions and 239 deletions

View file

@ -33,7 +33,7 @@ const initials = author
---
<blockquote class="facet-card rounded-xl bg-white/[0.06] border border-white/[0.1] p-5 hover:bg-white/[0.1] transition-colors duration-200">
<p class="text-sm text-white/70 leading-relaxed italic">
<p class="text-white/70 leading-relaxed italic">
"{truncated}"
</p>
<div class="mt-3 flex items-center gap-2.5">
@ -50,7 +50,7 @@ const initials = author
<span class="text-[10px] font-bold text-white/70">{initials}</span>
</div>
)}
<cite class="not-italic text-xs">
<cite class="not-italic text-sm">
{url ? (
<a href={url} target="_blank" rel="noopener noreferrer" class="font-semibold text-white/90 hover:text-purple-200 transition-colors">
{author}

View file

@ -0,0 +1,12 @@
---
interface Props {
size?: number;
class?: string;
}
const { size = 20, class: className = '' } = Astro.props;
---
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" class={className}>
<path d="M16.7773 0c1.6018 0 2.9004 1.2986 2.9004 2.9005s-1.2986 2.9004-2.9004 2.9004c-1.0854 0-2.0315-.596-2.5288-1.4787H12.91c-2.3322 0-4.2272 1.8718-4.2649 4.195l-.0007 2.1175a7.0759 7.0759 0 0 1 4.148-1.4205l.1176-.001 1.3385.0002c.4973-.8827 1.4434-1.4788 2.5288-1.4788 1.6018 0 2.9004 1.2986 2.9004 2.9005s-1.2986 2.9004-2.9004 2.9004c-1.0854 0-2.0315-.596-2.5288-1.4787H12.91c-2.3322 0-4.2272 1.8718-4.2649 4.195l-.0007 2.319c.8827.4973 1.4788 1.4434 1.4788 2.5287 0 1.602-1.2986 2.9005-2.9005 2.9005-1.6018 0-2.9004-1.2986-2.9004-2.9005 0-1.0853.596-2.0314 1.4788-2.5287l-.0002-9.9831c0-3.887 3.1195-7.0453 6.9915-7.108l.1176-.001h1.3385C14.7458.5962 15.692 0 16.7773 0ZM7.2227 19.9052c-.6596 0-1.1943.5347-1.1943 1.1943s.5347 1.1943 1.1943 1.1943 1.1944-.5347 1.1944-1.1943-.5348-1.1943-1.1944-1.1943Zm9.5546-10.4644c-.6596 0-1.1944.5347-1.1944 1.1943s.5348 1.1943 1.1944 1.1943c.6596 0 1.1943-.5347 1.1943-1.1943s-.5347-1.1943-1.1943-1.1943Zm0-7.7346c-.6596 0-1.1944.5347-1.1944 1.1943s.5348 1.1943 1.1944 1.1943c.6596 0 1.1943-.5347 1.1943-1.1943s-.5347-1.1943-1.1943-1.1943Z"/>
</svg>

View file

@ -0,0 +1,12 @@
---
interface Props {
size?: number;
class?: string;
}
const { size = 20, class: className = '' } = Astro.props;
---
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" class={className}>
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>
</svg>

View file

@ -0,0 +1,12 @@
---
interface Props {
size?: number;
class?: string;
}
const { size = 20, class: className = '' } = Astro.props;
---
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" class={className}>
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
</svg>

View file

@ -0,0 +1,12 @@
---
interface Props {
size?: number;
class?: string;
}
const { size = 20, class: className = '' } = Astro.props;
---
<svg width={size} height={size} viewBox="-0.2 9.5 6.5 6.5" fill="currentColor" class={className}>
<path d="M3.499 13.563l-.21.21.619.618c.304.304.79.598 1.244.144.339-.34.26-.695.073-.98-.06.004-1.726.008-1.726.008zm-.963-2.325.21-.21-.608-.607c-.304-.303-.765-.621-1.243-.143-.351.35-.273.692-.087.97Zm2.86.416c-.037.043-1.511 1.524-1.511 1.524h1.154c.43 0 .981-.101.981-.777 0-.496-.296-.683-.624-.747zm-3.244-.031H.981c-.43 0-.981.135-.981.778 0 .479.307.676.641.745.04-.046 1.511-1.523 1.511-1.523zm1.484 3.04-.618-.618-.608.607a2.613 2.613 0 0 1-.137.128c.07.333.266.639.745.639s.676-.307.745-.641c-.043-.037-.085-.073-.127-.115zM2.41 10.15l.608.607.618-.618a2.25 2.25 0 0 1 .128-.118c-.065-.327-.251-.623-.747-.623s-.682.297-.746.625c.046.04.092.08.14.127zm2.742.117c-.455-.454-.94-.16-1.244.144l-2.87 2.87c-.303.303-.621.765-.143 1.243.478.478.94.16 1.243-.143l2.87-2.87c.304-.304.598-.79.144-1.244Z"/>
</svg>

View file

@ -0,0 +1,12 @@
---
interface Props {
size?: number;
class?: string;
}
const { size = 20, class: className = '' } = Astro.props;
---
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" class={className}>
<path d="M15.725 0l-1.72 1.277 6.39 8.588 1.72-1.277L15.725 0zm-3.94 3.418l-1.369 1.644 8.225 6.85 1.369-1.644-8.225-6.85zm-3.15 4.465l-.905 1.94 9.702 4.517.905-1.94-9.702-4.517zm-1.85 4.86l-.44 2.093 10.473 2.201.44-2.092-10.473-2.203zM1.89 15.47V24h19.19v-8.53h-2.133v6.397H4.021v-6.396H1.89zm4.265 2.133v2.13h10.66v-2.13H6.154z"/>
</svg>

View file

@ -5,10 +5,14 @@ import Layout from "../../../layouts/main.astro";
import Link from "../../../components/Link.astro";
import FeaturedRecommendation from "../../../components/code/FeaturedRecommendation.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 logoTiqa from "../../../assets/images/logo-tiqa-blanc.png";
import skillsData from "../../../data/skills.json";
const locale = "ar";
const projectsBasePath = getProjectsBasePath(locale);
@ -17,7 +21,7 @@ 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 recentExperiences = experiences.slice(0, 4);
const recentExperiences = experiences.filter((e) => e.data.featured).slice(0, 4);
const projects = (await getCollection("projects"))
.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 || '',
}));
const topSkills = skillsData.categories.slice(0, 3);
function formatMonth(dateStr: string) {
const [year, month] = dateStr.split('-');
return new Date(parseInt(year), parseInt(month) - 1)
@ -42,14 +44,16 @@ function formatMonth(dateStr: string) {
---
<Layout
title="برمجة - جليل عرفاوي"
title="جليل عرفاوي · مطوّر مستقل في ألبي، حِرَفيّ البرمجيات"
facet="code"
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">
<div class="mb-10">
<h1 class="text-4xl sm:text-5xl font-semibold text-white font-display tracking-wide">حِرَفيّ البرمجيات</h1>
<div class="mt-6 space-y-4 text-lg text-white/60 leading-relaxed max-w-2xl">
<section dir="rtl" lang="ar" class="relative z-20 max-w-3xl mx-auto my-16 px-7 lg:px-0">
<div class="mb-16">
<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>
<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>
TDD، Clean Code، Domain-Driven Design: هذه طريقتي في بناء البرمجيات. أرافق الفرق كمطوّر أول، أو قائد تقني، أو مدرب تقني. أدواتي: TypeScript/JavaScript، وكذلك PHP وElixir.
</p>
@ -62,44 +66,56 @@ function formatMonth(dateStr: string) {
</div>
</div>
<div class="mb-12">
<div class="flex items-center justify-between mb-5">
<h2 class="text-2xl font-bold text-white">المسار</h2>
<div class="mb-20 -mx-7 px-7 py-8 bg-white/[0.03] rounded-2xl">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-white ">المسار</h2>
<a href="/ar/برمجة/مسار" class="text-sm text-purple-200 hover:text-white transition-colors">&larr; عرض الكل</a>
</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) => {
const isOngoing = !exp.data.endDate;
const start = formatMonth(exp.data.startDate);
const end = exp.data.endDate ? formatMonth(exp.data.endDate) : 'الحالي';
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="flex items-start justify-between gap-4">
<div class="min-w-0">
<p class="font-semibold text-white text-sm truncate">{exp.data.role}</p>
<p class="text-xs text-white/50 mt-0.5">
{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>
) : exp.data.company}
{exp.data.location && ` · ${exp.data.location}`}
</p>
</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 class="relative pr-7 pb-6 last:pb-0">
<div class:list={[
"absolute right-0 top-1.5 w-2.5 h-2.5 rounded-full translate-x-1/2 border-2",
isOngoing
? "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 ? (
<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.location && ` · ${exp.data.location}`}
</p>
</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">
عرض المسار الكامل &larr;
</a>
</div>
</div>
</div>
<div class="mb-12">
<div class="flex items-center justify-between mb-5">
<h2 class="text-2xl font-bold text-white">المشاريع</h2>
<div class="mb-20 -mx-7 px-7 py-8 bg-white/[0.03] rounded-2xl">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-white ">المشاريع</h2>
<a href="/ar/برمجة/مشاريع" class="text-sm text-purple-200 hover:text-white transition-colors">&larr; عرض الكل</a>
</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) => (
<ProjectCard
title={project.data.title}
@ -114,12 +130,12 @@ function formatMonth(dateStr: string) {
</div>
{recommendationTexts.length > 0 && (
<div class="mb-12">
<div class="flex items-center justify-between mb-5">
<h2 class="text-2xl font-bold text-white">التوصيات</h2>
<div class="mb-20">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-white ">التوصيات</h2>
<a href="/ar/برمجة/توصيات" class="text-sm text-purple-200 hover:text-white transition-colors">&larr; عرض الكل</a>
</div>
<div class="space-y-4">
<div class="space-y-5">
{recommendationTexts.map((rec) => (
<FeaturedRecommendation
author={rec.data.author}
@ -134,86 +150,49 @@ function formatMonth(dateStr: string) {
</div>
)}
<div class="mb-12">
<div class="flex items-center justify-between mb-5">
<h2 class="text-2xl font-bold text-white">المهارات</h2>
<a href="/ar/برمجة/مهارات" class="text-sm text-purple-200 hover:text-white transition-colors">&larr; عرض الكل</a>
</div>
<div class="space-y-4">
{topSkills.map((category) => (
<div>
<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>
<div class="mb-20">
<h2 class="text-2xl font-bold text-white mb-6">القيم والمنهج</h2>
<ul class="space-y-4 text-white/80">
<ValueItem>حركة <Link href="http://manifesto.softwarecraftsmanship.org/#/fr-fr" external>Software Craftsmanship</Link></ValueItem>
<ValueItem>الفائدة الاجتماعية للمطوّر</ValueItem>
<ValueItem>الفخر بالعمل، دون غرور</ValueItem>
<ValueItem>منهج <strong class="text-white">Domain Driven Design</strong></ValueItem>
<ValueItem>تنظيم <Link href="https://agilemanifesto.org/iso/fr/manifesto.html" external>أجايل</Link>: التكرار والتحسين المستمر</ValueItem>
</ul>
</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">
<p class="text-white/70 leading-relaxed">
<div class="mb-20">
<h2 class="text-2xl font-bold text-white mb-6">المجتمع والتدريس</h2>
<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.
</p>
</div>
<h2 class="text-2xl font-bold text-white mb-5">على الإنترنت</h2>
<div class="facet-card flex flex-wrap gap-3 mb-12">
{[
{ label: 'LinkedIn', href: 'https://www.linkedin.com/in/jalil' },
{ label: 'Malt', href: 'https://www.malt.fr/profile/jalilarfaoui' },
{ 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>
<div class="mb-20">
<h2 class="text-2xl font-bold text-white mb-6">على الإنترنت</h2>
<div class="flex flex-wrap gap-x-6 gap-y-3">
<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">
<LinkedInIcon size={18} /> LinkedIn
</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 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} />
<p class="text-sm text-white/40">
<strong class="text-white/60">SAS Tiqa</strong><br />
<p class="text-sm text-white/30">
<strong class="text-white/45">SAS Tiqa</strong><br />
12, rue Fabre d'Églantine — 81 000 Albi, France<br />
811 917 871 RCS Albi
</p>

View file

@ -6,6 +6,11 @@ import Link from "../../components/Link.astro";
import FeaturedRecommendation from "../../components/code/FeaturedRecommendation.astro";
import ProjectCard from "../../components/code/ProjectCard.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 logoTiqa from "../../assets/images/logo-tiqa-blanc.png";
@ -39,7 +44,7 @@ function formatMonth(dateStr: string) {
---
<Layout
title="Code - Jalil Arfaoui"
title="Jalil Arfaoui · Développeur freelance à Albi, artisan du logiciel"
facet="code"
description="Parcours professionnel de Jalil Arfaoui : développeur freelance spécialisé en Software Craftsmanship, TDD, DDD. TypeScript, PHP, Elixir."
>
@ -61,7 +66,7 @@ function formatMonth(dateStr: string) {
</div>
</div>
<div class="mb-16">
<div class="mb-20 -mx-7 px-7 py-8 bg-white/[0.03] rounded-2xl">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-white ">Parcours</h2>
<a href="/code/parcours" class="text-sm text-purple-200 hover:text-white transition-colors">Voir tout &rarr;</a>
@ -105,7 +110,7 @@ function formatMonth(dateStr: string) {
</div>
</div>
<div class="mb-16">
<div class="mb-20 -mx-7 px-7 py-8 bg-white/[0.03] rounded-2xl">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-white ">Projets</h2>
<a href="/code/projets" class="text-sm text-purple-200 hover:text-white transition-colors">Voir tous &rarr;</a>
@ -125,7 +130,7 @@ function formatMonth(dateStr: string) {
</div>
{recommendationTexts.length > 0 && (
<div class="mb-16">
<div class="mb-20">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-white ">Recommandations</h2>
<a href="/code/recommandations" class="text-sm text-purple-200 hover:text-white transition-colors">Voir toutes &rarr;</a>
@ -145,7 +150,7 @@ function formatMonth(dateStr: string) {
</div>
)}
<div class="mb-16">
<div class="mb-20">
<h2 class="text-2xl font-bold text-white mb-6">Valeurs & Approche</h2>
<ul class="space-y-4 text-white/80">
<ValueItem>Le mouvement <Link href="http://manifesto.softwarecraftsmanship.org/#/fr-fr" external>Software Craftsmanship</Link></ValueItem>
@ -156,33 +161,31 @@ function formatMonth(dateStr: string) {
</ul>
</div>
<div class="mb-16">
<div class="mb-20">
<h2 class="text-2xl font-bold text-white mb-6">Communauté & Enseignement</h2>
<p class="text-white/60 leading-relaxed">
J'anime les <Link href="https://www.meetup.com/software-crafters-albi/" external>Software Crafters d'Albi</Link> depuis 2018. Enseignant en génie logiciel à <Link href="https://www.univ-jfc.fr/" external>l'université Champollion</Link> d'Albi depuis 2019.
</p>
</div>
<div class="mb-16">
<div class="mb-20">
<h2 class="text-2xl font-bold text-white mb-6">En ligne</h2>
<div class="flex flex-wrap gap-x-6 gap-y-2">
{[
{ label: 'LinkedIn', href: 'https://www.linkedin.com/in/jalil' },
{ label: 'Malt', href: 'https://www.malt.fr/profile/jalilarfaoui' },
{ 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 personnelle', href: 'https://forge.tiqa.fr' },
].map((link) => (
<a
href={link.href}
target="_blank"
rel="noopener noreferrer"
class="text-sm text-white/50 hover:text-white transition-colors py-1"
>
{link.label}
</a>
))}
<div class="flex flex-wrap gap-x-6 gap-y-3">
<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">
<LinkedInIcon size={18} /> LinkedIn
</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 personnelle
</a>
</div>
</div>

View file

@ -5,10 +5,14 @@ import Layout from "../../../layouts/main.astro";
import Link from "../../../components/Link.astro";
import FeaturedRecommendation from "../../../components/code/FeaturedRecommendation.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 logoTiqa from "../../../assets/images/logo-tiqa-blanc.png";
import skillsData from "../../../data/skills.json";
const locale = "en";
const projectsBasePath = getProjectsBasePath(locale);
@ -17,7 +21,7 @@ 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 recentExperiences = experiences.slice(0, 4);
const recentExperiences = experiences.filter((e) => e.data.featured).slice(0, 4);
const projects = (await getCollection("projects"))
.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 || '',
}));
const topSkills = skillsData.categories.slice(0, 3);
function formatMonth(dateStr: string) {
const [year, month] = dateStr.split('-');
return new Date(parseInt(year), parseInt(month) - 1)
@ -42,14 +44,16 @@ function formatMonth(dateStr: string) {
---
<Layout
title="Code - Jalil Arfaoui"
title="Jalil Arfaoui · Freelance developer in Albi, software craftsman"
facet="code"
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">
<div class="mb-10">
<h1 class="text-4xl sm:text-5xl font-semibold text-white font-display tracking-wide">Software Craftsman</h1>
<div class="mt-6 space-y-4 text-lg text-white/60 leading-relaxed max-w-2xl">
<section class="relative z-20 max-w-3xl mx-auto my-16 px-7 lg:px-0">
<div class="mb-16">
<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>
<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>
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>
@ -62,44 +66,56 @@ function formatMonth(dateStr: string) {
</div>
</div>
<div class="mb-12">
<div class="flex items-center justify-between mb-5">
<h2 class="text-2xl font-bold text-white">Career</h2>
<div class="mb-20 -mx-7 px-7 py-8 bg-white/[0.03] rounded-2xl">
<div class="flex items-center justify-between mb-6">
<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 &rarr;</a>
</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) => {
const isOngoing = !exp.data.endDate;
const start = formatMonth(exp.data.startDate);
const end = exp.data.endDate ? formatMonth(exp.data.endDate) : 'Present';
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="flex items-start justify-between gap-4">
<div class="min-w-0">
<p class="font-semibold text-white text-sm truncate">{exp.data.role}</p>
<p class="text-xs text-white/50 mt-0.5">
{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>
) : exp.data.company}
{exp.data.location && ` · ${exp.data.location}`}
</p>
</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 class="relative pl-7 pb-6 last:pb-0">
<div class:list={[
"absolute left-0 top-1.5 w-2.5 h-2.5 rounded-full -translate-x-1/2 border-2",
isOngoing
? "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 ? (
<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.location && ` · ${exp.data.location}`}
</p>
</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 &rarr;
</a>
</div>
</div>
</div>
<div class="mb-12">
<div class="flex items-center justify-between mb-5">
<h2 class="text-2xl font-bold text-white">Projects</h2>
<div class="mb-20 -mx-7 px-7 py-8 bg-white/[0.03] rounded-2xl">
<div class="flex items-center justify-between mb-6">
<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 &rarr;</a>
</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) => (
<ProjectCard
title={project.data.title}
@ -114,12 +130,12 @@ function formatMonth(dateStr: string) {
</div>
{recommendationTexts.length > 0 && (
<div class="mb-12">
<div class="flex items-center justify-between mb-5">
<h2 class="text-2xl font-bold text-white">Recommendations</h2>
<div class="mb-20">
<div class="flex items-center justify-between mb-6">
<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 &rarr;</a>
</div>
<div class="space-y-4">
<div class="space-y-5">
{recommendationTexts.map((rec) => (
<FeaturedRecommendation
author={rec.data.author}
@ -134,86 +150,49 @@ function formatMonth(dateStr: string) {
</div>
)}
<div class="mb-12">
<div class="flex items-center justify-between mb-5">
<h2 class="text-2xl font-bold text-white">Skills</h2>
<a href="/en/code/skills" class="text-sm text-purple-200 hover:text-white transition-colors">See all &rarr;</a>
</div>
<div class="space-y-4">
{topSkills.map((category) => (
<div>
<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>
<div class="mb-20">
<h2 class="text-2xl font-bold text-white mb-6">Values & Approach</h2>
<ul class="space-y-4 text-white/80">
<ValueItem>The <Link href="http://manifesto.softwarecraftsmanship.org/#/en" external>Software Craftsmanship</Link> movement</ValueItem>
<ValueItem>The social usefulness of the developer</ValueItem>
<ValueItem>Taking pride in your work, without ego</ValueItem>
<ValueItem><strong class="text-white">Domain Driven Design</strong> approach</ValueItem>
<ValueItem><Link href="https://agilemanifesto.org/iso/en/manifesto.html" external>Agile</Link> organization: iteration and continuous improvement</ValueItem>
</ul>
</div>
<h2 class="text-2xl font-bold text-white mb-5">Community & Teaching</h2>
<div class="facet-card rounded-2xl bg-white/[0.04] border border-white/[0.08] p-6 mb-10">
<p class="text-white/70 leading-relaxed">
<div class="mb-20">
<h2 class="text-2xl font-bold text-white mb-6">Community & Teaching</h2>
<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.
</p>
</div>
<h2 class="text-2xl font-bold text-white mb-5">Online</h2>
<div class="facet-card flex flex-wrap gap-3 mb-12">
{[
{ label: 'LinkedIn', href: 'https://www.linkedin.com/in/jalil' },
{ label: 'Malt', href: 'https://www.malt.fr/profile/jalilarfaoui' },
{ 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>
<div class="mb-20">
<h2 class="text-2xl font-bold text-white mb-6">Online</h2>
<div class="flex flex-wrap gap-x-6 gap-y-3">
<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">
<LinkedInIcon size={18} /> LinkedIn
</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 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} />
<p class="text-sm text-white/40">
<strong class="text-white/60">SAS Tiqa</strong><br />
<p class="text-sm text-white/30">
<strong class="text-white/45">SAS Tiqa</strong><br />
12, rue Fabre d'Églantine — 81 000 Albi, France<br />
811 917 871 RCS Albi
</p>