Compare commits
2 commits
bf26caded3
...
3e4a632adc
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e4a632adc | |||
| e48a551dcb |
BIN
src/assets/images/recommendations/antoine-wolff.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
src/assets/images/recommendations/bouchra-ghaoui.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
src/assets/images/recommendations/daniel-gall.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/assets/images/recommendations/gregoire-lacoste.jpg
Normal file
|
After Width: | Height: | Size: 300 KiB |
BIN
src/assets/images/recommendations/guillaume-gendrillon.webp
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/assets/images/recommendations/john-samson.png
Normal file
|
After Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 6.1 KiB |
BIN
src/assets/images/recommendations/olivier-cornudet.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
src/assets/images/recommendations/thomas-kientz.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/images/recommendations/thomas-morellato.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/images/recommendations/vadim-toropoff.jpg
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/assets/images/recommendations/vanessa-boissard.jpg
Normal file
|
After Width: | Height: | Size: 31 KiB |
|
|
@ -10,7 +10,7 @@ const avatarImages = import.meta.glob<{ default: ImageMetadata }>(
|
||||||
interface Props {
|
interface Props {
|
||||||
author: string;
|
author: string;
|
||||||
authorRole: string;
|
authorRole: string;
|
||||||
company: string;
|
company?: string;
|
||||||
text: string;
|
text: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
---
|
|
||||||
interface Props {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
href: string;
|
|
||||||
icon: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { title, description, href, icon } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<a
|
|
||||||
href={href}
|
|
||||||
class="facet-card group block rounded-2xl bg-white/[0.06] border border-white/[0.1] p-6 hover:bg-white/[0.12] hover:border-white/[0.2] hover:scale-[1.02] transition-all duration-300 no-underline"
|
|
||||||
>
|
|
||||||
<div class="text-3xl mb-3 opacity-80 group-hover:opacity-100 group-hover:scale-110 transition-all duration-300 inline-block">{icon}</div>
|
|
||||||
<h3 class="text-lg font-bold text-white mb-2 group-hover:text-purple-200 transition-colors">
|
|
||||||
{title}
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm text-white/55 leading-relaxed group-hover:text-white/70 transition-colors">
|
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
|
|
@ -10,7 +10,7 @@ const avatarImages = import.meta.glob<{ default: ImageMetadata }>(
|
||||||
interface Props {
|
interface Props {
|
||||||
author: string;
|
author: string;
|
||||||
authorRole: string;
|
authorRole: string;
|
||||||
company: string;
|
company?: string;
|
||||||
text: string;
|
text: string;
|
||||||
date: Date;
|
date: Date;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ const recommendationsCollection = defineCollection({
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
author: z.string(),
|
author: z.string(),
|
||||||
authorRole: z.string(),
|
authorRole: z.string(),
|
||||||
company: z.string(),
|
company: z.string().optional(),
|
||||||
avatar: z.string().optional(),
|
avatar: z.string().optional(),
|
||||||
url: z.string().url().optional(),
|
url: z.string().url().optional(),
|
||||||
date: z.date(),
|
date: z.date(),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
author: "Antoine Wolff"
|
author: "Antoine Wolff"
|
||||||
authorRole: "Développeur, graphiste et chef de projet"
|
authorRole: "Développeur, graphiste et chef de projet"
|
||||||
company: "LeCollectif"
|
company: "LeCollectif"
|
||||||
|
avatar: antoine-wolff.png
|
||||||
url: https://www.linkedin.com/in/wolffantoine
|
url: https://www.linkedin.com/in/wolffantoine
|
||||||
date: 2020-12-07
|
date: 2020-12-07
|
||||||
lang: "fr"
|
lang: "fr"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
author: "Benoit Sarda"
|
author: "Benoit Sarda"
|
||||||
authorRole: "Sr Solution Architect, Manuf"
|
authorRole: "Sr Solution Architect"
|
||||||
company: "Amazon Web Services (AWS)"
|
company: "Amazon Web Services (AWS)"
|
||||||
avatar: benoit-sarda.jpg
|
avatar: benoit-sarda.jpg
|
||||||
url: https://www.linkedin.com/in/benoitsarda
|
url: https://www.linkedin.com/in/benoitsarda
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
author: "Bouchra Ghaoui"
|
author: "Bouchra Ghaoui"
|
||||||
authorRole: "Senior Engagement Manager"
|
authorRole: "Senior Engagement Manager"
|
||||||
company: "Capgemini"
|
company: "Capgemini"
|
||||||
|
avatar: bouchra-ghaoui.jpg
|
||||||
url: https://www.linkedin.com/in/bouchra-ghaoui-46509a10
|
url: https://www.linkedin.com/in/bouchra-ghaoui-46509a10
|
||||||
date: 2011-12-09
|
date: 2011-12-09
|
||||||
lang: "fr"
|
lang: "fr"
|
||||||
|
|
|
||||||
10
src/content/recommendations/john-samson.md
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
author: "John Samson"
|
||||||
|
authorRole: "Président"
|
||||||
|
company: "DisMoi SAS"
|
||||||
|
avatar: john-samson.png
|
||||||
|
url: https://www.malt.fr/profile/jalilarfaoui
|
||||||
|
date: 2022-06-17
|
||||||
|
lang: "fr"
|
||||||
|
---
|
||||||
|
Jalil a su trouver la bonne architecture à notre projet à 5 pattes, il est réactif en cas d'urgence, et s'engage au-delà de son rôle de développeur, très apprécié par les autres membres de l'équipe.
|
||||||
9
src/content/recommendations/thomas-kientz.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
author: "Thomas Kientz"
|
||||||
|
authorRole: "Expert Vue.js | Nuxt"
|
||||||
|
avatar: thomas-kientz.jpg
|
||||||
|
url: https://www.malt.fr/profile/jalilarfaoui
|
||||||
|
date: 2022-06-17
|
||||||
|
lang: "fr"
|
||||||
|
---
|
||||||
|
Jalil est un développeur et mentor hors pair. Le développement logiciel est pour lui un art dont il adore partager sa passion. Je consulte Jalil régulièrement pour avoir son regard expérimenté tant le choix d'une nouvelle techno que pour des reviews de code. C'est un véritable atout à avoir dans son équipe, je le recommande fortement.
|
||||||
9
src/content/recommendations/thomas-morellato.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
author: "Thomas Morellato"
|
||||||
|
authorRole: "UI / UX designer"
|
||||||
|
avatar: thomas-morellato.webp
|
||||||
|
url: https://www.malt.fr/profile/jalilarfaoui
|
||||||
|
date: 2022-06-17
|
||||||
|
lang: "fr"
|
||||||
|
---
|
||||||
|
Jalil est un professionnel engagé et très compétent. Son relationnel et sa vision du projet en font un élément indispensable au sein d'une équipe. Je recommande vivement son profil.
|
||||||
11
src/content/recommendations/vadim-toropoff.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
author: "Vadim Toropoff"
|
||||||
|
authorRole: "Dirigeant"
|
||||||
|
company: "Event Finder"
|
||||||
|
url: https://www.malt.fr/profile/jalilarfaoui
|
||||||
|
date: 2015-10-08
|
||||||
|
lang: "fr"
|
||||||
|
---
|
||||||
|
Jalil nous a apporté conseil et expertise pour le lancement de tous nos projets technos pour notre agence événementielle. Il a réussi à comprendre et à mettre en place des systèmes complexes et surtout sur mesure.
|
||||||
|
|
||||||
|
Patient, à l'écoute, je conseille les yeux fermés de travailler avec lui.
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
author: "Vanessa Boissard"
|
author: "Vanessa Boissard"
|
||||||
authorRole: "Psychologue sociale"
|
authorRole: "Psychologue sociale"
|
||||||
company: "AlterAlliance"
|
company: "AlterAlliance"
|
||||||
|
avatar: vanessa-boissard.jpg
|
||||||
url: https://www.linkedin.com/in/vanessaboissard
|
url: https://www.linkedin.com/in/vanessaboissard
|
||||||
date: 2011-12-05
|
date: 2011-12-05
|
||||||
lang: "fr"
|
lang: "fr"
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ import { getCollection } from "astro:content";
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
import Layout from "../../../layouts/main.astro";
|
import Layout from "../../../layouts/main.astro";
|
||||||
import Link from "../../../components/Link.astro";
|
import Link from "../../../components/Link.astro";
|
||||||
import NavigationCard from "../../../components/code/NavigationCard.astro";
|
|
||||||
import FeaturedRecommendation from "../../../components/code/FeaturedRecommendation.astro";
|
import FeaturedRecommendation from "../../../components/code/FeaturedRecommendation.astro";
|
||||||
|
import ProjectCard from "../../../components/code/ProjectCard.astro";
|
||||||
|
import SkillBadge from "../../../components/code/SkillBadge.astro";
|
||||||
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";
|
||||||
|
|
||||||
|
|
@ -13,7 +15,15 @@ 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 currentPosition = experiences.find((e) => !e.data.endDate);
|
const recentExperiences = experiences.slice(0, 4);
|
||||||
|
|
||||||
|
const projects = (await getCollection("projects"))
|
||||||
|
.filter((p) => p.data.lang === locale && !p.data.draft && p.data.category === "dev")
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.data.featured !== b.data.featured) return a.data.featured ? -1 : 1;
|
||||||
|
return b.data.date.getTime() - a.data.date.getTime();
|
||||||
|
})
|
||||||
|
.slice(0, 3);
|
||||||
|
|
||||||
const recommendations = (await getCollection("recommendations"))
|
const recommendations = (await getCollection("recommendations"))
|
||||||
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime())
|
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime())
|
||||||
|
|
@ -23,6 +33,14 @@ const recommendationTexts = recommendations.map((rec) => ({
|
||||||
...rec,
|
...rec,
|
||||||
text: rec.body || '',
|
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)
|
||||||
|
.toLocaleDateString('ar-SA', { year: 'numeric', month: 'short' });
|
||||||
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
|
|
@ -44,47 +62,56 @@ const recommendationTexts = recommendations.map((rec) => ({
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-12">
|
<div class="mb-12">
|
||||||
<NavigationCard
|
<div class="flex items-center justify-between mb-5">
|
||||||
title="المسار"
|
<h2 class="text-2xl font-bold text-white">المسار</h2>
|
||||||
description="الجدول الزمني لخبراتي المهنية منذ 2002."
|
<a href="/ar/برمجة/مسار" class="text-sm text-purple-200 hover:text-white transition-colors">← عرض الكل</a>
|
||||||
href="/ar/برمجة/مسار"
|
|
||||||
icon="📋"
|
|
||||||
/>
|
|
||||||
<NavigationCard
|
|
||||||
title="المشاريع"
|
|
||||||
description="برمجيات مفتوحة المصدر ومشاريع شخصية."
|
|
||||||
href="/ar/برمجة/مشاريع"
|
|
||||||
icon="💻"
|
|
||||||
/>
|
|
||||||
<NavigationCard
|
|
||||||
title="المهارات"
|
|
||||||
description="لغات، أطر عمل، ممارسات وأدوات."
|
|
||||||
href="/ar/برمجة/مهارات"
|
|
||||||
icon="🛠"
|
|
||||||
/>
|
|
||||||
<NavigationCard
|
|
||||||
title="التوصيات"
|
|
||||||
description="ما يقوله الأشخاص الذين عملت معهم."
|
|
||||||
href="/ar/برمجة/توصيات"
|
|
||||||
icon="💬"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="space-y-3">
|
||||||
{currentPosition && (
|
{recentExperiences.map((exp) => {
|
||||||
<div class="facet-card rounded-2xl bg-gradient-to-r from-purple-500/20 to-indigo-500/20 border border-purple-300/15 p-6 mb-10">
|
const isOngoing = !exp.data.endDate;
|
||||||
<p class="text-xs font-semibold text-purple-200 uppercase tracking-wider mb-2">المنصب الحالي</p>
|
const start = formatMonth(exp.data.startDate);
|
||||||
<p class="text-xl font-bold text-white">{currentPosition.data.role}</p>
|
const end = exp.data.endDate ? formatMonth(exp.data.endDate) : 'الحالي';
|
||||||
<p class="text-sm text-white/60 mt-1">
|
return (
|
||||||
{currentPosition.data.companyUrl ? (
|
<div class="facet-card rounded-xl bg-white/[0.06] border border-white/[0.1] p-4 hover:bg-white/[0.1] transition-colors">
|
||||||
<a href={currentPosition.data.companyUrl} target="_blank" rel="noopener noreferrer" class="text-purple-200 hover:text-white transition-colors">{currentPosition.data.company}</a>
|
<div class="flex items-start justify-between gap-4">
|
||||||
) : (
|
<div class="min-w-0">
|
||||||
currentPosition.data.company
|
<p class="font-semibold text-white text-sm truncate">{exp.data.role}</p>
|
||||||
)}
|
<p class="text-xs text-white/50 mt-0.5">
|
||||||
{currentPosition.data.location && ` · ${currentPosition.data.location}`}
|
{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>
|
</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>
|
||||||
|
</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">← عرض الكل</a>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
{projects.map((project) => (
|
||||||
|
<ProjectCard
|
||||||
|
title={project.data.title}
|
||||||
|
description={project.data.description}
|
||||||
|
technologies={project.data.technologies}
|
||||||
|
url={project.data.url}
|
||||||
|
github={project.data.github}
|
||||||
|
featured={project.data.featured}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{recommendationTexts.length > 0 && (
|
{recommendationTexts.length > 0 && (
|
||||||
<div class="mb-12">
|
<div class="mb-12">
|
||||||
|
|
@ -107,6 +134,25 @@ const recommendationTexts = recommendations.map((rec) => ({
|
||||||
</div>
|
</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">← عرض الكل</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>
|
<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">
|
<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">
|
<ul class="space-y-3 text-white/70">
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ import { getCollection } from "astro:content";
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
import Layout from "../../layouts/main.astro";
|
import Layout from "../../layouts/main.astro";
|
||||||
import Link from "../../components/Link.astro";
|
import Link from "../../components/Link.astro";
|
||||||
import NavigationCard from "../../components/code/NavigationCard.astro";
|
|
||||||
import FeaturedRecommendation from "../../components/code/FeaturedRecommendation.astro";
|
import FeaturedRecommendation from "../../components/code/FeaturedRecommendation.astro";
|
||||||
|
import ProjectCard from "../../components/code/ProjectCard.astro";
|
||||||
|
import SkillBadge from "../../components/code/SkillBadge.astro";
|
||||||
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 = "fr";
|
const locale = "fr";
|
||||||
|
|
||||||
|
|
@ -13,7 +15,15 @@ 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 currentPosition = experiences.find((e) => !e.data.endDate);
|
const recentExperiences = experiences.slice(0, 4);
|
||||||
|
|
||||||
|
const projects = (await getCollection("projects"))
|
||||||
|
.filter((p) => p.data.lang === locale && !p.data.draft && p.data.category === "dev")
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.data.featured !== b.data.featured) return a.data.featured ? -1 : 1;
|
||||||
|
return b.data.date.getTime() - a.data.date.getTime();
|
||||||
|
})
|
||||||
|
.slice(0, 3);
|
||||||
|
|
||||||
const recommendations = (await getCollection("recommendations"))
|
const recommendations = (await getCollection("recommendations"))
|
||||||
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime())
|
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime())
|
||||||
|
|
@ -23,6 +33,14 @@ const recommendationTexts = recommendations.map((rec) => ({
|
||||||
...rec,
|
...rec,
|
||||||
text: rec.body || '',
|
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)
|
||||||
|
.toLocaleDateString('fr-FR', { year: 'numeric', month: 'short' });
|
||||||
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
|
|
@ -44,47 +62,56 @@ const recommendationTexts = recommendations.map((rec) => ({
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-12">
|
<div class="mb-12">
|
||||||
<NavigationCard
|
<div class="flex items-center justify-between mb-5">
|
||||||
title="Parcours"
|
<h2 class="text-2xl font-bold text-white">Parcours</h2>
|
||||||
description="Timeline de mes expériences professionnelles depuis 2002."
|
<a href="/code/parcours" class="text-sm text-purple-200 hover:text-white transition-colors">Voir tout →</a>
|
||||||
href="/code/parcours"
|
|
||||||
icon="📋"
|
|
||||||
/>
|
|
||||||
<NavigationCard
|
|
||||||
title="Projets"
|
|
||||||
description="Logiciels open source et projets personnels."
|
|
||||||
href="/code/projets"
|
|
||||||
icon="💻"
|
|
||||||
/>
|
|
||||||
<NavigationCard
|
|
||||||
title="Compétences"
|
|
||||||
description="Langages, frameworks, pratiques et outils."
|
|
||||||
href="/code/competences"
|
|
||||||
icon="🛠"
|
|
||||||
/>
|
|
||||||
<NavigationCard
|
|
||||||
title="Recommandations"
|
|
||||||
description="Ce que disent les gens avec qui j'ai travaillé."
|
|
||||||
href="/code/recommandations"
|
|
||||||
icon="💬"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="space-y-3">
|
||||||
{currentPosition && (
|
{recentExperiences.map((exp) => {
|
||||||
<div class="facet-card rounded-2xl bg-gradient-to-r from-purple-500/20 to-indigo-500/20 border border-purple-300/15 p-6 mb-10">
|
const isOngoing = !exp.data.endDate;
|
||||||
<p class="text-xs font-semibold text-purple-200 uppercase tracking-wider mb-2">Poste actuel</p>
|
const start = formatMonth(exp.data.startDate);
|
||||||
<p class="text-xl font-bold text-white">{currentPosition.data.role}</p>
|
const end = exp.data.endDate ? formatMonth(exp.data.endDate) : 'Présent';
|
||||||
<p class="text-sm text-white/60 mt-1">
|
return (
|
||||||
{currentPosition.data.companyUrl ? (
|
<div class="facet-card rounded-xl bg-white/[0.06] border border-white/[0.1] p-4 hover:bg-white/[0.1] transition-colors">
|
||||||
<a href={currentPosition.data.companyUrl} target="_blank" rel="noopener noreferrer" class="text-purple-200 hover:text-white transition-colors">{currentPosition.data.company}</a>
|
<div class="flex items-start justify-between gap-4">
|
||||||
) : (
|
<div class="min-w-0">
|
||||||
currentPosition.data.company
|
<p class="font-semibold text-white text-sm truncate">{exp.data.role}</p>
|
||||||
)}
|
<p class="text-xs text-white/50 mt-0.5">
|
||||||
{currentPosition.data.location && ` · ${currentPosition.data.location}`}
|
{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>
|
</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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-12">
|
||||||
|
<div class="flex items-center justify-between mb-5">
|
||||||
|
<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 →</a>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
{projects.map((project) => (
|
||||||
|
<ProjectCard
|
||||||
|
title={project.data.title}
|
||||||
|
description={project.data.description}
|
||||||
|
technologies={project.data.technologies}
|
||||||
|
url={project.data.url}
|
||||||
|
github={project.data.github}
|
||||||
|
featured={project.data.featured}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{recommendationTexts.length > 0 && (
|
{recommendationTexts.length > 0 && (
|
||||||
<div class="mb-12">
|
<div class="mb-12">
|
||||||
|
|
@ -107,6 +134,25 @@ const recommendationTexts = recommendations.map((rec) => ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div class="mb-12">
|
||||||
|
<div class="flex items-center justify-between mb-5">
|
||||||
|
<h2 class="text-2xl font-bold text-white">Compétences</h2>
|
||||||
|
<a href="/code/competences" class="text-sm text-purple-200 hover:text-white transition-colors">Voir toutes →</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">Valeurs & Approche</h2>
|
<h2 class="text-2xl font-bold text-white mb-5">Valeurs & Approche</h2>
|
||||||
<div class="facet-card rounded-2xl bg-white/[0.04] border border-white/[0.08] p-6 mb-10">
|
<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">
|
<ul class="space-y-3 text-white/70">
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ import { getCollection } from "astro:content";
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
import Layout from "../../../layouts/main.astro";
|
import Layout from "../../../layouts/main.astro";
|
||||||
import Link from "../../../components/Link.astro";
|
import Link from "../../../components/Link.astro";
|
||||||
import NavigationCard from "../../../components/code/NavigationCard.astro";
|
|
||||||
import FeaturedRecommendation from "../../../components/code/FeaturedRecommendation.astro";
|
import FeaturedRecommendation from "../../../components/code/FeaturedRecommendation.astro";
|
||||||
|
import ProjectCard from "../../../components/code/ProjectCard.astro";
|
||||||
|
import SkillBadge from "../../../components/code/SkillBadge.astro";
|
||||||
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";
|
||||||
|
|
||||||
|
|
@ -13,7 +15,15 @@ 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 currentPosition = experiences.find((e) => !e.data.endDate);
|
const recentExperiences = experiences.slice(0, 4);
|
||||||
|
|
||||||
|
const projects = (await getCollection("projects"))
|
||||||
|
.filter((p) => p.data.lang === locale && !p.data.draft && p.data.category === "dev")
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.data.featured !== b.data.featured) return a.data.featured ? -1 : 1;
|
||||||
|
return b.data.date.getTime() - a.data.date.getTime();
|
||||||
|
})
|
||||||
|
.slice(0, 3);
|
||||||
|
|
||||||
const recommendations = (await getCollection("recommendations"))
|
const recommendations = (await getCollection("recommendations"))
|
||||||
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime())
|
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime())
|
||||||
|
|
@ -23,6 +33,14 @@ const recommendationTexts = recommendations.map((rec) => ({
|
||||||
...rec,
|
...rec,
|
||||||
text: rec.body || '',
|
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)
|
||||||
|
.toLocaleDateString('en-US', { year: 'numeric', month: 'short' });
|
||||||
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
|
|
@ -44,47 +62,56 @@ const recommendationTexts = recommendations.map((rec) => ({
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-12">
|
<div class="mb-12">
|
||||||
<NavigationCard
|
<div class="flex items-center justify-between mb-5">
|
||||||
title="Career"
|
<h2 class="text-2xl font-bold text-white">Career</h2>
|
||||||
description="Timeline of my professional experiences since 2002."
|
<a href="/en/code/career" class="text-sm text-purple-200 hover:text-white transition-colors">See all →</a>
|
||||||
href="/en/code/career"
|
|
||||||
icon="📋"
|
|
||||||
/>
|
|
||||||
<NavigationCard
|
|
||||||
title="Projects"
|
|
||||||
description="Open source software and personal projects."
|
|
||||||
href="/en/code/projects"
|
|
||||||
icon="💻"
|
|
||||||
/>
|
|
||||||
<NavigationCard
|
|
||||||
title="Skills"
|
|
||||||
description="Languages, frameworks, practices and tools."
|
|
||||||
href="/en/code/skills"
|
|
||||||
icon="🛠"
|
|
||||||
/>
|
|
||||||
<NavigationCard
|
|
||||||
title="Recommendations"
|
|
||||||
description="What people I've worked with say about me."
|
|
||||||
href="/en/code/recommendations"
|
|
||||||
icon="💬"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="space-y-3">
|
||||||
{currentPosition && (
|
{recentExperiences.map((exp) => {
|
||||||
<div class="facet-card rounded-2xl bg-gradient-to-r from-purple-500/20 to-indigo-500/20 border border-purple-300/15 p-6 mb-10">
|
const isOngoing = !exp.data.endDate;
|
||||||
<p class="text-xs font-semibold text-purple-200 uppercase tracking-wider mb-2">Current position</p>
|
const start = formatMonth(exp.data.startDate);
|
||||||
<p class="text-xl font-bold text-white">{currentPosition.data.role}</p>
|
const end = exp.data.endDate ? formatMonth(exp.data.endDate) : 'Present';
|
||||||
<p class="text-sm text-white/60 mt-1">
|
return (
|
||||||
{currentPosition.data.companyUrl ? (
|
<div class="facet-card rounded-xl bg-white/[0.06] border border-white/[0.1] p-4 hover:bg-white/[0.1] transition-colors">
|
||||||
<a href={currentPosition.data.companyUrl} target="_blank" rel="noopener noreferrer" class="text-purple-200 hover:text-white transition-colors">{currentPosition.data.company}</a>
|
<div class="flex items-start justify-between gap-4">
|
||||||
) : (
|
<div class="min-w-0">
|
||||||
currentPosition.data.company
|
<p class="font-semibold text-white text-sm truncate">{exp.data.role}</p>
|
||||||
)}
|
<p class="text-xs text-white/50 mt-0.5">
|
||||||
{currentPosition.data.location && ` · ${currentPosition.data.location}`}
|
{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>
|
</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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-12">
|
||||||
|
<div class="flex items-center justify-between mb-5">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
{projects.map((project) => (
|
||||||
|
<ProjectCard
|
||||||
|
title={project.data.title}
|
||||||
|
description={project.data.description}
|
||||||
|
technologies={project.data.technologies}
|
||||||
|
url={project.data.url}
|
||||||
|
github={project.data.github}
|
||||||
|
featured={project.data.featured}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{recommendationTexts.length > 0 && (
|
{recommendationTexts.length > 0 && (
|
||||||
<div class="mb-12">
|
<div class="mb-12">
|
||||||
|
|
@ -107,6 +134,25 @@ const recommendationTexts = recommendations.map((rec) => ({
|
||||||
</div>
|
</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 →</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>
|
<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">
|
<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">
|
<ul class="space-y-3 text-white/70">
|
||||||
|
|
|
||||||