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,33 +6,16 @@ import { getAllLives } from '@/lib/livesData'; ...@@ -6,33 +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 ( return (
<div className={`${styles.codeContainer} ${isTerminal ? styles.terminalContainer : ''}`} style={{ maxHeight: height }}> <div className={`${styles.codeContainer} ${isTerminal ? styles.terminalContainer : ''}`} style={{ maxHeight: height }}>
{isTerminal && ( {isTerminal && (
...@@ -45,28 +28,19 @@ function CodeBlock({ children, height = '400px', isTerminal = false }) { ...@@ -45,28 +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>{children}</code> language="haskell"
</pre> style={atomOneDark}
</div> wrapLongLines={true}
); customStyle={{
} margin: 0,
borderRadius: isTerminal ? '0 0 8px 8px' : '8px',
return ( height: 'auto',
<div className={`${styles.codeContainer} ${isTerminal ? styles.terminalContainer : ''}`} style={{ maxHeight: height }}> maxHeight: isTerminal ? `calc(${height} - 30px)` : height
{isTerminal && ( }}
<div className={styles.terminalHeader}> >
<div className={styles.terminalControls}> {children}
<span className={styles.redCircle}></span> </SyntaxHighlighter>
<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>
</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);
...@@ -152,6 +126,16 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12) ...@@ -152,6 +126,16 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
} }
}, [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(() => {
if (!backgroundRef.current) return; if (!backgroundRef.current) return;
...@@ -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,21 +295,14 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12) ...@@ -334,21 +295,14 @@ 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>
{/* Main content flex-auto to push footer to bottom */}
<main className="flex-auto">
{/* Hero Section */} {/* Hero Section */}
<section className={styles.heroSection}> <section className={`${styles.heroSection} mt-0`}>
<div className={styles.posterCollage} ref={backgroundRef}> <div className={styles.posterCollage} ref={backgroundRef}>
{posterImages.map((src, i) => ( {posterImages.map((src, i) => (
<Image <Image
...@@ -472,8 +426,8 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12) ...@@ -472,8 +426,8 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
</div> </div>
</section> </section>
{/* Section 2: Find me */} {/* Section 2: Music */}
<section className={styles.sectionContainer}> <section id="music" 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">
Tracks Tracks
...@@ -514,6 +468,62 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12) ...@@ -514,6 +468,62 @@ d4 $ note ("<e3 fs3 <gs3 d4> <a3 df4>>" - 12)
))} ))}
</div> </div>
</section> </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> </div>
</> </>
); );
......
...@@ -186,20 +186,14 @@ export default function Live({ data, slug, images }) { ...@@ -186,20 +186,14 @@ 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>
)} )}
...@@ -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>
...@@ -404,6 +398,7 @@ export default function Live({ data, slug, images }) { ...@@ -404,6 +398,7 @@ 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