Commit ae3a9078 by PLN (Algolia)

feat(ParVagues): Iterate

parent d93890ce
import React from 'react';
import Link from 'next/link';
import { FaEnvelope, FaInstagram, FaTwitter } from 'react-icons/fa';
import { SiMastodon, SiBluesky } from 'react-icons/si';
import styles from '@/styles/parvagues.module.css';
export default function ParVaguesFooter() {
const socialLinks = [
{ icon: <FaEnvelope />, label: 'email', url: 'mailto:parvagues@nech.pl' },
{ icon: <SiMastodon />, label: 'mastodon', url: 'https://chaos.social/@PixelNoir' },
{ icon: <FaTwitter />, label: 'twitter', url: 'https://x.com/ParVagues' },
{ icon: <SiBluesky />, label: 'bluesky', url: '#' },
{ icon: <FaInstagram />, label: 'instagram', url: 'https://instagram.com/parvagues.mp3' }
];
const year = new Date().getFullYear();
return (
<footer className="bg-black border-t border-purple-500/20 py-12">
<div className="max-w-6xl mx-auto px-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{/* About Column */}
<div>
<h3 className="text-purple-400 font-semibold text-lg mb-4">ParVagues</h3>
<p className="text-gray-400 text-sm mb-4">
Livecoding de musique open-source avec TidalCycles et contrôleur MIDI.
Performances algorithmiques et création sonore en direct.
</p>
</div>
{/* Links Column */}
<div>
<h3 className="text-purple-400 font-semibold text-lg mb-4">Explorez</h3>
<ul className="space-y-2 text-sm">
<li>
<Link href="/parvagues#music" className="text-gray-400 hover:text-purple-300 transition-colors">
Musique
</Link>
</li>
<li>
<Link href="/parvagues#performances" className="text-gray-400 hover:text-purple-300 transition-colors">
Performances
</Link>
</li>
<li>
<Link href="/parvagues#about" className="text-gray-400 hover:text-purple-300 transition-colors">
À propos
</Link>
</li>
<li>
<a
href="mailto:parvagues@nech.pl?subject=Booking Request"
className="text-gray-400 hover:text-purple-300 transition-colors"
>
Réserver un live
</a>
</li>
</ul>
</div>
{/* Social Column */}
<div>
<h3 className="text-purple-400 font-semibold text-lg mb-4">Connect</h3>
<div className="flex flex-wrap gap-4">
{socialLinks.map((link, index) => (
<a
key={index}
href={link.url}
target="_blank"
rel="noopener noreferrer"
className="bg-gray-800 hover:bg-purple-900 text-white p-3 rounded-full transition-colors"
aria-label={link.label}
>
{link.icon}
</a>
))}
</div>
</div>
</div>
{/* Bottom Bar */}
<div className="border-t border-gray-800 mt-8 pt-8 text-center text-gray-500 text-sm">
<p>© {year} ParVagues. All code is open-source.</p>
</div>
</div>
</footer>
);
}
\ No newline at end of file
...@@ -3,21 +3,17 @@ import Link from 'next/link'; ...@@ -3,21 +3,17 @@ import Link from 'next/link';
import { FaEnvelope } from 'react-icons/fa'; import { FaEnvelope } from 'react-icons/fa';
import styles from '@/styles/parvagues.module.css'; import styles from '@/styles/parvagues.module.css';
export default function ParVaguesHeader({ isSticky, eventName = null }) { export default function ParVaguesHeader({ eventName = null }) {
return ( return (
<header <header
className={`${ className="sticky top-0 z-50 bg-gradient-to-b from-black/90 to-black/70 backdrop-blur-md transition-all duration-300 border-b border-purple-500/20 shadow-lg"
isSticky
? 'fixed top-0 bg-black/90 backdrop-blur-md border-b border-purple-500/20 shadow-lg z-[100] w-full transition-all duration-300 translate-y-0'
: 'fixed top-0 bg-transparent w-full z-[100] -translate-y-full opacity-0'
} transition-all duration-300`}
> >
<div className="max-w-6xl mx-auto px-4 py-4 flex justify-between items-center"> <div className="max-w-6xl mx-auto px-4 py-4 flex justify-between items-center">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Link href="/parvagues" className="text-purple-400 hover:text-purple-300 transition-colors"> <Link href="/parvagues" className="text-purple-400 hover:text-purple-300 transition-colors">
ParVagues ParVagues
</Link> </Link>
{isSticky && eventName && ( {eventName && (
<> <>
<span className="text-gray-500 mx-2">|</span> <span className="text-gray-500 mx-2">|</span>
<span className="text-white">{eventName}</span> <span className="text-white">{eventName}</span>
...@@ -25,11 +21,23 @@ export default function ParVaguesHeader({ isSticky, eventName = null }) { ...@@ -25,11 +21,23 @@ export default function ParVaguesHeader({ isSticky, eventName = null }) {
)} )}
</div> </div>
{/* CTA button that appears when sticky */} {/* Navigation Links */}
<div className="hidden md:flex items-center space-x-6 text-sm">
<Link href="/parvagues#music" className="text-white hover:text-purple-300 transition-colors">
Music
</Link>
<Link href="/parvagues#performances" className="text-white hover:text-purple-300 transition-colors">
Performances
</Link>
<Link href="/parvagues#about" className="text-white hover:text-purple-300 transition-colors">
About
</Link>
</div>
{/* CTA button */}
<a <a
href="mailto:parvagues@nech.pl?subject=Booking Request&body=Bonjour,%0D%0A%0D%0AJe souhaiterais discuter d'une possibilité de performance live coding." href="mailto:parvagues@nech.pl?subject=Booking Request&body=Bonjour,%0D%0A%0D%0AJe souhaiterais discuter d'une possibilité de performance live coding."
className={`${styles.ctaButton} transition-all duration-300`} className={`${styles.ctaButton} transition-all duration-300`}
style={{ position: 'relative', top: 'auto', right: 'auto' }}
> >
<FaEnvelope className="inline mr-2" /> <FaEnvelope className="inline mr-2" />
Invoquer un live Invoquer un live
......
---
title: "[38C3] Secret Toilet Rave"
date: "2025-12-28"
time: "01:37"
location: "Hamburg, Germany"
address: "CCH, Hamburg"
description: "TODO"
ctaURL: "https://soundcloud.com/parvagues/live-38c3-secret-toilet-rave"
ctaText: "SECRETSecret Toilet Algorave Set ㊙️🕺🪠"
video: ""
audio: "https://soundcloud.com/parvagues/live-38c3-secret-toilet-rave"
archive: "https://soundcloud.com/parvagues/live-38c3-secret-toilet-rave"
tags: ["livecoding", "170BPM", "DNB", "Techno", "live"]
---
Secret Toilet Algorave Set ㊙️🕺🪠
Played live at the CCC 38C3 edition (Ghosts in the Toilets composed on the spot 🤟)
Tracklist:
- Ghosts in the Toilets
- Nouveau Punk
- Pitbul Punk
- Acidulé
- L'or Bleu
\ No newline at end of file
...@@ -5,8 +5,8 @@ time: "XX:XX" ...@@ -5,8 +5,8 @@ time: "XX:XX"
location: "Venue TBD" location: "Venue TBD"
address: "Lyon, France" address: "Lyon, France"
description: "Live algorithmic performance" description: "Live algorithmic performance"
ctaURL: "https://nech.pl/algorave-lyon" # ctaURL: "https://nech.pl/algorave-lyon"
ctaText: "Plus d'infos" # ctaText: "Plus d'infos"
teasing1: | teasing1: |
# AlgoRave preparation # AlgoRave preparation
......
...@@ -39,7 +39,6 @@ export async function getStaticPaths() { ...@@ -39,7 +39,6 @@ export async function getStaticPaths() {
} }
export default function Hydra({ hydraData, sourceCode }) { export default function Hydra({ hydraData, sourceCode }) {
const [showCode, setShowCode] = useState(false);
return ( return (
<Layout> <Layout>
...@@ -74,31 +73,15 @@ export default function Hydra({ hydraData, sourceCode }) { ...@@ -74,31 +73,15 @@ export default function Hydra({ hydraData, sourceCode }) {
); );
} }
// Suggestion for later: // Suggestion for later:
// // TODO: refactor to Dynamic import with SSR enabled.
// Dynamic import with SSR disabled // DISCUSS: What would be the benefit of this?
// AI opinion: It would be better to use a dynamic import with SSR enabled,
// >because the HydraSynth component is not needed on the server side.
// >It's a client-side component that needs to be rendered on the client side.
// >So it's better to use a dynamic import with SSR enabled.
// Human: Ok -> TODO for next time someone changes this file, please do refactor to Dynamic import with SSR enabled.
// const HydraSynth = dynamic( // const HydraSynth = dynamic(
// () => import('../../components/hydra-view'), // () => import('../../components/hydra-view'),
// { ssr: false } // { ssr: false }
// ) // )
// \ No newline at end of file
// export default function Hydra({ hydraData, sourceCode }) {
// const canvasRef = useRef(null);
//
// return (
// <Layout>
// {/* ... rest of your component ... */}
//
// {/* Now this will only run on the client side */}
// <HydraSynth
// width={700}
// height={475}
// canvasRef={canvasRef}
// source={hydraData.source}
// />
//
// {/* ... rest of your component ... */}
// </Layout>
// );
// }
...@@ -6,52 +6,16 @@ import { getAllLives } from '@/lib/livesData'; ...@@ -6,52 +6,16 @@ import { getAllLives } from '@/lib/livesData';
import styles from '@/styles/parvagues.module.css'; import styles from '@/styles/parvagues.module.css';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import ParVaguesHeader from '@/components/ParVaguesHeader'; import ParVaguesHeader from '@/components/ParVaguesHeader';
import ParVaguesFooter from '@/components/ParVaguesFooter';
import SyntaxHighlighter from "react-syntax-highlighter";
import { atomOneDark } from "react-syntax-highlighter/dist/cjs/styles/hljs";
// React Icons imports // React Icons imports
import { FaSpotify, FaDeezer, FaYoutube, FaApple, FaAmazon, FaInstagram, FaTwitter, FaEnvelope } from 'react-icons/fa'; import { FaSpotify, FaDeezer, FaYoutube, FaApple, FaAmazon, FaInstagram, FaTwitter, FaEnvelope } from 'react-icons/fa';
import { SiTidal, SiBluesky, SiMastodon } from 'react-icons/si'; import { SiTidal, SiBluesky, SiMastodon } from 'react-icons/si';
import { MdPlayArrow, MdPause } from 'react-icons/md'; import { MdPlayArrow, MdPause } from 'react-icons/md';
// Dynamically import Prism.js with no SSR
const Prism = dynamic(() => import('prismjs'), { ssr: false });
const PrismHaskell = dynamic(() => import('prismjs/components/prism-haskell'), { ssr: false });
function CodeBlock({ children, height = '400px', isTerminal = false }) { function CodeBlock({ children, height = '400px', isTerminal = false }) {
const [isClient, setIsClient] = useState(false);
const codeRef = useRef(null);
useEffect(() => {
setIsClient(true);
}, []);
useEffect(() => {
if (isClient && codeRef.current) {
import('prismjs').then((Prism) => {
Prism.default.highlightElement(codeRef.current);
});
}
}, [isClient, children]);
if (!isClient) {
return (
<div className={`${styles.codeContainer} ${isTerminal ? styles.terminalContainer : ''}`} style={{ maxHeight: height }}>
{isTerminal && (
<div className={styles.terminalHeader}>
<div className={styles.terminalControls}>
<span className={styles.redCircle}></span>
<span className={styles.yellowCircle}></span>
<span className={styles.greenCircle}></span>
</div>
<div className={styles.terminalTitle}>ParVagues@tidal:~</div>
</div>
)}
<pre className="language-haskell">
<code>{children}</code>
</pre>
</div>
);
}
return ( return (
<div className={`${styles.codeContainer} ${isTerminal ? styles.terminalContainer : ''}`} style={{ maxHeight: height }}> <div className={`${styles.codeContainer} ${isTerminal ? styles.terminalContainer : ''}`} style={{ maxHeight: height }}>
{isTerminal && ( {isTerminal && (
...@@ -64,9 +28,19 @@ function CodeBlock({ children, height = '400px', isTerminal = false }) { ...@@ -64,9 +28,19 @@ function CodeBlock({ children, height = '400px', isTerminal = false }) {
<div className={styles.terminalTitle}>ParVagues@tidal:~</div> <div className={styles.terminalTitle}>ParVagues@tidal:~</div>
</div> </div>
)} )}
<pre className="language-haskell"> <SyntaxHighlighter
<code ref={codeRef}>{children}</code> language="haskell"
</pre> style={atomOneDark}
wrapLongLines={true}
customStyle={{
margin: 0,
borderRadius: isTerminal ? '0 0 8px 8px' : '8px',
height: 'auto',
maxHeight: isTerminal ? `calc(${height} - 30px)` : height
}}
>
{children}
</SyntaxHighlighter>
</div> </div>
); );
} }
...@@ -87,7 +61,7 @@ export default function ParVagues({ lives }) { ...@@ -87,7 +61,7 @@ export default function ParVagues({ lives }) {
const [selectedSection, setSelectedSection] = useState('code'); const [selectedSection, setSelectedSection] = useState('code');
const [sectionImages, setSectionImages] = useState([]); const [sectionImages, setSectionImages] = useState([]);
const [currentImageIndex, setCurrentImageIndex] = useState(0); const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [isHeaderSticky, setIsHeaderSticky] = useState(false); const [currentAlbumIndex, setCurrentAlbumIndex] = useState(0);
const backgroundRef = useRef(null); const backgroundRef = useRef(null);
const audioRef = useRef(null); const audioRef = useRef(null);
...@@ -151,6 +125,16 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12) ...@@ -151,6 +125,16 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
return () => clearInterval(interval); return () => clearInterval(interval);
} }
}, [sectionImages.length]); }, [sectionImages.length]);
// Carousel effect for albums
useEffect(() => {
if (albums.length > 1) {
const interval = setInterval(() => {
setCurrentAlbumIndex(prev => (prev + 1) % albums.length);
}, 5000);
return () => clearInterval(interval);
}
}, []);
// Animate background images // Animate background images
useEffect(() => { useEffect(() => {
...@@ -175,21 +159,6 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12) ...@@ -175,21 +159,6 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, []);
// Fix scroll handling for sticky header
useEffect(() => {
const handleScroll = () => {
const scrollPosition = window.scrollY;
const threshold = window.innerHeight * 0.7; // Lower threshold to 70% of viewport height
setIsHeaderSticky(scrollPosition > threshold);
};
// Initial check
handleScroll();
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const scrollToSection = (e) => { const scrollToSection = (e) => {
e.preventDefault(); e.preventDefault();
const section = document.getElementById('section1'); const section = document.getElementById('section1');
...@@ -242,14 +211,6 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12) ...@@ -242,14 +211,6 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
} }
]; ];
const socialLinks = [
{ icon: <FaEnvelope />, label: 'email', url: 'mailto:parvagues@nech.pl' },
{ icon: <SiMastodon />, label: 'mastodon', url: 'https://chaos.social/@PixelNoir' },
{ icon: <FaTwitter />, label: 'twitter', url: 'https://x.com/ParVagues' },
{ icon: <SiBluesky />, label: 'bluesky [soon]', url: '#' },
{ icon: <FaInstagram />, label: 'instagram', url: 'https://instagram.com/parvagues.mp3' }
];
const renderSectionContent = () => { const renderSectionContent = () => {
const sectionContent = sections[selectedSection]; const sectionContent = sections[selectedSection];
if (!sectionContent) return null; if (!sectionContent) return null;
...@@ -334,186 +295,235 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12) ...@@ -334,186 +295,235 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</Head> </Head>
<div className="min-h-screen bg-black text-white relative"> <div className="flex flex-col min-h-screen bg-black text-white">
{/* Replace static CTA with ParVaguesHeader component */} {/* Apply proper sticky header */}
<ParVaguesHeader isSticky={isHeaderSticky} /> <ParVaguesHeader />
{/* Fixed top CTA that shows only when not in sticky mode */}
<a
href="mailto:parvagues@nech.pl?subject=Booking Request&body=Bonjour,%0D%0A%0D%0AJe souhaiterais discuter d'une possibilité de performance live coding."
className={`${styles.ctaButton} ${isHeaderSticky ? 'opacity-0' : 'opacity-100'} transition-opacity duration-300`}
>
<FaEnvelope className="inline mr-2" />
Invoquer un live
</a>
{/* Hero Section */} {/* Main content flex-auto to push footer to bottom */}
<section className={styles.heroSection}> <main className="flex-auto">
<div className={styles.posterCollage} ref={backgroundRef}> {/* Hero Section */}
{posterImages.map((src, i) => ( <section className={`${styles.heroSection} mt-0`}>
<Image <div className={styles.posterCollage} ref={backgroundRef}>
key={i} {posterImages.map((src, i) => (
src={src} <Image
alt={`Poster ${i + 1}`} key={i}
fill src={src}
className={styles.posterImage} alt={`Poster ${i + 1}`}
priority={i < 3} fill
/> className={styles.posterImage}
))} priority={i < 3}
</div> />
))}
{/* Audio element */} </div>
<audio
ref={audioRef} {/* Audio element */}
src="/parvagues.mp3" <audio
loop ref={audioRef}
onPlay={() => setIsPlaying(true)} src="/parvagues.mp3"
onPause={() => setIsPlaying(false)} loop
/> onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
/>
<div className={styles.contentOverlay}>
<div className={styles.heroContent}>
{/* Left side: Title and content */}
<div className="md:w-1/2">
<h1 className={`${styles.heroTitle} ${styles.glitchEffect}`}>
ParVagues
</h1>
<p className={styles.heroSubtitle}>
Livecoding de musique open-source avec TidalCycles et contrôleur MIDI
</p>
<a href="#section1" onClick={scrollToSection} className={styles.plungeButton}>
Plonger
</a>
</div>
{/* Right side: Code sample with play overlay */}
<div className="md:w-1/2 relative">
<div className="relative">
<CodeBlock height="300px" isTerminal={true}>
{tidalCode}
</CodeBlock>
{/* Pretty Play overlay */}
<button
onClick={toggleAudio}
className="absolute inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center transition-all duration-300 hover:bg-black/80 group"
>
<div className="bg-gradient-to-br from-purple-500 to-pink-500 rounded-full p-8 shadow-xl shadow-purple-500/50 transition-all duration-300 group-hover:scale-110 group-hover:shadow-2xl group-hover:shadow-purple-500/30">
{isPlaying ? (
<MdPause className="w-16 h-16 text-white" />
) : (
<MdPlayArrow className="w-16 h-16 text-white" />
)}
</div>
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 text-sm text-purple-300 opacity-0 group-hover:opacity-100 transition-opacity">
{isPlaying ? 'Pause' : 'Écouter le mix'}
</div>
</button>
</div>
</div>
</div>
</div>
</section>
<div className={styles.contentOverlay}> {/* Section 1: Interactive Sections */}
<div className={styles.heroContent}> <section id="section1" className={styles.sectionContainer}>
{/* Left side: Title and content */} <div className={styles.splitSection}>
<div className="md:w-1/2"> <div>
<h1 className={`${styles.heroTitle} ${styles.glitchEffect}`}> <div
ParVagues className={`${styles.bulletPoint} cursor-pointer ${selectedSection === 'potentiel' ? 'text-purple-400' : ''}`}
</h1> onClick={() => setSelectedSection('potentiel')}
<p className={styles.heroSubtitle}> >
Livecoding de musique open-source avec TidalCycles et contrôleur MIDI <h3 className="text-xl font-semibold text-purple-400 mb-2">Potentiel</h3>
</p> <p className="text-gray-300">Samples et synthés SuperCollider pour une palette sonore infinie</p>
<a href="#section1" onClick={scrollToSection} className={styles.plungeButton}> </div>
Plonger
</a> <div
className={`${styles.bulletPoint} cursor-pointer ${selectedSection === 'composition' ? 'text-purple-400' : ''}`}
onClick={() => setSelectedSection('composition')}
>
<h3 className="text-xl font-semibold text-purple-400 mb-2">Composition</h3>
<p className="text-gray-300">Code Haskell TidalCycles pour des patterns algorithmiques complexes</p>
</div>
<div
className={`${styles.bulletPoint} cursor-pointer ${selectedSection === 'performance' ? 'text-purple-400' : ''}`}
onClick={() => setSelectedSection('performance')}
>
<h3 className="text-xl font-semibold text-purple-400 mb-2">Performance</h3>
<p className="text-gray-300">Live improvise avec contrôleur MIDI pour une interactivité totale</p>
</div>
</div> </div>
{/* Right side: Code sample with play overlay */} <div>
<div className="md:w-1/2 relative"> <div className="mb-4 flex gap-4">
<div className="relative">
<CodeBlock height="300px" isTerminal={true}>
{tidalCode}
</CodeBlock>
{/* Pretty Play overlay */}
<button <button
onClick={toggleAudio} onClick={() => setSelectedSection('code')}
className="absolute inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center transition-all duration-300 hover:bg-black/80 group" className={`px-4 py-2 rounded-md transition-all ${
selectedSection === 'code'
? 'bg-purple-500 text-white'
: 'bg-gray-800 text-gray-300 hover:bg-gray-700'
}`}
> >
<div className="bg-gradient-to-br from-purple-500 to-pink-500 rounded-full p-8 shadow-xl shadow-purple-500/50 transition-all duration-300 group-hover:scale-110 group-hover:shadow-2xl group-hover:shadow-purple-500/30"> Code
{isPlaying ? ( </button>
<MdPause className="w-16 h-16 text-white" /> <button
) : ( onClick={() => setSelectedSection('potentiel')}
<MdPlayArrow className="w-16 h-16 text-white" /> className={`px-4 py-2 rounded-md transition-all ${
)} selectedSection === 'potentiel'
</div> ? 'bg-purple-500 text-white'
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 text-sm text-purple-300 opacity-0 group-hover:opacity-100 transition-opacity"> : 'bg-gray-800 text-gray-300 hover:bg-gray-700'
{isPlaying ? 'Pause' : 'Écouter le mix'} }`}
</div> >
Samples
</button> </button>
</div> </div>
{renderSectionContent()}
</div> </div>
</div> </div>
</div> </section>
</section>
{/* Section 2: Music */}
{/* Section 1: Interactive Sections */} <section id="music" className={styles.sectionContainer}>
<section id="section1" className={styles.sectionContainer}> <h2 className="text-3xl font-bold mb-8 text-center">
<div className={styles.splitSection}> <span className="bg-gradient-to-r from-purple-400 to-pink-600 bg-clip-text text-transparent">
<div> Tracks
<div </span>
className={`${styles.bulletPoint} cursor-pointer ${selectedSection === 'potentiel' ? 'text-purple-400' : ''}`} </h2>
onClick={() => setSelectedSection('potentiel')} <div className={styles.albumGrid}>
> {albums.map(album => (
<h3 className="text-xl font-semibold text-purple-400 mb-2">Potentiel</h3> <div
<p className="text-gray-300">Samples et synthés SuperCollider pour une palette sonore infinie</p> key={album.id}
</div> className={`${styles.albumCard} group relative overflow-hidden aspect-square`}
<div
className={`${styles.bulletPoint} cursor-pointer ${selectedSection === 'composition' ? 'text-purple-400' : ''}`}
onClick={() => setSelectedSection('composition')}
>
<h3 className="text-xl font-semibold text-purple-400 mb-2">Composition</h3>
<p className="text-gray-300">Code Haskell TidalCycles pour des patterns algorithmiques complexes</p>
</div>
<div
className={`${styles.bulletPoint} cursor-pointer ${selectedSection === 'performance' ? 'text-purple-400' : ''}`}
onClick={() => setSelectedSection('performance')}
>
<h3 className="text-xl font-semibold text-purple-400 mb-2">Performance</h3>
<p className="text-gray-300">Live improvise avec contrôleur MIDI pour une interactivité totale</p>
</div>
</div>
<div>
<div className="mb-4 flex gap-4">
<button
onClick={() => setSelectedSection('code')}
className={`px-4 py-2 rounded-md transition-all ${
selectedSection === 'code'
? 'bg-purple-500 text-white'
: 'bg-gray-800 text-gray-300 hover:bg-gray-700'
}`}
>
Code
</button>
<button
onClick={() => setSelectedSection('potentiel')}
className={`px-4 py-2 rounded-md transition-all ${
selectedSection === 'potentiel'
? 'bg-purple-500 text-white'
: 'bg-gray-800 text-gray-300 hover:bg-gray-700'
}`}
> >
Samples <Image
</button> src={album.image}
</div> alt={album.title}
{renderSectionContent()} fill
className={`${styles.albumImage} object-cover transition-opacity duration-300 group-hover:opacity-50`}
/>
<div className="absolute inset-0 flex flex-col justify-center items-center opacity-0 group-hover:opacity-100 transition-opacity duration-300 bg-black/60">
<div className={styles.playerContainer}>
{album.links.map(link => (
<a
key={link.platform}
href={link.url}
target="_blank"
rel="noopener noreferrer"
className={`${styles.playerButton} text-white hover:text-purple-400`}
>
{link.icon}
<span className="ml-2">{link.platform}</span>
</a>
))}
</div>
</div>
<h3 className="absolute bottom-0 left-0 right-0 p-2 text-lg font-semibold text-white bg-black/50 text-center group-hover:hidden">
{album.title}
</h3>
</div>
))}
</div> </div>
</div> </section>
</section>
{/* Section 3: Performances */}
{/* Section 2: Find me */} <section id="performances" className={styles.sectionContainer}>
<section className={styles.sectionContainer}> <h2 className="text-3xl font-bold mb-8 text-center">
<h2 className="text-3xl font-bold mb-8 text-center"> <span className="bg-gradient-to-r from-purple-400 to-pink-600 bg-clip-text text-transparent">
<span className="bg-gradient-to-r from-purple-400 to-pink-600 bg-clip-text text-transparent"> Performances
Tracks </span>
</span> </h2>
</h2> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className={styles.albumGrid}> {posterImages.map((src, index) => (
{albums.map(album => ( <div key={index} className="relative aspect-[3/4] rounded-lg overflow-hidden group">
<div <Image
key={album.id} src={src}
className={`${styles.albumCard} group relative overflow-hidden aspect-square`} alt={`Performance ${index + 1}`}
> fill
<Image className="object-cover transition-transform duration-500 group-hover:scale-110"
src={album.image} />
alt={album.title} <div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent opacity-60 group-hover:opacity-80 transition-opacity">
fill </div>
className={`${styles.albumImage} object-cover transition-opacity duration-300 group-hover:opacity-50`} <div className="absolute bottom-0 left-0 w-full p-4 text-white">
/> <h3 className="text-xl font-bold mb-1">Live {2022 + Math.floor(index/2)}</h3>
<div className="absolute inset-0 flex flex-col justify-center items-center opacity-0 group-hover:opacity-100 transition-opacity duration-300 bg-black/60"> <p className="text-sm opacity-80">Algorave Paris</p>
<div className={styles.playerContainer}>
{album.links.map(link => (
<a
key={link.platform}
href={link.url}
target="_blank"
rel="noopener noreferrer"
className={`${styles.playerButton} text-white hover:text-purple-400`}
>
{link.icon}
<span className="ml-2">{link.platform}</span>
</a>
))}
</div> </div>
</div> </div>
<h3 className="absolute bottom-0 left-0 right-0 p-2 text-lg font-semibold text-white bg-black/50 text-center group-hover:hidden"> ))}
{album.title} </div>
</h3> </section>
</div>
))} {/* Section 4: About */}
</div> <section id="about" className={`${styles.sectionContainer} pb-24`}>
</section> <h2 className="text-3xl font-bold mb-8 text-center">
<span className="bg-gradient-to-r from-purple-400 to-pink-600 bg-clip-text text-transparent">
À propos
</span>
</h2>
<div className="max-w-3xl mx-auto text-gray-300 space-y-4">
<p>
ParVagues est un projet de musique algorithmique qui utilise le live coding pour créer des performances immersives et uniques.
Chaque set est programmé en direct avec le langage TidalCycles, basé sur Haskell, qui permet de créer des patterns rythmiques et mélodiques complexes.
</p>
<p>
L'approche créative combine improvisation, expérimentation sonore et visualisation du code pour une expérience audiovisuelle complète.
Le code source est projeté pendant les performances, rendant visible le processus de création musicale.
</p>
<blockquote className="border-l-4 border-purple-500 pl-4 italic my-6">
"La programmation en direct permet une interaction unique avec le public. Chaque performance est une exploration nouvelle de l'espace sonore algorithmique."
</blockquote>
<p>
ParVagues se produit régulièrement dans des festivals d'art numérique, événements algorave et espaces dédiés aux nouvelles formes d'expression musicale.
</p>
</div>
</section>
</main>
{/* Footer - not sticky */}
<ParVaguesFooter />
</div> </div>
</> </>
); );
......
...@@ -186,23 +186,17 @@ export default function Live({ data, slug, images }) { ...@@ -186,23 +186,17 @@ export default function Live({ data, slug, images }) {
> >
{/* Background poster image with blur */} {/* Background poster image with blur */}
{posterImage && ( {posterImage && (
<div className="absolute inset-0 overflow-hidden z-0"> <div className={styles.posterBackground}>
<div className="absolute inset-0 w-[120%] h-[120%] -left-[10%] -top-[10%]"> <Image
<Image
src={posterImage} src={posterImage}
alt={data.frontmatter.title} alt={data.frontmatter.title}
fill fill
className="object-cover opacity-40" className={styles.posterImage}
style={{
filter: 'blur(24px)',
transform: 'scale(1.1)',
}}
/> />
</div> <div className={styles.posterGradient}></div>
<div className="absolute inset-0 bg-gradient-to-b from-black/40 via-transparent to-black/90"></div>
</div> </div>
)} )}
{/* Use the ParVaguesHeader component */} {/* Use the ParVaguesHeader component */}
<ParVaguesHeader isSticky={isHeaderSticky} eventName={data.frontmatter.title} /> <ParVaguesHeader isSticky={isHeaderSticky} eventName={data.frontmatter.title} />
...@@ -223,12 +217,12 @@ export default function Live({ data, slug, images }) { ...@@ -223,12 +217,12 @@ export default function Live({ data, slug, images }) {
{/* Left column: Title and info (3/5 width) */} {/* Left column: Title and info (3/5 width) */}
<div className="md:col-span-3 p-8 md:p-10"> <div className="md:col-span-3 p-8 md:p-10">
<h1 className="text-4xl md:text-5xl font-bold mb-4"> <h1 className="text-4xl md:text-5xl font-bold mb-4">
<span className={`bg-gradient-to-r from-purple-400 via-pink-500 to-purple-600 bg-clip-text text-transparent ${styles.glitchEffect}`}> <span className={`bg-gradient-to-r from-purple-400 via-pink-500 to-purple-600 bg-clip-text text-transparent z-10 ${styles.glitchEffect}`}>
{data.frontmatter.title} {data.frontmatter.title}
</span> </span>
</h1> </h1>
<h2 className="text-2xl md:text-3xl font-bold mb-4 text-gray-200"> <h2 className="text-2xl md:text-3xl font-bold mb-4 text-gray-200 z-10">
{data.frontmatter.subtitle || 'Événement ParVagues'} {data.frontmatter.subtitle || 'ParVagues'}
</h2> </h2>
<div className="text-gray-300 mb-6"> <div className="text-gray-300 mb-6">
<p className="text-xl">{data.frontmatter.location}</p> <p className="text-xl">{data.frontmatter.location}</p>
...@@ -403,7 +397,8 @@ export default function Live({ data, slug, images }) { ...@@ -403,7 +397,8 @@ export default function Live({ data, slug, images }) {
)} )}
</article> </article>
</main> </main>
{/* Footer */} {/* Footer */}
<footer className={styles.footer}> <footer className={styles.footer}>
<p className="text-gray-400">© {new Date().getFullYear()} ParVagues</p> <p className="text-gray-400">© {new Date().getFullYear()} ParVagues</p>
......
...@@ -7,9 +7,9 @@ ...@@ -7,9 +7,9 @@
} }
.heroTitle { .heroTitle {
font-size: clamp(4rem, 12vw, 9rem); font-size: clamp(4rem, 9vw, 9rem);
font-weight: 800; font-weight: 800;
margin: 1.5rem; margin: 1.5rem 0; /* less on the left and right */
padding: 0.5rem; padding: 0.5rem;
text-wrap: balance; text-wrap: balance;
} }
...@@ -335,6 +335,13 @@ ...@@ -335,6 +335,13 @@
gap: 0.5rem; gap: 0.5rem;
} }
.posterBackground {
position: absolute;
inset: 0;
opacity: 0.6;
filter: blur(4px);
}
.posterImage { .posterImage {
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -342,6 +349,12 @@ ...@@ -342,6 +349,12 @@
opacity: 0.7; opacity: 0.7;
} }
.posterGradient {
position: absolute;
inset: 0;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.9));
}
.glitchEffect { .glitchEffect {
animation: glitch 8s linear infinite; animation: glitch 8s linear infinite;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment