Compare commits

..

5 commits

12 changed files with 87 additions and 59 deletions

View file

@ -14,28 +14,35 @@ const { title, description, technologies, featured = false, detailUrl, lang = 'f
const isRtl = lang === 'ar';
const readMoreLabel = t('projects', 'readMore', lang);
const primaryTechs = technologies?.slice(0, 3) ?? [];
const secondaryTechs = technologies?.slice(3) ?? [];
---
<a href={detailUrl} class:list={[
"facet-card rounded-2xl border p-6 flex flex-col h-full transition-all duration-300 hover:scale-[1.02] no-underline",
"project-card group relative rounded-2xl border p-6 flex flex-col h-full transition-all duration-300 hover:scale-[1.02] no-underline overflow-hidden",
featured
? "bg-white/[0.12] border-purple-300/20 hover:bg-white/[0.18] hover:border-purple-300/30"
: "bg-white/[0.06] border-white/[0.1] hover:bg-white/[0.12] hover:border-white/[0.2]",
? "bg-white/[0.10] border-purple-300/20"
: "bg-white/[0.05] border-white/[0.08]",
]}>
<div class="mb-2">
<h3 class="text-lg font-bold text-white">
<div class="mb-3">
<h3 class="text-xl font-bold text-white">
{title}
</h3>
</div>
<p class="text-sm text-white/60 leading-relaxed flex-1 mb-4">
<p class="text-white/60 leading-relaxed flex-1 mb-5">
{description}
</p>
{technologies && technologies.length > 0 && (
<div class="flex flex-wrap gap-1.5 mb-4">
{technologies.map((tech) => (
<span class="inline-block px-2 py-0.5 text-xs rounded-full bg-white/[0.08] text-white/60 border border-white/[0.08]">
{(primaryTechs.length > 0 || secondaryTechs.length > 0) && (
<div class="flex flex-wrap gap-1.5 mb-5">
{primaryTechs.map((tech) => (
<span class="inline-block px-2.5 py-1 text-xs font-medium rounded-full bg-purple-400/15 text-purple-200 border border-purple-300/15">
{tech}
</span>
))}
{secondaryTechs.map((tech) => (
<span class="inline-block px-2.5 py-1 text-xs rounded-full bg-white/[0.05] text-white/40 border border-white/[0.06]">
{tech}
</span>
))}
@ -50,3 +57,11 @@ const readMoreLabel = t('projects', 'readMore', lang);
)}
</span>
</a>
<style>
.project-card:hover {
background: rgba(255, 255, 255, 0.12);
border-color: rgba(168, 85, 247, 0.25);
box-shadow: 0 0 24px rgba(168, 85, 247, 0.08), 0 4px 16px rgba(0, 0, 0, 0.2);
}
</style>

View file

@ -0,0 +1,7 @@
---
---
<li class="flex items-start gap-4">
<span class="w-4 h-0.5 bg-purple-400/60 mt-3 flex-shrink-0"></span>
<span><slot /></span>
</li>

View file

@ -7,6 +7,7 @@ startDate: "2022-01"
endDate: "2023-01"
technologies: ["TypeScript", "React.js", "Redux", "Nx", "Vite"]
type: "freelance"
featured: true
lang: "ar"
---

View file

@ -7,6 +7,7 @@ startDate: "2022-01"
endDate: "2023-01"
technologies: ["TypeScript", "React.js", "Redux", "Nx", "Vite"]
type: "freelance"
featured: true
lang: "en"
---

View file

@ -7,6 +7,7 @@ startDate: "2022-01"
endDate: "2023-01"
technologies: ["TypeScript", "React.js", "Redux", "Nx", "Vite"]
type: "freelance"
featured: true
lang: "fr"
---

View file

@ -6,6 +6,7 @@ location: "ألبي"
startDate: "2019-09"
technologies: ["TypeScript", "JavaScript", "Node.js", "TDD", "Clean Code"]
type: "teaching"
featured: true
lang: "ar"
---

View file

@ -6,6 +6,7 @@ location: "Albi"
startDate: "2019-09"
technologies: ["TypeScript", "JavaScript", "Node.js", "TDD", "Clean Code"]
type: "teaching"
featured: true
lang: "en"
---

View file

@ -6,6 +6,7 @@ location: "Albi"
startDate: "2019-09"
technologies: ["TypeScript", "JavaScript", "Node.js", "TDD", "Clean Code"]
type: "teaching"
featured: true
lang: "fr"
---

View file

@ -17,8 +17,8 @@ const recommendationCount = (await getCollection("recommendations")).length;
---
<Layout
title="جليل عرفاوي - حِرَفي برمجة • ممثل ارتجالي • مصوّر"
description="جليل عرفاوي: مطوّر مستقل (Software Craftsmanship، TDD، DDD)، ممثل ارتجالي ومصوّر مقيم في ألبي، فرنسا."
title="جليل عرفاوي - مطوّر • مصوّر • ممثل ارتجالي"
description="جليل عرفاوي: مطوّر مستقل (Software Craftsmanship، TDD، DDD)، مصوّر وممثل مقيم في ألبي، فرنسا."
>
<!-- Hero Section -->
<div dir="rtl" lang="ar" class="relative z-20 w-full max-w-6xl mx-auto mt-16 px-7 md:mt-24 lg:mt-32 xl:px-0">
@ -28,7 +28,7 @@ const recommendationCount = (await getCollection("recommendations")).length;
جليل عرفاوي
</h1>
<h2 class="mb-6 text-xl font-medium text-neutral-600 dark:text-neutral-300 md:text-2xl">
حِرَفي برمجة • ممثل ارتجالي • مصوّر
مطوّر • مصوّر • ممثل
</h2>
<p class="mb-8 text-lg text-neutral-600 dark:text-neutral-400 leading-relaxed">
أكتب الكود من أجل عالم أكثر عدلًا، وأصعد على الخشبة لأجل الباقي. أو العكس.

View file

@ -5,6 +5,7 @@ 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 ValueItem from "../../components/code/ValueItem.astro";
import { getProjectBaseSlug, getProjectsBasePath } from "../../utils/i18n";
import logoTiqa from "../../assets/images/logo-tiqa-blanc.png";
@ -15,7 +16,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)
@ -44,8 +45,10 @@ function formatMonth(dateStr: string) {
>
<section class="relative z-20 max-w-3xl mx-auto my-16 px-7 lg:px-0">
<div class="mb-16">
<h1 class="text-4xl sm:text-5xl font-semibold text-white font-display tracking-wide">Artisan du logiciel</h1>
<div class="mt-6 space-y-4 text-lg text-white/60 leading-relaxed max-w-2xl">
<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">Développeur freelance · Albi</span>
<h1 class="text-5xl sm:text-6xl font-semibold text-white font-display tracking-wide">Artisan du logiciel</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 : c'est ma façon de construire du logiciel. J'accompagne les équipes comme développeur senior, tech lead ou coach technique. Ma stack : TypeScript/JavaScript, mais aussi PHP et Elixir.
</p>
@ -63,30 +66,42 @@ function formatMonth(dateStr: string) {
<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>
</div>
<div class="divide-y divide-white/[0.08]">
<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) : 'Présent';
return (
<div class="py-4 first:pt-0 last:pb-0">
<div class="flex items-start justify-between gap-4">
<div class="min-w-0">
<p class="font-semibold text-white text-sm">{exp.data.role}</p>
<p class="text-sm text-white/45 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-sm whitespace-nowrap flex-shrink-0", isOngoing ? "text-purple-200" : "text-white/35"]}>
{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="/code/parcours" class="text-purple-200 hover:text-white transition-colors">
Voir le parcours complet &rarr;
</a>
</div>
</div>
</div>
@ -132,27 +147,12 @@ function formatMonth(dateStr: string) {
<div class="mb-16">
<h2 class="text-2xl font-bold text-white mb-6">Valeurs & Approche</h2>
<ul class="space-y-3 text-white/60">
<li class="flex items-start gap-3">
<span class="w-1 h-1 rounded-full bg-purple-300/60 mt-2.5 flex-shrink-0"></span>
<span>Le mouvement <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 h-1 rounded-full bg-purple-300/60 mt-2.5 flex-shrink-0"></span>
<span>L'utilité sociale du développeur</span>
</li>
<li class="flex items-start gap-3">
<span class="w-1 h-1 rounded-full bg-purple-300/60 mt-2.5 flex-shrink-0"></span>
<span>Être fier de son travail, mais sans égo</span>
</li>
<li class="flex items-start gap-3">
<span class="w-1 h-1 rounded-full bg-purple-300/60 mt-2.5 flex-shrink-0"></span>
<span>Approche <strong class="text-white">Domain Driven Design</strong></span>
</li>
<li class="flex items-start gap-3">
<span class="w-1 h-1 rounded-full bg-purple-300/60 mt-2.5 flex-shrink-0"></span>
<span>Organisation <Link href="https://agilemanifesto.org/iso/fr/manifesto.html" external>agile</Link> : itération et amélioration continue</span>
</li>
<ul class="space-y-4 text-white/80">
<ValueItem>Le mouvement <Link href="http://manifesto.softwarecraftsmanship.org/#/fr-fr" external>Software Craftsmanship</Link></ValueItem>
<ValueItem>L'utilité sociale du développeur</ValueItem>
<ValueItem>Être fier de son travail, mais sans égo</ValueItem>
<ValueItem>Approche <strong class="text-white">Domain Driven Design</strong></ValueItem>
<ValueItem>Organisation <Link href="https://agilemanifesto.org/iso/fr/manifesto.html" external>agile</Link> : itération et amélioration continue</ValueItem>
</ul>
</div>

View file

@ -17,8 +17,8 @@ const recommendationCount = (await getCollection("recommendations")).length;
---
<Layout
title="Jalil Arfaoui - Software Craftsman • Improv Actor • Photographer"
description="Jalil Arfaoui: freelance developer (Software Craftsmanship, TDD, DDD), improv actor and photographer based in Albi, France."
title="Jalil Arfaoui - Software Craftsman • Photographer • Improv Actor"
description="Jalil Arfaoui: freelance developer (Software Craftsmanship, TDD, DDD), photographer and actor based in Albi, France."
>
<!-- Hero Section -->
<div class="relative z-20 w-full max-w-6xl mx-auto mt-16 px-7 md:mt-24 lg:mt-32 xl:px-0">
@ -28,7 +28,7 @@ const recommendationCount = (await getCollection("recommendations")).length;
Jalil Arfaoui
</h1>
<h2 class="mb-6 text-xl font-medium text-neutral-600 dark:text-neutral-300 md:text-2xl">
Software Craftsman • Improv Actor • Photographer
Software Craftsman • Photographer • Actor
</h2>
<p class="mb-8 text-lg text-neutral-600 dark:text-neutral-400 leading-relaxed">
I write code for a fairer world and get on stage for the rest. Or the other way around.

View file

@ -17,8 +17,8 @@ const recommendationCount = (await getCollection("recommendations")).length;
---
<Layout
title="Jalil Arfaoui - Développeur artisan • Comédien improvisateur • Photographe"
description="Jalil Arfaoui : développeur freelance (Software Craftsmanship, TDD, DDD), comédien improvisateur et photographe basé à Albi."
title="Jalil Arfaoui - Développeur artisan • Photographe • Comédien improvisateur"
description="Jalil Arfaoui : développeur freelance, photographe et comédien improvisateur basé à Albi."
>
<!-- Hero Section -->
<div class="relative z-20 w-full max-w-6xl mx-auto mt-16 px-7 md:mt-24 lg:mt-32 xl:px-0">
@ -28,7 +28,7 @@ const recommendationCount = (await getCollection("recommendations")).length;
Jalil Arfaoui
</h1>
<h2 class="mb-6 text-xl font-medium text-neutral-600 dark:text-neutral-300 md:text-2xl">
Développeur artisan • Comédien improvisateur • Photographe
Développeur • Photographe • Comédien
</h2>
<p class="mb-8 text-lg text-neutral-600 dark:text-neutral-400 leading-relaxed">
Je code pour un monde plus juste et je monte sur scène pour le reste. Ou l'inverse.