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';
import { FaEnvelope } from 'react-icons/fa';
import styles from '@/styles/parvagues.module.css';
export default function ParVaguesHeader({ isSticky, eventName = null }) {
export default function ParVaguesHeader({ eventName = null }) {
return (
<header
className={`${
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`}
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"
>
<div className="max-w-6xl mx-auto px-4 py-4 flex justify-between items-center">
<div className="flex items-center gap-2">
<Link href="/parvagues" className="text-purple-400 hover:text-purple-300 transition-colors">
ParVagues
ParVagues
</Link>
{isSticky && eventName && (
{eventName && (
<>
<span className="text-gray-500 mx-2">|</span>
<span className="text-white">{eventName}</span>
......@@ -25,11 +21,23 @@ export default function ParVaguesHeader({ isSticky, eventName = null }) {
)}
</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
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`}
style={{ position: 'relative', top: 'auto', right: 'auto' }}
>
<FaEnvelope className="inline mr-2" />
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"
location: "Venue TBD"
address: "Lyon, France"
description: "Live algorithmic performance"
ctaURL: "https://nech.pl/algorave-lyon"
ctaText: "Plus d'infos"
# ctaURL: "https://nech.pl/algorave-lyon"
# ctaText: "Plus d'infos"
teasing1: |
# AlgoRave preparation
......
......@@ -39,7 +39,6 @@ export async function getStaticPaths() {
}
export default function Hydra({ hydraData, sourceCode }) {
const [showCode, setShowCode] = useState(false);
return (
<Layout>
......@@ -74,31 +73,15 @@ export default function Hydra({ hydraData, sourceCode }) {
);
}
// Suggestion for later:
//
// Dynamic import with SSR disabled
// TODO: refactor to Dynamic import with SSR enabled.
// 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(
// () => import('../../components/hydra-view'),
// { 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,33 +6,16 @@ import { getAllLives } from '@/lib/livesData';
import styles from '@/styles/parvagues.module.css';
import dynamic from 'next/dynamic';
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
import { FaSpotify, FaDeezer, FaYoutube, FaApple, FaAmazon, FaInstagram, FaTwitter, FaEnvelope } from 'react-icons/fa';
import { SiTidal, SiBluesky, SiMastodon } from 'react-icons/si';
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 }) {
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 && (
......@@ -45,28 +28,19 @@ function CodeBlock({ children, height = '400px', isTerminal = false }) {
<div className={styles.terminalTitle}>ParVagues@tidal:~</div>
</div>
)}
<pre className="language-haskell">
<code>{children}</code>
</pre>
</div>
);
}
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 ref={codeRef}>{children}</code>
</pre>
<SyntaxHighlighter
language="haskell"
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>
);
}
......@@ -87,7 +61,7 @@ export default function ParVagues({ lives }) {
const [selectedSection, setSelectedSection] = useState('code');
const [sectionImages, setSectionImages] = useState([]);
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [isHeaderSticky, setIsHeaderSticky] = useState(false);
const [currentAlbumIndex, setCurrentAlbumIndex] = useState(0);
const backgroundRef = useRef(null);
const audioRef = useRef(null);
......@@ -152,6 +126,16 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
}
}, [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
useEffect(() => {
if (!backgroundRef.current) return;
......@@ -175,21 +159,6 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
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) => {
e.preventDefault();
const section = document.getElementById('section1');
......@@ -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 sectionContent = sections[selectedSection];
if (!sectionContent) return null;
......@@ -334,21 +295,14 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</Head>
<div className="min-h-screen bg-black text-white relative">
{/* Replace static CTA with ParVaguesHeader component */}
<ParVaguesHeader isSticky={isHeaderSticky} />
{/* 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>
<div className="flex flex-col min-h-screen bg-black text-white">
{/* Apply proper sticky header */}
<ParVaguesHeader />
{/* Main content flex-auto to push footer to bottom */}
<main className="flex-auto">
{/* Hero Section */}
<section className={styles.heroSection}>
<section className={`${styles.heroSection} mt-0`}>
<div className={styles.posterCollage} ref={backgroundRef}>
{posterImages.map((src, i) => (
<Image
......@@ -472,8 +426,8 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
</div>
</section>
{/* Section 2: Find me */}
<section className={styles.sectionContainer}>
{/* Section 2: Music */}
<section id="music" className={styles.sectionContainer}>
<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">
Tracks
......@@ -514,6 +468,62 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
))}
</div>
</section>
{/* Section 3: Performances */}
<section id="performances" className={styles.sectionContainer}>
<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">
Performances
</span>
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{posterImages.map((src, index) => (
<div key={index} className="relative aspect-[3/4] rounded-lg overflow-hidden group">
<Image
src={src}
alt={`Performance ${index + 1}`}
fill
className="object-cover transition-transform duration-500 group-hover:scale-110"
/>
<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">
</div>
<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>
<p className="text-sm opacity-80">Algorave Paris</p>
</div>
</div>
))}
</div>
</section>
{/* Section 4: About */}
<section id="about" className={`${styles.sectionContainer} pb-24`}>
<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>
</>
);
......
......@@ -186,20 +186,14 @@ export default function Live({ data, slug, images }) {
>
{/* Background poster image with blur */}
{posterImage && (
<div className="absolute inset-0 overflow-hidden z-0">
<div className="absolute inset-0 w-[120%] h-[120%] -left-[10%] -top-[10%]">
<div className={styles.posterBackground}>
<Image
src={posterImage}
alt={data.frontmatter.title}
fill
className="object-cover opacity-40"
style={{
filter: 'blur(24px)',
transform: 'scale(1.1)',
}}
className={styles.posterImage}
/>
</div>
<div className="absolute inset-0 bg-gradient-to-b from-black/40 via-transparent to-black/90"></div>
<div className={styles.posterGradient}></div>
</div>
)}
......@@ -223,12 +217,12 @@ export default function Live({ data, slug, images }) {
{/* Left column: Title and info (3/5 width) */}
<div className="md:col-span-3 p-8 md:p-10">
<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}
</span>
</h1>
<h2 className="text-2xl md:text-3xl font-bold mb-4 text-gray-200">
{data.frontmatter.subtitle || 'Événement ParVagues'}
<h2 className="text-2xl md:text-3xl font-bold mb-4 text-gray-200 z-10">
{data.frontmatter.subtitle || 'ParVagues'}
</h2>
<div className="text-gray-300 mb-6">
<p className="text-xl">{data.frontmatter.location}</p>
......@@ -404,6 +398,7 @@ export default function Live({ data, slug, images }) {
</article>
</main>
{/* Footer */}
<footer className={styles.footer}>
<p className="text-gray-400">© {new Date().getFullYear()} ParVagues</p>
......
......@@ -7,9 +7,9 @@
}
.heroTitle {
font-size: clamp(4rem, 12vw, 9rem);
font-size: clamp(4rem, 9vw, 9rem);
font-weight: 800;
margin: 1.5rem;
margin: 1.5rem 0; /* less on the left and right */
padding: 0.5rem;
text-wrap: balance;
}
......@@ -335,6 +335,13 @@
gap: 0.5rem;
}
.posterBackground {
position: absolute;
inset: 0;
opacity: 0.6;
filter: blur(4px);
}
.posterImage {
width: 100%;
height: 100%;
......@@ -342,6 +349,12 @@
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 {
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