Commit 38870816 by PLN (Algolia)

wip: Header and albums

parent 9291a1fc
import React from 'react';
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 }) {
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`}
>
<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
</Link>
{isSticky && eventName && (
<>
<span className="text-gray-500 mx-2">|</span>
<span className="text-white">{eventName}</span>
</>
)}
</div>
{/* CTA button that appears when sticky */}
<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
</a>
</div>
</header>
);
}
\ No newline at end of file
......@@ -2,13 +2,14 @@ import Head from 'next/head';
import Link from 'next/link';
import Image from 'next/image';
import { useState, useEffect, useRef } from 'react';
import { getAllLives } from '../lib/livesData';
import styles from '../styles/parvagues.module.css';
import { getAllLives } from '@/lib/livesData';
import styles from '@/styles/parvagues.module.css';
import dynamic from 'next/dynamic';
import ParVaguesHeader from '@/components/ParVaguesHeader';
// 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 { SiTidal, SiBluesky, SiMastodon } from 'react-icons/si';
import { MdPlayArrow, MdPause } from 'react-icons/md';
// Dynamically import Prism.js with no SSR
......@@ -86,6 +87,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 backgroundRef = useRef(null);
const audioRef = useRef(null);
......@@ -173,6 +175,21 @@ 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');
......@@ -199,8 +216,9 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
const albums = [
{
id: 'livecoding',
id: '2024_opal',
title: 'Livecoding (Opal Festival 2024)',
image: '/images/parvagues/albums/2024_opal/cover.jpg',
links: [
{ platform: 'YouTube', url: 'https://www.youtube.com/playlist?list=OLAK5uy_l4MF3OCIXcdPMpsHGVX2Q9MiX6oU1zT6g', icon: <FaYoutube /> },
{ platform: 'Deezer', url: 'https://www.deezer.com/us/album/656760591', icon: <FaDeezer /> },
......@@ -211,8 +229,9 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
]
},
{
id: 'connexion',
id: '2023_connexion',
title: 'Connexion Etablie EP',
image: '/images/parvagues/albums/2023_connexion/cover.jpg',
links: [
{ platform: 'YouTube', url: 'https://www.youtube.com/watch?v=VODSdQKrzyw&list=OLAK5uy_nzlx3b7YJYzrbagXF5swhENsCg5vJkT_Q', icon: <FaYoutube /> },
{ platform: 'Spotify', url: 'https://open.spotify.com/album/4uzSN6Uv9IwcYeHdRtkUmM', icon: <FaSpotify /> },
......@@ -249,24 +268,45 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
);
case 'carousel':
return (
<div className="relative w-full aspect-video rounded-lg overflow-hidden shadow-xl">
{sectionImages.map((image, index) => (
<Image
key={image}
src={image}
alt={`${selectedSection} ${index + 1}`}
fill
className={`object-cover transition-opacity duration-500 ${
index === currentImageIndex ? 'opacity-100' : 'opacity-0'
}`}
/>
<div className="relative w-full aspect-video rounded-lg overflow-hidden shadow-xl group">
{albums.map((album, index) => (
<div key={album.id} className="absolute inset-0">
<Image
src={album.image}
alt={album.title}
fill
className={`object-cover transition-all duration-500 group-hover:blur-sm group-hover:opacity-40 ${
index === currentAlbumIndex ? 'opacity-100' : 'opacity-0'
}`}
/>
{index === currentAlbumIndex && (
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity duration-500 flex items-center justify-center">
<div className="text-white text-center">
<h3 className="text-2xl font-bold mb-4">{album.title}</h3>
<div className="flex justify-center space-x-4">
{album.links.map((link) => (
<a
key={link.platform}
href={link.url}
target="_blank"
rel="noopener noreferrer"
className="text-3xl hover:text-purple-400 transition-colors"
>
{link.icon}
</a>
))}
</div>
</div>
</div>
)}
</div>
))}
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex gap-2">
{sectionImages.map((_, index) => (
{albums.map((_, index) => (
<div
key={index}
className={`w-2 h-2 rounded-full transition-all ${
index === currentImageIndex
index === currentAlbumIndex
? 'bg-purple-400 w-6'
: 'bg-gray-400'
}`}
......@@ -294,11 +334,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">
{/* Sticky CTA */}
<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}
className={`${styles.ctaButton} ${isHeaderSticky ? 'opacity-0' : 'opacity-100'} transition-opacity duration-300`}
>
<FaEnvelope className="inline mr-2" />
Invoquer un live
......@@ -436,19 +479,19 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
Tracks
</span>
</h2>
<div className={styles.albumGrid}>
{albums.map(album => (
<div key={album.id} className={styles.albumCard}>
<h3 className="text-lg font-semibold mb-3">{album.title}</h3>
{!showPlayers[album.id] ? (
<button
onClick={() => togglePlayer(album.id)}
className="text-purple-400 hover:text-purple-300 text-sm transition-colors flex items-center gap-2"
>
<MdPlayArrow /> Afficher les lecteurs
</button>
) : (
<div
key={album.id}
className={`${styles.albumCard} group relative overflow-hidden aspect-square`}
>
<Image
src={album.image}
alt={album.title}
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
......@@ -456,50 +499,21 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
href={link.url}
target="_blank"
rel="noopener noreferrer"
className={styles.playerButton}
className={`${styles.playerButton} text-white hover:text-purple-400`}
>
{link.icon}
<span>{link.platform}</span>
<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>
</section>
{/* Section 3: Reach me */}
<section 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">
Reach me
</span>
</h2>
<div className={styles.socialLinks}>
{socialLinks.map(link => (
<a
key={link.label}
href={link.url}
className={styles.socialLink}
target="_blank"
rel="noopener noreferrer"
>
{link.icon}
<span>{link.label}</span>
</a>
))}
</div>
</section>
{/* Footer */}
<footer className={styles.footer}>
<p className="text-gray-400">© {new Date().getFullYear()} ParVagues</p>
<Link href="/" className="mt-4 inline-block text-gray-500 text-sm hover:text-purple-400 transition-colors">
[more PLN Works]
</Link>
</footer>
</div>
</>
);
......
import { useEffect, useState } from 'react';
import { useEffect, useState, useRef } from 'react';
import { getAllLives, getLiveData, getLivesImages } from '../../../lib/livesData';
import ImageGallery from '../../../components/ImageGallery';
import ImageGallery from '@/components/ImageGallery';
import ParVaguesHeader from '@/components/ParVaguesHeader';
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';
import styles from '../../../styles/parvagues.module.css';
import styles from '@/styles/parvagues.module.css';
import { FaEnvelope, FaYoutube } from 'react-icons/fa';
export default function Live({ data, slug, images }) {
const [timeToEvent, setTimeToEvent] = useState(null);
const [currentTeasing, setCurrentTeasing] = useState(null);
const [pastEvent, setPastEvent] = useState(false);
const [teasingsToShow, setTeasingsToShow] = useState([]);
const [posterImage, setPosterImage] = useState(null);
const [mainColor, setMainColor] = useState('#a855f7');
const [isHeaderSticky, setIsHeaderSticky] = useState(false);
const headerRef = useRef(null);
// Find poster image
// Find poster or live image for background
useEffect(() => {
if (images && images.length > 0) {
const poster = images.find(img => img.includes('poster.'));
const liveImage = images.find(img => img.includes('live.'));
if (poster) {
setPosterImage(poster);
// Here you could use a color extraction library like 'color-thief' to get dominant color
// For now, using default purple
} else if (liveImage) {
setPosterImage(liveImage);
}
// Could add color extraction here in the future
}
}, [images]);
// Handle Prism code highlighting
useEffect(() => {
if (typeof window !== 'undefined') {
Prism.highlightAll();
}
}, [currentTeasing]);
}, [teasingsToShow]);
// 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);
}, []);
// Calculate time to event and set appropriate teasings
useEffect(() => {
const updateCountdown = () => {
const now = new Date();
const eventDate = new Date(data.frontmatter.date);
const timeDiff = eventDate - now;
const daysDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
if (timeDiff > 0) {
const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
setTimeToEvent(days);
setTimeToEvent(daysDiff);
setPastEvent(false);
// Set which teasings to show based on days remaining
const teasings = [];
if (data.frontmatter.teasing1) teasings.push(data.frontmatter.teasing1);
if (daysDiff <= 14 && data.frontmatter.teasing2) {
teasings.push(data.frontmatter.teasing2);
}
// Set appropriate teasing based on days remaining
if (days > 7) {
setCurrentTeasing(data.frontmatter.teasing1);
} else if (days > 3) {
setCurrentTeasing(data.frontmatter.teasing2);
} else {
setCurrentTeasing(data.frontmatter.teasing3);
if (daysDiff <= 7 && data.frontmatter.teasing3) {
teasings.push(data.frontmatter.teasing3);
}
// Always show most recent teasing at the top
setTeasingsToShow(teasings.reverse());
} else {
setTimeToEvent(-1); // Event has passed
setTimeToEvent(-1);
setPastEvent(true);
// Show all teasings for past events
const teasings = [];
if (data.frontmatter.teasing3) teasings.push(data.frontmatter.teasing3);
if (data.frontmatter.teasing2) teasings.push(data.frontmatter.teasing2);
if (data.frontmatter.teasing1) teasings.push(data.frontmatter.teasing1);
setTeasingsToShow(teasings);
}
};
......@@ -60,13 +103,72 @@ export default function Live({ data, slug, images }) {
const interval = setInterval(updateCountdown, 1000 * 60 * 60); // Update every hour
return () => clearInterval(interval);
}, [data.frontmatter.date]);
}, [data.frontmatter]);
// Helper function to render markdown
const renderMarkdown = (content) => {
if (!content) return { __html: '' };
const html = marked(content);
return { __html: html };
};
// Helper function to format date for display
const formatEventDate = (dateString) => {
const date = new Date(dateString);
return new Intl.DateTimeFormat('fr-FR', {
day: 'numeric',
month: 'long',
year: 'numeric'
}).format(date);
};
// Scroll to details section
const scrollToSection = (e) => {
e.preventDefault();
const section = document.getElementById('details-section');
section?.scrollIntoView({ behavior: 'smooth' });
};
// Render YouTube embed if URL is provided
const renderYouTubeEmbed = (url) => {
if (!url) return null;
// Extract YouTube video ID
const videoId = url.match(/(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))([^?&]+)/)?.[1];
if (!videoId) return null;
return (
<div className="aspect-video w-full rounded-lg overflow-hidden shadow-lg">
<iframe
width="100%"
height="100%"
src={`https://www.youtube.com/embed/${videoId}`}
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen>
</iframe>
</div>
);
};
// Render audio player if URL is provided
const renderAudioPlayer = (url) => {
if (!url) return null;
return (
<div className="my-6">
<h3 className="text-xl font-semibold text-purple-400 mb-3">Audio</h3>
<audio
controls
className="w-full"
src={url}>
Votre navigateur ne supporte pas l'élément audio.
</audio>
</div>
);
};
return (
<>
......@@ -75,135 +177,240 @@ export default function Live({ data, slug, images }) {
<meta name="description" content={data.frontmatter.description} />
</Head>
{/* Above fold section */}
<div
className="min-h-screen text-white relative overflow-hidden"
style={{
background: `radial-gradient(circle at 50% 0%, ${mainColor}20 0%, #000 60%)`
background: mainColor ? `radial-gradient(circle at 50% 0%, ${mainColor}20 0%, #000 60%)` : '#000'
}}
>
{/* Gradient overlay */}
<div className="fixed inset-0" style={{
background: `linear-gradient(135deg, #000 0%, ${mainColor}10 50%, #000 100%)`
}} />
<div className="relative z-10">
<nav className="sticky top-0 bg-black/60 backdrop-blur-md border-b border-purple-500/20">
<div className="max-w-6xl mx-auto px-4 py-4">
<Link href="/parvagues" className="text-purple-400 hover:text-purple-300 transition-colors">
ParVagues
</Link>
</div>
</nav>
<main className="max-w-6xl mx-auto px-4 py-8">
{/* Visible Poster Section */}
{posterImage && (
<div className="max-w-2xl mx-auto mb-8">
<div className="relative w-full aspect-[3/4] rounded-lg overflow-hidden shadow-xl shadow-purple-500/20">
<Image
src={posterImage}
alt={data.frontmatter.title}
fill
className="object-cover"
/>
</div>
</div>
)}
<div className="mb-8 text-center">
<h1 className="text-4xl font-bold mb-2">
<span className={`bg-gradient-to-r from-purple-400 via-pink-500 to-purple-600 bg-clip-text text-transparent ${styles.glitchEffect}`}>
{data.frontmatter.title}
</span>
</h1>
<p className="text-gray-400">{data.frontmatter.location} {data.frontmatter.date}</p>
{data.frontmatter.time && (
<p className="text-gray-500 text-sm">{data.frontmatter.time}</p>
)}
{/* 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%]">
<Image
src={posterImage}
alt={data.frontmatter.title}
fill
className="object-cover opacity-40"
style={{
filter: 'blur(24px)',
transform: 'scale(1.1)',
}}
/>
</div>
{timeToEvent !== null && timeToEvent >= 0 ? (
// Pre-event view
<div className="space-y-8">
<div className="text-center">
<div className="inline-block bg-gradient-to-r from-purple-500 to-pink-500 p-8 rounded-lg shadow-lg shadow-purple-500/20">
<div className="text-6xl font-mono mb-2">{timeToEvent}</div>
<div className="text-gray-200">jours restants</div>
</div>
</div>
<div className="prose prose-purple prose-invert max-w-none">
<div dangerouslySetInnerHTML={renderMarkdown(data.frontmatter.description)} />
</div>
{currentTeasing && (
<div className="bg-black/40 backdrop-blur-sm rounded-lg p-6 border border-purple-500/20">
<div
className="prose prose-purple prose-invert prose-pre:bg-gray-900/50 prose-pre:border prose-pre:border-purple-500/20 max-w-none"
dangerouslySetInnerHTML={renderMarkdown(currentTeasing)}
/>
<div className="absolute inset-0 bg-gradient-to-b from-black/40 via-transparent to-black/90"></div>
</div>
)}
{/* Use the ParVaguesHeader component */}
<ParVaguesHeader isSticky={isHeaderSticky} eventName={data.frontmatter.title} />
{/* 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>
{/* Main hero content */}
<div className="relative z-10 flex items-center justify-center min-h-screen pt-16 pb-20">
<div className="w-full max-w-6xl mx-auto px-4 md:px-6 lg:px-8">
<article className="w-full mx-auto bg-black/80 backdrop-filter backdrop-blur-sm rounded-2xl overflow-hidden border border-purple-500/20 shadow-xl shadow-purple-900/20">
<div className="grid md:grid-cols-5 gap-0">
{/* 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}`}>
{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>
<div className="text-gray-300 mb-6">
<p className="text-xl">{data.frontmatter.location}</p>
<p>{formatEventDate(data.frontmatter.date)} {data.frontmatter.time && `• ${data.frontmatter.time}`}</p>
</div>
)}
{data.frontmatter.ctaURL && (
<div className="text-center">
{/* Countdown or date display */}
{timeToEvent !== null && (
<div className="mb-8">
{timeToEvent >= 0 ? (
<div className="inline-flex items-center bg-black/60 p-4 rounded-lg border border-purple-500/30">
<div className="text-3xl font-bold mr-3 text-purple-400">J-{timeToEvent}</div>
<div className="text-gray-300">avant l'événement</div>
</div>
) : (
<div className="inline-flex items-center bg-black/60 p-4 rounded-lg border border-purple-500/30">
<div className="text-gray-300">Événement passé</div>
</div>
)}
</div>
)}
{/* CTA button and scroll link */}
<div className="flex flex-wrap gap-4">
{data.frontmatter.ctaURL && !pastEvent && (
<a
href={data.frontmatter.ctaURL}
className="inline-block bg-gradient-to-r from-purple-500 to-pink-500 text-white px-8 py-3 rounded-lg font-semibold hover:from-purple-600 hover:to-pink-600 transition-all transform hover:scale-105 shadow-lg shadow-purple-500/20"
>
{data.frontmatter.ctaText || "S'inscrire"}
</a>
)}
<a
href={data.frontmatter.ctaURL}
className="inline-block bg-gradient-to-r from-purple-500 to-pink-500 text-white px-8 py-3 rounded-lg font-semibold hover:from-purple-600 hover:to-pink-600 transition-all transform hover:scale-105 shadow-lg shadow-purple-500/20"
href="#details-section"
onClick={scrollToSection}
className="inline-block bg-black/70 text-white border border-purple-500/30 px-8 py-3 rounded-lg font-semibold hover:bg-black/90 hover:border-purple-500/50 transition-all"
>
{data.frontmatter.ctaText || data.frontmatter.title}
Détails
</a>
</div>
)}
</div>
</div>
</article>
</div>
</div>
{/* Floating tags in background */}
{data.frontmatter.tags && data.frontmatter.tags.length > 0 && (
<div className="absolute inset-0 overflow-hidden pointer-events-none">
{data.frontmatter.tags.map((tag, index) => (
<div
key={index}
className="absolute text-purple-500/20 font-bold text-3xl md:text-6xl uppercase select-none"
style={{
top: `${Math.random() * 100}%`,
left: `${Math.random() * 100}%`,
transform: `rotate(${Math.random() * 60 - 30}deg)`,
opacity: 0.1 + Math.random() * 0.3
}}
>
{tag}
</div>
) : (
// Post-event view
<div className="space-y-8">
<div className="prose prose-purple prose-invert max-w-none">
<div dangerouslySetInnerHTML={renderMarkdown(data.content)} />
))}
</div>
)}
</div>
{/* Below fold section */}
<div id="details-section" className="min-h-screen bg-black text-white relative">
{/* Gradient overlay */}
<div className="absolute inset-0" style={{
background: `linear-gradient(135deg, #000 0%, ${mainColor}10 50%, #000 100%)`
}} />
{/* Content */}
<div className="relative z-10">
<main className="w-full max-w-6xl mx-auto px-4 md:px-6 lg:px-8 py-16">
<article className="bg-black/80 backdrop-filter backdrop-blur-sm rounded-2xl overflow-hidden border border-purple-500/20 shadow-xl shadow-purple-900/20 p-6 md:p-10">
{pastEvent ? (
// Post-event view
<div className="space-y-12">
{/* Media section */}
{(data.frontmatter.video || data.frontmatter.audio) && (
<div className="space-y-8">
<h2 className="text-3xl font-bold mb-8">
<span className="bg-gradient-to-r from-purple-400 to-pink-600 bg-clip-text text-transparent">
Captures
</span>
</h2>
{data.frontmatter.video && renderYouTubeEmbed(data.frontmatter.video)}
{data.frontmatter.audio && renderAudioPlayer(data.frontmatter.audio)}
</div>
)}
{/* Content description */}
<div className="prose prose-purple prose-invert max-w-none">
<div dangerouslySetInnerHTML={renderMarkdown(data.content)} />
</div>
{/* Gallery if available */}
{images && images.length > 0 && (
<div className="mt-12">
<h2 className="text-3xl font-bold mb-8">
<span className="bg-gradient-to-r from-purple-400 to-pink-600 bg-clip-text text-transparent">
Gallerie
</span>
</h2>
<ImageGallery images={images} slug={slug} />
</div>
)}
{/* Teasings archive */}
{teasingsToShow.length > 0 && (
<details className="group mt-12">
<summary className="cursor-pointer p-4 bg-black/50 rounded-lg hover:bg-black/70 transition-colors border border-purple-500/10">
<span className="font-mono text-purple-400">// decrypt teasings</span>
</summary>
<div className="mt-4 space-y-4 pl-4 border-l-2 border-purple-500/20">
{teasingsToShow.map((teasing, i) => (
<div key={i} className="bg-black/40 rounded p-4">
<div
className="prose prose-purple prose-invert prose-pre:bg-gray-900/70 prose-pre:border prose-pre:border-purple-500/20 max-w-none text-sm"
dangerouslySetInnerHTML={renderMarkdown(teasing)}
/>
</div>
))}
</div>
</details>
)}
</div>
{(data.frontmatter.video || data.frontmatter.audio) && (
<div className="grid md:grid-cols-2 gap-6">
{data.frontmatter.video && (
<a href={data.frontmatter.video} className="block bg-black/40 backdrop-blur-sm rounded-lg p-6 border border-purple-500/20 hover:border-purple-400 transition-colors">
<h3 className="text-xl font-semibold mb-2">📹 Vidéo</h3>
<p className="text-gray-400">Regarder la session</p>
</a>
)}
{data.frontmatter.audio && (
<a href={data.frontmatter.audio} className="block bg-black/40 backdrop-blur-sm rounded-lg p-6 border border-purple-500/20 hover:border-purple-400 transition-colors">
<h3 className="text-xl font-semibold mb-2">🎵 Audio</h3>
<p className="text-gray-400">Écouter l'enregistrement</p>
</a>
)}
) : (
// Pre-event view
<div className="space-y-12">
{/* Description */}
<div className="prose prose-purple prose-invert max-w-none">
<div dangerouslySetInnerHTML={renderMarkdown(data.frontmatter.description)} />
</div>
)}
{images && images.length > 0 && (
<ImageGallery images={images} slug={slug} />
)}
<details className="group">
<summary className="cursor-pointer p-4 bg-black/30 rounded-lg hover:bg-black/50 transition-colors border border-purple-500/10">
<span className="font-mono text-purple-400">// decrypt teasings</span>
</summary>
<div className="mt-4 space-y-4 pl-4 border-l-2 border-purple-500/20">
{[data.frontmatter.teasing1, data.frontmatter.teasing2, data.frontmatter.teasing3].map((teasing, i) =>
teasing && (
<div key={i} className="bg-black/20 rounded p-4">
{/* Teasings */}
{teasingsToShow.length > 0 && (
<div className="space-y-8">
<h2 className="text-3xl font-bold mb-4">
<span className="bg-gradient-to-r from-purple-400 to-pink-600 bg-clip-text text-transparent">
Teasings
</span>
</h2>
{teasingsToShow.map((teasing, i) => (
<div key={i} className="bg-black/60 backdrop-blur-sm rounded-lg p-6 border border-purple-500/20">
<div
className="prose prose-purple prose-invert prose-pre:bg-gray-900/50 prose-pre:border prose-pre:border-purple-500/20 max-w-none text-sm"
className="prose prose-purple prose-invert prose-pre:bg-gray-900/70 prose-pre:border prose-pre:border-purple-500/20 max-w-none"
dangerouslySetInnerHTML={renderMarkdown(teasing)}
/>
</div>
)
)}
</div>
</details>
</div>
)}
))}
</div>
)}
{/* CTA button if available */}
{data.frontmatter.ctaURL && (
<div className="text-center py-8">
<a
href={data.frontmatter.ctaURL}
className="inline-block bg-gradient-to-r from-purple-500 to-pink-500 text-white px-8 py-4 rounded-lg font-semibold hover:from-purple-600 hover:to-pink-600 transition-all transform hover:scale-105 shadow-lg shadow-purple-500/20 text-xl"
>
{data.frontmatter.ctaText || data.frontmatter.title}
</a>
</div>
)}
</div>
)}
</article>
</main>
{/* Footer */}
<footer className={styles.footer}>
<p className="text-gray-400">© {new Date().getFullYear()} ParVagues</p>
<Link href="/parvagues" className="mt-4 inline-block text-gray-500 text-sm hover:text-purple-400 transition-colors">
← Retour à ParVagues
</Link>
</footer>
</div>
</div>
</>
......
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