Commit 2bdc2a1e by PLN (Algolia)

chore: Clean behind, choose images, iterate

parent 3af699a6
---
description:
globs: *.css
alwaysApply: false
---
# AVoid global css:
Global CSS cannot be imported from files other than your Custom <App>. Due to the Global nature of stylesheets, and to avoid conflicts, Please move all first-party global CSS imports to pages/_app.js. Or convert the import to Component-Level CSS (CSS Modules).
---
title: "[CCC] Cookie Collective Compilation Release Party"
date: "2024-10-25"
time: "XX:XX"
location: "Lieu tenu secret"
address: "Paris, France"
description: ""
# ctaURL: "https://nech.pl/algorave-lyon"
# ctaText: "Plus d'infos"
# teasing1: |
# # AlgoRave preparation
# ```tidal
# d1 $ degradeBy 0.25 $ sound "jungle:45"
# d2 $ every 3 (fast 2) $ sound "cp"
# ```
# Set in preparation for Lyon
# teasing2: |
# # Week countdown...
# ```tidal
# d8 $ chop 16 $ loopAt 2 $ sound "jungle_breaks:45"
# ```
# Details coming soon
# teasing3: |
# # Final call
# ```tidal
# d1 $ stack [sound "cp", sound "ho:3"]
# ```
# Tonight @ Lyon
# video: ""
# audio: ""
# archive: ""
# tags: ["livecoding", "algorave", "lyon", "tidal"]
# ---
# # AlgoRave Lyon 2025
# Détails à venir...
---
title: "AlgoRave Lyon 2025"
date: "2025-XX-XX"
date: "2025-05-25"
time: "XX:XX"
location: "Venue TBD"
address: "Lyon, France"
description: "Live algorithmic performance"
ctaURL: "https://nech.pl/lyon"
ctaURL: "https://nech.pl/algorave-lyon"
ctaText: "Plus d'infos"
teasing1: |
# AlgoRave preparation
......
......@@ -6,7 +6,7 @@ location: "ENSAD Paris"
address: "20 cours Saint Vincent, Issy"
description: "breakbeat.nujazz.hybrid()"
ctaURL: "https://nech.pl/ensad"
ctaText: "En savoir plus"
ctaText: "Sur inscription gratuite"
teasing1: |
# Session en préparation...
......
# Stratégie Web ParVagues - Mai 2025
## Vue d'ensemble
Mise en place d'un système complet de gestion d'événements live sur le site ParVagues avec :
- Landing page redessinée (cyberpunk aesthetic)
- Pages d'événements dynamiques avec countdown
- Système de teasings programmés
- Galerie de photos automatique
## Architecture Implémentée
### Structure des Fichiers
```
next/
├── pages/
│ ├── parvagues.js (nouvelle landing)
│ └── parvagues/live/[id].js (pages événements)
├── components/
│ └── ImageGallery.js (gallery masonry)
├── lib/
│ └── livesData.js (logique de chargement)
├── content/lives/
│ ├── 2025/
│ │ ├── ensad-2025-05-22.md
│ │ └── algorave-lyon-2025-XX-XX.md
│ └── _template.md
└── public/images/parvagues/lives/
└── [year]/[event-slug]/
```
### Fonctionnalités Clés
#### 1. Système d'Events Live
- **URL pattern**: `/parvagues/live/[slug]`
- **Countdown dynamique** : J-14, J-7, J-3 avec teasings différents
- **Mode pre/post** : Affichage conditionnel selon la date
- **Metadata YAML** : title, date, location, teasings, etc.
#### 2. Landing Page
- **Cyberpunk design** : Dark theme + purple/pink gradients
- **Rain effects** : Animations canvas pour atmosphère
- **Code samples** : Syntax highlighting TidalCycles
- **Events sidebar** : Liste des événements à venir/passés
#### 3. Galerie de Photos
- **Masonry layout** : Disposition automatique des images
- **Lightbox** : Vue agrandie des photos
- **Auto-detection** : Scan du dossier pour images
## Événements Programmés
### ENSAD Paris - 22 Mai 2025
- **Date**: 2025-05-22, 18:30-20:00
- **URL**: `/parvagues/live/ensad-2025-05-22`
- **Short link**: `nech.pl/ensad`
- **Teasings**: 3 phases programmées avec code TidalCycles
### AlgoRave Lyon - Date TBD
- **URL**: `/parvagues/live/algorave-lyon-2025-XX-XX`
- **Short link**: `nech.pl/lyon`
- **Status**: Template créé, à finaliser
## Workflow d'Utilisation
### Ajouter un Nouvel Événement
1. **Créer le fichier markdown**:
```bash
cp content/lives/_template.md content/lives/2025/mon-event-YYYY-MM-DD.md
```
2. **Ajouter les images**:
```bash
mkdir -p public/images/parvagues/lives/2025/mon-event-YYYY-MM-DD/
# Puis copier les photos (jpg/png/gif/webp)
```
3. **Configurer l'URL courte**:
- Créer `nech.pl/monlien``/parvagues/live/mon-event-YYYY-MM-DD`
## Stratégie de Communication
### Timeline Promo ENSAD
- **J-7**: Post Instagram avec teasing1 + lien nech.pl/ensad
- **J-3**: Stories avec countdown
- **J-DAY**: Live updates sur Instagram/Bluesky/Mastodon
### Content Strategy
- **Pre-event**: Focus sur l'attente, mystery, code samples
- **Post-event**: Archives, photos, liens streaming
- **Teasings**: Progression narrative avec révélations
## Next Steps
### Technique
1. **Installer dépendances**:
```bash
npm install marked prismjs react-masonry-css
```
2. **Remplacer placeholders** par vraies images
3. **Setup URL shortener** pour nech.pl/{ensad,lyon,parvagues}
### Content
1. **Photos ENSAD**: Préparer visuels pour le 22 mai
2. **Code samples**: Sélectionner meilleurs exemples TidalCycles
3. **Bio/description**: Finaliser texte de présentation
### Marketing
1. **Cross-platform**: Instagram → Mastodon → Bluesky
2. **Tracking**: Mesurer trafic via URL courtes
3. **Community**: Engager Cookie collective
## Architecture Technique
### Frontend
- **Next.js** : Static generation avec revalidation
- **Prism.js** : Syntax highlighting pour code Haskell
- **Masonry CSS** : Layout gallery responsive
- **Canvas animations** : Effets cyberpunk
### Data Flow
1. Pages pre-renders avec getStaticProps
2. Countdown updates côté client
3. Images chargées dynamiquement
4. Revalidation toutes les minutes
## Optimisations SEO
### Meta Tags
- Title dynamique par événement
- Description basée sur metadata
- Open Graph pour partage social
### Performance
- Images lazy loading
- Static generation
- Minimal JavaScript
## Maintenance
### Ajout d'Événement
- Simple : Copier template + ajouter images
- Auto-détection par filesystem
- Zero config pour nouveaux événements
### Archivage
- Passage auto pre→post event
- Photos visibles immédiatement
- Teasings cachés mais accessibles
---
**Status**: Prêt pour déploiement
**Last Update**: 11 Mai 2025
**Dependencies**: `marked prismjs react-masonry-css`
......@@ -24,7 +24,8 @@
"react-player": "^2.14.1",
"react-syntax-highlighter": "^15.5.0",
"remark": "^14.0.0",
"remark-html": "^15.0.0"
"remark-html": "^15.0.0",
"swiper": "^11.2.6"
},
"devDependencies": {
"@types/react": "^18.2.61",
......@@ -3271,6 +3272,24 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/swiper": {
"version": "11.2.6",
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.6.tgz",
"integrity": "sha512-8aXpYKtjy3DjcbzZfz+/OX/GhcU5h+looA6PbAzHMZT6ESSycSp9nAjPCenczgJyslV+rUGse64LMGpWE3PX9Q==",
"funding": [
{
"type": "patreon",
"url": "https://www.patreon.com/swiperjs"
},
{
"type": "open_collective",
"url": "http://opencollective.com/swiper"
}
],
"engines": {
"node": ">= 4.7.0"
}
},
"node_modules/trim-lines": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
......
......@@ -27,7 +27,8 @@
"react-player": "^2.14.1",
"react-syntax-highlighter": "^15.5.0",
"remark": "^14.0.0",
"remark-html": "^15.0.0"
"remark-html": "^15.0.0",
"swiper": "^11.2.6"
},
"devDependencies": {
"@types/react": "^18.2.61",
......
import 'bootstrap/dist/css/bootstrap.css'
import '../styles/main.css'
import '../styles/masonry.css'
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
......
......@@ -2,18 +2,58 @@ import Head from 'next/head';
import Link from 'next/link';
import Image from 'next/image';
import { getAllLives } from '../lib/livesData';
import { useEffect, useRef } from 'react';
import Prism from 'prismjs';
import 'prismjs/components/prism-haskell';
import 'prismjs/themes/prism-tomorrow.css';
import { useEffect, useRef, useLayoutEffect, useState } from 'react';
import dynamic from 'next/dynamic';
// Dynamically import Prism.js with no SSR
const Prism = dynamic(() => import('prismjs'), { ssr: false });
const PrismHaskell = dynamic(() => import('prismjs/components/prism-haskell'), { ssr: false });
const PrismCSS = dynamic(() => import('prismjs/themes/prism-tomorrow.css'), { ssr: false });
// Import Swiper components
const Swiper = dynamic(() => import('swiper/react').then(mod => mod.Swiper), { ssr: false });
const SwiperSlide = dynamic(() => import('swiper/react').then(mod => mod.SwiperSlide), { ssr: false });
// Import Swiper styles
import 'swiper/css';
import 'swiper/css/effect-cards';
import 'swiper/css/pagination';
import 'swiper/css/navigation';
function CodeBlock({ children, className = '' }) {
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 (
<pre className={`bg-gray-900 p-4 rounded-lg text-sm overflow-x-auto border border-purple-500/20 ${className}`}>
<code>{children}</code>
</pre>
);
}
return (
<pre className={`language-haskell bg-gray-900 p-4 rounded-lg text-sm overflow-x-auto border border-purple-500/20 ${className}`}>
<code ref={codeRef}>{children}</code>
</pre>
);
}
export default function ParVagues({ lives }) {
const rainRef = useRef(null);
useEffect(() => {
// Highlight code blocks
Prism.highlightAll();
// Subtle rain effect
const canvas = rainRef.current;
if (!canvas) return;
......@@ -71,8 +111,15 @@ export default function ParVagues({ lives }) {
return (
<>
<Head>
<title>ParVagues - Live Algorithmic Music</title>
<meta name="description" content="Live coding performances, algorithmic music, and cyberpunk aesthetics" />
<title>ParVagues - Musique Algorithmique Live</title>
<meta name="description" content="Performances de Livecoding: musique algorithmique du breakbeat à la Jungle Drum n Bass, nujazz, broken beat, ambient electronic, melodic technocyberpunk" />
<meta name="language" content="french" />
<meta name="author" content="ParVagues" />
<meta name="keywords" content="musique algorithmique, livecoding, live coding, tidalcycles, tidal, hydra, algorave, live music, live performance, musique, live, coding, open-source, free software" />
<meta name="robots" content="index, follow" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#6B486B" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-purple-900 to-gray-900 text-white overflow-hidden relative">
......@@ -86,28 +133,36 @@ export default function ParVagues({ lives }) {
{/* Hero Section */}
<section className="min-h-screen flex flex-col justify-center px-4 relative">
<div className="max-w-6xl mx-auto w-full">
<div className="flex flex-col md:flex-row items-center justify-between">
<div className="md:w-2/3 mb-8 md:mb-0">
<div className="flex flex-col md:flex-row items-center justify-between gap-8">
<div className="md:w-1/2 mb-8 md:mb-0">
<div className="relative w-full aspect-square md:aspect-[4/3] rounded-lg overflow-hidden mb-6">
<Image
src="/images/parvagues/live.jpg"
alt="ParVagues Live Performance"
fill
className="object-cover"
priority
/>
</div>
<h1 className="text-5xl md:text-7xl font-bold mb-4">
<span className="bg-gradient-to-r from-purple-400 via-pink-500 to-purple-600 bg-clip-text text-transparent">
ParVagues
</span>
</h1>
<p className="text-xl text-gray-300 mb-8 font-mono">
live.coding() breakbeat.nujazz.hybrid()
live.coding() electronic.hybrid.genres() realtime.music()
</p>
</div>
<div className="md:w-1/2 space-y-8">
<div className="grid gap-4">
<pre className="bg-gray-900 p-4 rounded-lg text-sm overflow-x-auto border border-purple-500/20">
<code className="language-haskell">{`d1 $ every 4 (0.25 <~)
<CodeBlock>{`d1 $ every 4 (0.25 <~)
$ fast 2 $ sound "cp ho:3 mt lt"
d8 $ chop 16 $ loopAt 2
$ sound "jungle_breaks:45"`}</code>
</pre>
$ sound "jungle_breaks:45"`}</CodeBlock>
</div>
</div>
{/* Live Events Sidebar */}
<div className="md:w-1/3 md:pl-8">
{/* Live Events Sidebar */}
<div className="bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 border border-purple-500/20">
<h2 className="text-xl font-semibold mb-4 text-purple-400">// upcoming</h2>
{upcomingLives.length > 0 ? (
......@@ -143,8 +198,7 @@ d8 $ chop 16 $ loopAt 2
<div className="grid md:grid-cols-2 gap-6">
<div className="bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 border border-purple-500/20">
<h3 className="text-lg font-semibold mb-3 text-purple-400">Breakbeat Deconstruction</h3>
<pre className="bg-gray-900 p-4 rounded text-sm overflow-x-auto">
<code className="language-haskell">{`d8 $ n (cat[
<CodeBlock>{`d8 $ n (cat[
0, 0, 0, [0,7],
0, [0,3], 0, [7,0],
0, 0, 7, [7,3],
......@@ -152,20 +206,17 @@ d8 $ chop 16 $ loopAt 2
])
# s "jungle_breaks"
# cut 8
# amp (range 0.7 1.2 rand)`}</code>
</pre>
# amp (range 0.7 1.2 rand)`}</CodeBlock>
</div>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-lg p-6 border border-purple-500/20">
<h3 className="text-lg font-semibold mb-3 text-purple-400">Nujazz Harmonies</h3>
<pre className="bg-gray-900 p-4 rounded text-sm overflow-x-auto">
<code className="language-haskell">{`d4 $ every 8 (|+ n "<12 19>")
<CodeBlock>{`d4 $ every 8 (|+ n "<12 19>")
$ off 0.125 (|+ n 7)
$ chord "<am9 dm9 bm7b5 e7b9>"
# s "nujazz_keys"
# lpf (sine * 1000 + 1200)
# amp 0.8`}</code>
</pre>
# amp 0.8`}</CodeBlock>
</div>
</div>
</div>
......@@ -181,33 +232,99 @@ d8 $ chop 16 $ loopAt 2
</h2>
<div className="grid md:grid-cols-3 gap-6">
<div className="aspect-video bg-gradient-to-br from-purple-500/20 to-pink-500/20 rounded-lg border border-purple-500/20 relative overflow-hidden">
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-gray-400 text-sm">
[live_coding_screenshot.png]
</div>
</div>
<div className="aspect-video relative rounded-lg overflow-hidden">
<Image
src="/images/parvagues/gear1.jpg"
alt="Live Coding Setup"
fill
className="object-cover"
/>
</div>
<div className="aspect-video bg-gradient-to-br from-purple-500/20 to-pink-500/20 rounded-lg border border-purple-500/20 relative overflow-hidden">
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-gray-400 text-sm">
[live_performance_setup.jpg]
</div>
</div>
<div className="aspect-video relative rounded-lg overflow-hidden">
<Image
src="/images/parvagues/studio1.jpg"
alt="Studio Setup"
fill
className="object-cover"
/>
</div>
<div className="aspect-video bg-gradient-to-br from-purple-500/20 to-pink-500/20 rounded-lg border border-purple-500/20 relative overflow-hidden">
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-gray-400 text-sm">
[cyberpunk_visuals.gif]
</div>
</div>
<div className="aspect-video relative rounded-lg overflow-hidden">
<Image
src="/images/parvagues/gear2.jpg"
alt="Performance Setup"
fill
className="object-cover"
/>
</div>
</div>
</div>
</section>
{/* Performance Carousel */}
<section className="py-20 px-4 overflow-hidden">
<div className="max-w-6xl mx-auto">
<h2 className="text-3xl font-bold mb-12 text-center">
<span className="bg-gradient-to-r from-purple-400 to-pink-600 bg-clip-text text-transparent">
Live Performances
</span>
</h2>
<div className="relative">
<Swiper
effect={'cards'}
grabCursor={true}
centeredSlides={true}
slidesPerView={'auto'}
spaceBetween={30}
className="w-full h-[60vh]"
>
<SwiperSlide className="relative rounded-lg overflow-hidden">
<Image
src="/images/parvagues/PXL_20241005_032846861.TS_exported_1474.jpg"
alt="Live Performance 1"
fill
className="object-cover"
/>
</SwiperSlide>
<SwiperSlide className="relative rounded-lg overflow-hidden">
<Image
src="/images/parvagues/signal-2024-09-27-20-41-19-515.jpg"
alt="Live Performance 2"
fill
className="object-cover"
/>
</SwiperSlide>
<SwiperSlide className="relative rounded-lg overflow-hidden">
<Image
src="/images/parvagues/IMG_5945_exported_77863~2.jpg"
alt="Live Performance 3"
fill
className="object-cover"
/>
</SwiperSlide>
<SwiperSlide className="relative rounded-lg overflow-hidden">
<Image
src="/images/parvagues/IMG-20230331-WA0001(1).jpg"
alt="Live Performance 4"
fill
className="object-cover"
/>
</SwiperSlide>
<SwiperSlide className="relative rounded-lg overflow-hidden">
<Image
src="/images/parvagues/img_9479.jpg"
alt="Live Performance 5"
fill
className="object-cover"
/>
</SwiperSlide>
</Swiper>
</div>
</div>
</section>
{/* Past Performances */}
<section className="py-20 px-4">
<div className="max-w-6xl mx-auto">
......@@ -243,16 +360,26 @@ d8 $ chop 16 $ loopAt 2
<footer className="py-8 px-4 border-t border-purple-500/20 bg-black/50">
<div className="max-w-6xl mx-auto text-center">
<p className="text-gray-400 text-sm">
© 2025 ParVagues | Algorithmic music & cyberpunk aesthetics
© { new Date().getFullYear() } ParVagues | Algorithmic music
</p>
<div className="flex justify-center gap-4 mt-4">
<a href="https://www.deezer.com/us/artist/103670512" className="text-gray-400 hover:text-purple-400 transition-colors">
Deezer
</a>
<a href="https://open.spotify.com/intl-fr/artist/0kznTQnx5QRhMwktmZboX4" className="text-gray-400 hover:text-purple-400 transition-colors">
Spotify
</a>
<a href="https://www.youtube.com/@parvagues" className="text-gray-400 hover:text-purple-400 transition-colors">
YouTube
</a>
<a href="https://soundcloud.com/parvagues" className="text-gray-400 hover:text-purple-400 transition-colors">
SoundCloud
</a>
<a href="https://bandcamp.com/parvagues" className="text-gray-400 hover:text-purple-400 transition-colors">
<a href="https://parvagues.bandcamp.com/" className="text-gray-400 hover:text-purple-400 transition-colors">
Bandcamp
</a>
<a href="https://instagram.com/parvagues" className="text-gray-400 hover:text-purple-400 transition-colors">
<a href="https://instagram.com/parvagues.mp3" className="text-gray-400 hover:text-purple-400 transition-colors">
Instagram
</a>
</div>
......
import { useEffect, useState } from 'react';
import { getAllLives, getLiveData, getLivesImages } from '../../../lib/livesData';
import ImageGallery from '../../../components/ImageGallery';
import '../../../styles/masonry.css';
import Head from 'next/head';
import Link from 'next/link';
import Image from 'next/image';
import { marked } from 'marked';
import Prism from 'prismjs';
import 'prismjs/components/prism-haskell';
......@@ -50,7 +48,7 @@ export default function Live({ data, slug, images }) {
}, [data.frontmatter.date]);
const renderMarkdown = (content) => {
if (!content) return '';
if (!content) return { __html: '' };
const html = marked(content);
return { __html: html };
};
......
# Placeholder for code sample screenshot
\ No newline at end of file
# This is a placeholder - replace with actual live performance image
\ No newline at end of file
This image diff could not be displayed because it is too large. You can view the blob instead.
# Placeholder for live setup photo
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
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