first commit

This commit is contained in:
Johan LEROY
2026-04-21 14:14:03 +02:00
commit 7c7ff160eb
125 changed files with 13379 additions and 0 deletions

View File

@@ -0,0 +1,174 @@
import { useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
interface Experience {
id: string;
company: string;
logo?: string;
role: string;
period: string;
duration: string;
description: string;
missions: string[];
stack: string[];
type: string;
current?: boolean;
}
interface Props {
items: Experience[];
}
function WaveformBar({ level }: { level: number }) {
// ASCII-like progress bar with blocks
const total = 12;
const filled = Math.round(level * total);
return (
<span className="font-mono tracking-tighter" aria-hidden="true">
{Array.from({ length: total }).map((_, i) => (
<span
key={i}
style={{
opacity: i < filled ? 1 : 0.25,
color: i < filled ? 'var(--color-cyan)' : 'var(--color-text-dim)',
}}
>
</span>
))}
</span>
);
}
export default function Setlist({ items }: Props) {
const [open, setOpen] = useState<string | null>(items[0]?.id ?? null);
return (
<div className="divide-y divide-[color:var(--color-border)] border-y border-[color:var(--color-border)]">
{items.map((exp, i) => {
const isOpen = open === exp.id;
const bpm = exp.current ? 174 : 160 - i * 10;
const level = exp.current ? 1 : Math.max(0.2, 1 - i * 0.15);
return (
<article key={exp.id} className={exp.current ? 'bg-[rgba(198,255,0,0.03)]' : ''}>
<button
type="button"
onClick={() => setOpen(isOpen ? null : exp.id)}
className="w-full flex items-center gap-3 md:gap-6 px-3 md:px-5 py-4 text-left group hover:bg-[rgba(255,255,255,0.02)] transition-colors"
aria-expanded={isOpen}
>
{/* Track number */}
<span className="font-mono text-xs md:text-sm text-[color:var(--color-text-dim)] w-8 tabular-nums">
{String(i + 1).padStart(2, '0')}
</span>
{/* Status tag */}
<span
className="hidden sm:inline-flex items-center gap-1.5 font-mono text-[10px] uppercase tracking-widest w-28"
style={{
color: exp.current ? 'var(--color-red)' : 'var(--color-text-dim)',
}}
>
{exp.current ? (
<>
<span className="led led-red animate-led-red"></span>
[now playing]
</>
) : (
<span className="opacity-60"></span>
)}
</span>
{/* Company + role */}
<div className="flex-1 min-w-0">
<div className="font-display text-sm md:text-base font-semibold truncate group-hover:text-[color:var(--color-cyan)] transition-colors">
{exp.company}
</div>
<div className="font-mono text-[10px] md:text-xs text-[color:var(--color-text-muted)] uppercase tracking-wider truncate">
{exp.role} · {exp.period}
</div>
</div>
{/* Waveform progress */}
<div className="hidden md:block">
<WaveformBar level={level} />
</div>
{/* BPM / duration */}
<div className="hidden sm:flex flex-col items-end gap-0.5 font-mono text-[10px] uppercase tracking-widest text-[color:var(--color-text-dim)] w-24 shrink-0">
<span>[{bpm} bpm]</span>
<span>[{exp.duration}]</span>
</div>
{/* Chevron */}
<span
className={`font-mono text-[color:var(--color-cyan)] transition-transform ${isOpen ? 'rotate-90' : ''}`}
aria-hidden="true"
>
&gt;
</span>
</button>
<AnimatePresence initial={false}>
{isOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.35, ease: [0.22, 1, 0.36, 1] }}
className="overflow-hidden"
>
<div className="px-3 md:px-5 pb-6 pt-2 grid grid-cols-1 md:grid-cols-[200px_1fr] gap-6">
<div className="flex items-start gap-3">
{exp.logo && (
<div className="w-16 h-16 shrink-0 flex items-center justify-center bg-[color:var(--color-bg)] border border-[color:var(--color-border)] rounded-sm p-2">
<img
src={exp.logo}
alt={exp.company}
className="w-full h-full object-contain"
/>
</div>
)}
<div className="font-mono text-[10px] uppercase tracking-widest text-[color:var(--color-text-dim)] space-y-1">
<div>[type: {exp.type}]</div>
<div>[duration: {exp.duration}]</div>
</div>
</div>
<div>
<p className="text-sm text-[color:var(--color-text)] mb-4">{exp.description}</p>
{exp.missions.length > 0 && (
<ul className="space-y-1.5 mb-4">
{exp.missions.map((m, idx) => (
<li key={idx} className="flex gap-2 text-sm">
<span className="font-mono text-[color:var(--color-acid)] shrink-0"></span>
<span className="text-[color:var(--color-text-muted)]">{m}</span>
</li>
))}
</ul>
)}
{exp.stack.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{exp.stack.map((tech) => (
<span
key={tech}
className="font-mono text-[10px] uppercase tracking-wider px-2 py-1 text-[color:var(--color-cyan)] border border-[color:var(--color-cyan)]/30 bg-[color:var(--color-cyan)]/5 rounded-sm"
>
{tech}
</span>
))}
</div>
)}
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</article>
);
})}
</div>
);
}